fix: prefer config tokens over env for discord/telegram

This commit is contained in:
Peter Steinberger
2026-01-16 23:12:50 +00:00
parent bf72a126d1
commit 106e308953
7 changed files with 73 additions and 13 deletions

View File

@@ -11,6 +11,7 @@
### Breaking
- **BREAKING:** iOS minimum version is now 18.0 to support Textual markdown rendering in native chat. (#702)
- **BREAKING:** Microsoft Teams is now a plugin; install `@clawdbot/msteams` via `clawdbot plugins install @clawdbot/msteams`.
- **BREAKING:** Discord/Telegram channel tokens now prefer config over env (env is fallback only).
### Changes
- CLI: set process titles to `clawdbot-<command>` for clearer process listings.

View File

@@ -13,6 +13,7 @@ Status: ready for DM and guild text channels via the official Discord bot gatewa
2) Set the token for Clawdbot:
- Env: `DISCORD_BOT_TOKEN=...`
- Or config: `channels.discord.token: "..."`.
- If both are set, config wins; env is fallback.
3) Invite the bot to your server with message permissions.
4) Start the gateway.
5) DM access is pairing by default; approve the pairing code on first contact.
@@ -39,8 +40,8 @@ Minimal config:
1. Create a Discord application → Bot, enable the intents you need (DMs + guild messages + message content), and grab the bot token.
2. Invite the bot to your server with the permissions required to read/send messages where you want to use it.
3. Configure Clawdbot with `DISCORD_BOT_TOKEN` (or `channels.discord.token` in `~/.clawdbot/clawdbot.json`).
4. Run the gateway; it auto-starts the Discord channel when a token is available (env or config) and `channels.discord.enabled` is not `false`.
- If you prefer env vars, set `DISCORD_BOT_TOKEN` (a config block is optional).
4. Run the gateway; it auto-starts the Discord channel when a token is available (config first, env fallback) and `channels.discord.enabled` is not `false`.
- If you prefer env vars, set `DISCORD_BOT_TOKEN` (and omit config).
5. Direct chats: use `user:<id>` (or a `<@id>` mention) when delivering; all turns land in the shared `main` session. Bare numeric IDs are ambiguous and rejected.
6. Guild channels: use `channel:<channelId>` for delivery. Mentions are required by default and can be set per guild or per channel.
7. Direct chats: secure by default via `channels.discord.dm.policy` (default: `"pairing"`). Unknown senders get a pairing code (expires after 1 hour); approve via `clawdbot pairing approve discord <code>`.

View File

@@ -13,6 +13,7 @@ Status: production-ready for bot DMs + groups via grammY. Long-polling by defaul
2) Set the token:
- Env: `TELEGRAM_BOT_TOKEN=...`
- Or config: `channels.telegram.botToken: "..."`.
- If both are set, config wins; env is fallback.
3) Start the gateway.
4) DM access is pairing by default; approve the pairing code on first contact.
@@ -60,11 +61,11 @@ Example:
}
```
Env option: `TELEGRAM_BOT_TOKEN=...` (works for the default account).
Env option: `TELEGRAM_BOT_TOKEN=...` (works for the default account; used only when config is missing).
Multi-account support: use `channels.telegram.accounts` with per-account tokens and optional `name`. See [`gateway/configuration`](/gateway/configuration#telegramaccounts--discordaccounts--slackaccounts--signalaccounts--imessageaccounts) for the shared pattern.
3) Start the gateway. Telegram starts when a token is resolved (env or config).
3) Start the gateway. Telegram starts when a token is resolved (config first, env fallback).
4) DM access defaults to pairing. Approve the code when the bot is first contacted.
5) For groups: add the bot, decide privacy/admin behavior (below), then set `channels.telegram.groups` to control mention gating + allowlists.

47
src/discord/token.test.ts Normal file
View File

@@ -0,0 +1,47 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import type { ClawdbotConfig } from "../config/config.js";
import { resolveDiscordToken } from "./token.js";
describe("resolveDiscordToken", () => {
afterEach(() => {
vi.unstubAllEnvs();
});
it("prefers config token over env", () => {
vi.stubEnv("DISCORD_BOT_TOKEN", "env-token");
const cfg = {
channels: { discord: { token: "cfg-token" } },
} as ClawdbotConfig;
const res = resolveDiscordToken(cfg);
expect(res.token).toBe("cfg-token");
expect(res.source).toBe("config");
});
it("uses env token when config is missing", () => {
vi.stubEnv("DISCORD_BOT_TOKEN", "env-token");
const cfg = {
channels: { discord: {} },
} as ClawdbotConfig;
const res = resolveDiscordToken(cfg);
expect(res.token).toBe("env-token");
expect(res.source).toBe("env");
});
it("prefers account token for non-default accounts", () => {
vi.stubEnv("DISCORD_BOT_TOKEN", "env-token");
const cfg = {
channels: {
discord: {
token: "base-token",
accounts: {
work: { token: "acct-token" },
},
},
},
} as ClawdbotConfig;
const res = resolveDiscordToken(cfg, { accountId: "work" });
expect(res.token).toBe("acct-token");
expect(res.source).toBe("config");
});
});

View File

@@ -29,13 +29,13 @@ export function resolveDiscordToken(
if (accountToken) return { token: accountToken, source: "config" };
const allowEnv = accountId === DEFAULT_ACCOUNT_ID;
const configToken = allowEnv ? normalizeDiscordToken(discordCfg?.token ?? undefined) : undefined;
if (configToken) return { token: configToken, source: "config" };
const envToken = allowEnv
? normalizeDiscordToken(opts.envToken ?? process.env.DISCORD_BOT_TOKEN)
: undefined;
if (envToken) return { token: envToken, source: "env" };
const configToken = allowEnv ? normalizeDiscordToken(discordCfg?.token ?? undefined) : undefined;
if (configToken) return { token: configToken, source: "config" };
return { token: "", source: "none" };
}

View File

@@ -16,12 +16,22 @@ describe("resolveTelegramToken", () => {
vi.unstubAllEnvs();
});
it("prefers env token over config", () => {
it("prefers config token over env", () => {
vi.stubEnv("TELEGRAM_BOT_TOKEN", "env-token");
const cfg = {
channels: { telegram: { botToken: "cfg-token" } },
} as ClawdbotConfig;
const res = resolveTelegramToken(cfg);
expect(res.token).toBe("cfg-token");
expect(res.source).toBe("config");
});
it("uses env token when config is missing", () => {
vi.stubEnv("TELEGRAM_BOT_TOKEN", "env-token");
const cfg = {
channels: { telegram: {} },
} as ClawdbotConfig;
const res = resolveTelegramToken(cfg);
expect(res.token).toBe("env-token");
expect(res.source).toBe("env");
});

View File

@@ -54,11 +54,6 @@ export function resolveTelegramToken(
}
const allowEnv = accountId === DEFAULT_ACCOUNT_ID;
const envToken = allowEnv ? (opts.envToken ?? process.env.TELEGRAM_BOT_TOKEN)?.trim() : "";
if (envToken) {
return { token: envToken, source: "env" };
}
const tokenFile = telegramCfg?.tokenFile?.trim();
if (tokenFile && allowEnv) {
if (!fs.existsSync(tokenFile)) {
@@ -81,5 +76,10 @@ export function resolveTelegramToken(
return { token: configToken, source: "config" };
}
const envToken = allowEnv ? (opts.envToken ?? process.env.TELEGRAM_BOT_TOKEN)?.trim() : "";
if (envToken) {
return { token: envToken, source: "env" };
}
return { token: "", source: "none" };
}