mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-18 00:23:25 -06:00
4a2a600809
* feat(channels): add channel-owned setup contracts * test(channels): align legacy setup fixtures * chore(channels): regenerate config and SDK baselines after rebase * fix(update): run fresh doctor after current-process core changes * fix(channels): align add pre-scan with execution precedence * style(cli): format channels-cli test additions * fix(channels): restore option-before-positional channel resolution via metadata arity scan * fix(channels): keep help flags out of metadata arity escalation * test(update): mock fresh post-update doctor in current-process suites * style: format review fixes and correct entrypoint mock type * fix(channels): register only modern contract options for dual-publishing plugins * test(update): align downgrade suites with fresh-doctor child invocation * docs(channels): record empty-contract and input-forwarding invariants * fix(line): keep the shipped --token switch as a channel access token alias * fix(signal): stop treating exact cross-family loopback endpoints as bind-aligned * chore(config): regenerate docs config baselines after second rebase * style: format rebased channels add tests * fix(channels): enforce field-key and flag-name agreement in setup contracts * fix(signal): detect container endpoints for bare --http-url setup * fix(signal): ignore unconfigured accounts in transport collision checks * fix(channels): validate negated setup flags in contract and normalizer * fix(signal): preserve existing transport kind when setup detection is unreachable * style(signal): use direct boolean check in collision guard * style(signal): type test config literals * docs(update): record two-read design of fresh-doctor validation gate * fix(channels): satisfy post-rebase architecture gates * docs: refresh channel setup map --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
131 lines
4.5 KiB
TypeScript
131 lines
4.5 KiB
TypeScript
import { defineChannelSetupContract } from "openclaw/plugin-sdk/channel-setup";
|
|
// Telegram plugin module implements setup core behavior.
|
|
import type { ChannelSetupAdapter } from "openclaw/plugin-sdk/setup-runtime";
|
|
import {
|
|
createEnvPatchedAccountSetupAdapter,
|
|
patchChannelConfigForAccount,
|
|
promptResolvedAllowFrom,
|
|
splitSetupEntries,
|
|
createSetupTranslator,
|
|
type OpenClawConfig,
|
|
type WizardPrompter,
|
|
} from "openclaw/plugin-sdk/setup-runtime";
|
|
import { formatCliCommand, formatDocsLink } from "openclaw/plugin-sdk/setup-tools";
|
|
import { resolveDefaultTelegramAccountId, resolveTelegramAccount } from "./accounts.js";
|
|
import { isNumericTelegramSenderUserId } from "./allow-from.js";
|
|
import { namedAccountPromotionKeys, singleAccountKeysToMove } from "./setup-contract.js";
|
|
|
|
const t = createSetupTranslator();
|
|
|
|
const channel = "telegram" as const;
|
|
|
|
export function getTelegramTokenHelpLines(): string[] {
|
|
return [
|
|
t("wizard.telegram.tokenHelpOpenBotFather"),
|
|
t("wizard.telegram.tokenHelpNewBot"),
|
|
t("wizard.telegram.tokenHelpCopyToken"),
|
|
// Telegram's documented BotFather Mini App deep link (core.telegram.org/bots/features);
|
|
// web-based alternative to the /newbot chat flow, also works on web.telegram.org.
|
|
t("wizard.telegram.tokenHelpWebApp", { url: "https://t.me/BotFather?startapp" }),
|
|
t("wizard.telegram.tokenEnvTip"),
|
|
t("wizard.channels.docs", { link: formatDocsLink("/telegram") }),
|
|
t("wizard.telegram.website", { url: "https://openclaw.ai" }),
|
|
];
|
|
}
|
|
|
|
export function getTelegramUserIdHelpLines(): string[] {
|
|
return [
|
|
t("wizard.telegram.userIdHelpLogs", {
|
|
command: formatCliCommand("openclaw logs --follow"),
|
|
}),
|
|
t("wizard.telegram.userIdHelpGetUpdates"),
|
|
t("wizard.telegram.userIdHelpThirdParty"),
|
|
t("wizard.channels.docs", { link: formatDocsLink("/telegram") }),
|
|
t("wizard.telegram.website", { url: "https://openclaw.ai" }),
|
|
];
|
|
}
|
|
|
|
function normalizeTelegramAllowFromInput(raw: string): string {
|
|
return raw
|
|
.trim()
|
|
.replace(/^(telegram|tg):/i, "")
|
|
.trim();
|
|
}
|
|
|
|
export function parseTelegramAllowFromId(raw: string): string | null {
|
|
const stripped = normalizeTelegramAllowFromInput(raw);
|
|
return isNumericTelegramSenderUserId(stripped) ? stripped : null;
|
|
}
|
|
|
|
export async function promptTelegramAllowFromForAccount(params: {
|
|
cfg: OpenClawConfig;
|
|
prompter: WizardPrompter;
|
|
accountId?: string;
|
|
}) {
|
|
const accountId = params.accountId ?? resolveDefaultTelegramAccountId(params.cfg);
|
|
const resolved = resolveTelegramAccount({ cfg: params.cfg, accountId });
|
|
await params.prompter.note(
|
|
getTelegramUserIdHelpLines().join("\n"),
|
|
t("wizard.telegram.userIdTitle"),
|
|
);
|
|
const unique = await promptResolvedAllowFrom({
|
|
prompter: params.prompter,
|
|
existing: resolved.config.allowFrom ?? [],
|
|
message: t("wizard.telegram.allowFromPrompt"),
|
|
placeholder: "123456789",
|
|
label: t("wizard.telegram.allowlistTitle"),
|
|
parseInputs: splitSetupEntries,
|
|
parseId: parseTelegramAllowFromId,
|
|
invalidWithoutTokenNote: t("wizard.telegram.allowFromInvalid"),
|
|
resolveEntries: async ({ entries }) =>
|
|
entries.map((entry) => {
|
|
const id = parseTelegramAllowFromId(entry);
|
|
return { input: entry, resolved: Boolean(id), id };
|
|
}),
|
|
});
|
|
return patchChannelConfigForAccount({
|
|
cfg: params.cfg,
|
|
channel,
|
|
accountId,
|
|
patch: { dmPolicy: "allowlist", allowFrom: unique },
|
|
setupSurface: telegramSetupAdapter,
|
|
});
|
|
}
|
|
|
|
export const telegramSetupAdapter: ChannelSetupAdapter = {
|
|
...createEnvPatchedAccountSetupAdapter({
|
|
channelKey: channel,
|
|
defaultAccountOnlyEnvError: "TELEGRAM_BOT_TOKEN can only be used for the default account.",
|
|
missingCredentialError: "Telegram requires token or --token-file (or --use-env).",
|
|
hasCredentials: (input) => Boolean(input.token || input.tokenFile),
|
|
buildPatch: (input) =>
|
|
input.tokenFile
|
|
? { tokenFile: input.tokenFile }
|
|
: input.token
|
|
? { botToken: input.token }
|
|
: {},
|
|
}),
|
|
singleAccountKeysToMove,
|
|
namedAccountPromotionKeys,
|
|
};
|
|
|
|
export const telegramSetupContract = defineChannelSetupContract({
|
|
fields: {
|
|
token: {
|
|
kind: "string",
|
|
sensitive: true,
|
|
cli: { flags: "--token <token>", description: "Telegram bot token" },
|
|
},
|
|
tokenFile: {
|
|
kind: "string",
|
|
sensitive: true,
|
|
cli: { flags: "--token-file <path>", description: "Telegram bot token file" },
|
|
},
|
|
useEnv: {
|
|
kind: "boolean",
|
|
cli: { flags: "--use-env", description: "Use TELEGRAM_BOT_TOKEN" },
|
|
},
|
|
},
|
|
legacyAdapter: telegramSetupAdapter,
|
|
});
|