Compare commits

..

1 Commits

Author SHA1 Message Date
Kenneth Lien
14927ff475 telegram/discord: make state dir configurable via env var
Hardcoded ~/.claude/channels/<name>/ meant only one bot per machine.
Respect TELEGRAM_STATE_DIR / DISCORD_STATE_DIR so users can run
multiple bots with separate tokens and allowlists.

Also fixed README path ('in your project' -> '~/...') to match the code.

Fixes #792
2026-03-20 10:56:57 -07:00
4 changed files with 15 additions and 37 deletions

View File

@@ -55,7 +55,9 @@ Install the plugin:
/discord:configure MTIz...
```
Writes `DISCORD_BOT_TOKEN=...` to `.claude/channels/discord/.env` in your project. You can also write that file by hand, or set the variable in your shell environment — shell takes precedence.
Writes `DISCORD_BOT_TOKEN=...` to `~/.claude/channels/discord/.env`. You can also write that file by hand, or set the variable in your shell environment — shell takes precedence.
> To run multiple bots on one machine (different tokens, separate allowlists), point `DISCORD_STATE_DIR` at a different directory per instance.
**6. Relaunch with the channel flag.**

View File

@@ -29,7 +29,7 @@ import { readFileSync, writeFileSync, mkdirSync, readdirSync, rmSync, statSync,
import { homedir } from 'os'
import { join, sep } from 'path'
const STATE_DIR = join(homedir(), '.claude', 'channels', 'discord')
const STATE_DIR = process.env.DISCORD_STATE_DIR ?? join(homedir(), '.claude', 'channels', 'discord')
const ACCESS_FILE = join(STATE_DIR, 'access.json')
const APPROVED_DIR = join(STATE_DIR, 'approved')
const ENV_FILE = join(STATE_DIR, '.env')

View File

@@ -35,7 +35,9 @@ Install the plugin:
/telegram:configure 123456789:AAHfiqksKZ8...
```
Writes `TELEGRAM_BOT_TOKEN=...` to `.claude/channels/telegram/.env` in your project. You can also write that file by hand, or set the variable in your shell environment — shell takes precedence.
Writes `TELEGRAM_BOT_TOKEN=...` to `~/.claude/channels/telegram/.env`. You can also write that file by hand, or set the variable in your shell environment — shell takes precedence.
> To run multiple bots on one machine (different tokens, separate allowlists), point `TELEGRAM_STATE_DIR` at a different directory per instance.
**4. Relaunch with the channel flag.**

View File

@@ -15,14 +15,14 @@ import {
ListToolsRequestSchema,
CallToolRequestSchema,
} from '@modelcontextprotocol/sdk/types.js'
import { Bot, GrammyError, InputFile, type Context } from 'grammy'
import { Bot, InputFile, type Context } from 'grammy'
import type { ReactionTypeEmoji } from 'grammy/types'
import { randomBytes } from 'crypto'
import { readFileSync, writeFileSync, mkdirSync, readdirSync, rmSync, statSync, renameSync, realpathSync, chmodSync } from 'fs'
import { homedir } from 'os'
import { join, extname, sep } from 'path'
const STATE_DIR = join(homedir(), '.claude', 'channels', 'telegram')
const STATE_DIR = process.env.TELEGRAM_STATE_DIR ?? join(homedir(), '.claude', 'channels', 'telegram')
const ACCESS_FILE = join(STATE_DIR, 'access.json')
const APPROVED_DIR = join(STATE_DIR, 'approved')
const ENV_FILE = join(STATE_DIR, '.env')
@@ -593,35 +593,9 @@ async function handleInbound(
})
}
// 409 Conflict = another getUpdates consumer is still active (zombie from a
// previous session, or a second Claude Code instance). Retry with backoff
// until the slot frees up instead of crashing on the first rejection.
void (async () => {
for (let attempt = 1; ; attempt++) {
try {
await bot.start({
onStart: info => {
botUsername = info.username
process.stderr.write(`telegram channel: polling as @${info.username}\n`)
},
})
return // bot.stop() was called — clean exit from the loop
} catch (err) {
if (err instanceof GrammyError && err.error_code === 409) {
const delay = Math.min(1000 * attempt, 15000)
const detail = attempt === 1
? ' — another instance is polling (zombie session, or a second Claude Code running?)'
: ''
process.stderr.write(
`telegram channel: 409 Conflict${detail}, retrying in ${delay / 1000}s\n`,
)
await new Promise(r => setTimeout(r, delay))
continue
}
// bot.stop() mid-setup rejects with grammy's "Aborted delay" — expected, not an error.
if (err instanceof Error && err.message === 'Aborted delay') return
process.stderr.write(`telegram channel: polling failed: ${err}\n`)
return
}
}
})()
void bot.start({
onStart: info => {
botUsername = info.username
process.stderr.write(`telegram channel: polling as @${info.username}\n`)
},
})