refactor(telegram): share plugin base config

This commit is contained in:
Peter Steinberger
2026-03-17 03:04:27 +00:00
parent ba79d90313
commit 3cc1c7ba83
3 changed files with 167 additions and 136 deletions

View File

@@ -1,69 +1,12 @@
import {
buildChannelConfigSchema,
getChatChannelMeta,
TelegramConfigSchema,
type ChannelPlugin,
} from "../../../src/plugin-sdk-internal/telegram.js";
import { type ChannelPlugin } from "openclaw/plugin-sdk/telegram";
import { type ResolvedTelegramAccount } from "./accounts.js";
import {
findTelegramTokenOwnerAccountId,
formatDuplicateTelegramTokenReason,
telegramConfigAccessors,
telegramConfigBase,
} from "./plugin-shared.js";
import type { TelegramProbe } from "./probe.js";
import { telegramSetupAdapter } from "./setup-core.js";
import { telegramSetupWizard } from "./setup-surface.js";
import { createTelegramPluginBase } from "./shared.js";
export const telegramSetupPlugin: ChannelPlugin<ResolvedTelegramAccount, TelegramProbe> = {
id: "telegram",
meta: {
...getChatChannelMeta("telegram"),
quickstartAllowFrom: true,
},
setupWizard: telegramSetupWizard,
capabilities: {
chatTypes: ["direct", "group", "channel", "thread"],
reactions: true,
threads: true,
media: true,
polls: true,
nativeCommands: true,
blockStreaming: true,
},
reload: { configPrefixes: ["channels.telegram"] },
configSchema: buildChannelConfigSchema(TelegramConfigSchema),
config: {
...telegramConfigBase,
isConfigured: (account, cfg) => {
if (!account.token?.trim()) {
return false;
}
return !findTelegramTokenOwnerAccountId({ cfg, accountId: account.accountId });
},
unconfiguredReason: (account, cfg) => {
if (!account.token?.trim()) {
return "not configured";
}
const ownerAccountId = findTelegramTokenOwnerAccountId({ cfg, accountId: account.accountId });
if (!ownerAccountId) {
return "not configured";
}
return formatDuplicateTelegramTokenReason({
accountId: account.accountId,
ownerAccountId,
});
},
describeAccount: (account, cfg) => ({
accountId: account.accountId,
name: account.name,
enabled: account.enabled,
configured:
Boolean(account.token?.trim()) &&
!findTelegramTokenOwnerAccountId({ cfg, accountId: account.accountId }),
tokenSource: account.tokenSource,
}),
...telegramConfigAccessors,
},
setup: telegramSetupAdapter,
};
export const telegramSetupPlugin: ChannelPlugin<ResolvedTelegramAccount, TelegramProbe> =
createTelegramPluginBase({
setupWizard: telegramSetupWizard,
setup: telegramSetupAdapter,
});

View File

@@ -1,27 +1,18 @@
import { parseTelegramTopicConversation } from "../../../src/acp/conversation-id.js";
import { resolveExecApprovalCommandDisplay } from "../../../src/infra/exec-approval-command-display.js";
import { buildExecApprovalPendingReplyPayload } from "../../../src/infra/exec-approval-reply.js";
import {
type OutboundSendDeps,
resolveOutboundSendDep,
} from "../../../src/infra/outbound/send-deps.js";
import {
buildAccountScopedAllowlistConfigEditor,
collectAllowlistProviderGroupPolicyWarnings,
collectOpenGroupPolicyRouteAllowlistWarnings,
createScopedDmSecurityResolver,
} from "../../../src/plugin-sdk-internal/channel-config.js";
} from "openclaw/plugin-sdk/compat";
import {
buildAgentSessionKey,
resolveThreadSessionKeys,
type RoutePeer,
} from "../../../src/plugin-sdk-internal/core.js";
} from "openclaw/plugin-sdk/core";
import {
buildChannelConfigSchema,
buildTokenChannelStatusSummary,
clearAccountEntryFields,
DEFAULT_ACCOUNT_ID,
getChatChannelMeta,
listTelegramDirectoryGroupsFromConfig,
listTelegramDirectoryPeersFromConfig,
PAIRING_APPROVED_MESSAGE,
@@ -29,14 +20,22 @@ import {
resolveConfiguredFromCredentialStatuses,
resolveTelegramGroupRequireMention,
resolveTelegramGroupToolPolicy,
TelegramConfigSchema,
type ChannelPlugin,
type ChannelMessageActionAdapter,
type ChannelPlugin,
type OpenClawConfig,
} from "../../../src/plugin-sdk-internal/telegram.js";
} from "openclaw/plugin-sdk/telegram";
import { parseTelegramTopicConversation } from "../../../src/acp/conversation-id.js";
import { resolveExecApprovalCommandDisplay } from "../../../src/infra/exec-approval-command-display.js";
import { buildExecApprovalPendingReplyPayload } from "../../../src/infra/exec-approval-reply.js";
import {
type OutboundSendDeps,
resolveOutboundSendDep,
} from "../../../src/infra/outbound/send-deps.js";
import { normalizeMessageChannel } from "../../../src/utils/message-channel.js";
import { inspectTelegramAccount } from "./account-inspect.js";
import {
listTelegramAccountIds,
resolveDefaultTelegramAccountId,
resolveTelegramAccount,
type ResolvedTelegramAccount,
} from "./accounts.js";
@@ -51,17 +50,17 @@ import { monitorTelegramProvider } from "./monitor.js";
import { looksLikeTelegramTargetId, normalizeTelegramMessagingTarget } from "./normalize.js";
import { sendTelegramPayloadMessages } from "./outbound-adapter.js";
import { parseTelegramReplyToMessageId, parseTelegramThreadId } from "./outbound-params.js";
import {
findTelegramTokenOwnerAccountId,
formatDuplicateTelegramTokenReason,
telegramConfigAccessors,
telegramConfigBase,
} from "./plugin-shared.js";
import { probeTelegram, type TelegramProbe } from "./probe.js";
import { getTelegramRuntime } from "./runtime.js";
import { sendTypingTelegram } from "./send.js";
import { telegramSetupAdapter } from "./setup-core.js";
import { telegramSetupWizard } from "./setup-surface.js";
import {
createTelegramPluginBase,
findTelegramTokenOwnerAccountId,
formatDuplicateTelegramTokenReason,
telegramConfigAccessors,
} from "./shared.js";
import { collectTelegramStatusIssues } from "./status-issues.js";
import { parseTelegramTarget } from "./targets.js";
@@ -69,8 +68,6 @@ type TelegramSendFn = ReturnType<
typeof getTelegramRuntime
>["channel"]["telegram"]["sendMessageTelegram"];
const meta = getChatChannelMeta("telegram");
type TelegramSendOptions = NonNullable<Parameters<TelegramSendFn>[2]>;
function buildTelegramSendOptions(params: {
@@ -327,12 +324,10 @@ function readTelegramAllowlistConfig(account: ResolvedTelegramAccount) {
}
export const telegramPlugin: ChannelPlugin<ResolvedTelegramAccount, TelegramProbe> = {
id: "telegram",
meta: {
...meta,
quickstartAllowFrom: true,
},
setupWizard: telegramSetupWizard,
...createTelegramPluginBase({
setupWizard: telegramSetupWizard,
setup: telegramSetupAdapter,
}),
pairing: {
idLabel: "telegramUserId",
normalizeAllowEntry: (entry) => entry.replace(/^(telegram|tg):/i, ""),
@@ -350,49 +345,6 @@ export const telegramPlugin: ChannelPlugin<ResolvedTelegramAccount, TelegramProb
);
},
},
capabilities: {
chatTypes: ["direct", "group", "channel", "thread"],
reactions: true,
threads: true,
media: true,
polls: true,
nativeCommands: true,
blockStreaming: true,
},
reload: { configPrefixes: ["channels.telegram"] },
configSchema: buildChannelConfigSchema(TelegramConfigSchema),
config: {
...telegramConfigBase,
isConfigured: (account, cfg) => {
if (!account.token?.trim()) {
return false;
}
return !findTelegramTokenOwnerAccountId({ cfg, accountId: account.accountId });
},
unconfiguredReason: (account, cfg) => {
if (!account.token?.trim()) {
return "not configured";
}
const ownerAccountId = findTelegramTokenOwnerAccountId({ cfg, accountId: account.accountId });
if (!ownerAccountId) {
return "not configured";
}
return formatDuplicateTelegramTokenReason({
accountId: account.accountId,
ownerAccountId,
});
},
describeAccount: (account, cfg) => ({
accountId: account.accountId,
name: account.name,
enabled: account.enabled,
configured:
Boolean(account.token?.trim()) &&
!findTelegramTokenOwnerAccountId({ cfg, accountId: account.accountId }),
tokenSource: account.tokenSource,
}),
...telegramConfigAccessors,
},
allowlist: {
supportsScope: ({ scope }) => scope === "dm" || scope === "group" || scope === "all",
readConfig: ({ cfg, accountId }) =>
@@ -548,7 +500,6 @@ export const telegramPlugin: ChannelPlugin<ResolvedTelegramAccount, TelegramProb
listGroups: async (params) => listTelegramDirectoryGroupsFromConfig(params),
},
actions: telegramMessageActions,
setup: telegramSetupAdapter,
outbound: {
deliveryMode: "direct",
chunker: (text, limit) => getTelegramRuntime().channel.text.chunkMarkdownText(text, limit),

View File

@@ -0,0 +1,137 @@
import { createScopedChannelConfigBase } from "openclaw/plugin-sdk/compat";
import {
createScopedAccountConfigAccessors,
formatAllowFromLowercase,
} from "openclaw/plugin-sdk/compat";
import {
buildChannelConfigSchema,
getChatChannelMeta,
normalizeAccountId,
TelegramConfigSchema,
type ChannelPlugin,
type OpenClawConfig,
} from "openclaw/plugin-sdk/telegram";
import { inspectTelegramAccount } from "./account-inspect.js";
import {
listTelegramAccountIds,
resolveDefaultTelegramAccountId,
resolveTelegramAccount,
type ResolvedTelegramAccount,
} from "./accounts.js";
export const TELEGRAM_CHANNEL = "telegram" as const;
export function findTelegramTokenOwnerAccountId(params: {
cfg: OpenClawConfig;
accountId: string;
}): string | null {
const normalizedAccountId = normalizeAccountId(params.accountId);
const tokenOwners = new Map<string, string>();
for (const id of listTelegramAccountIds(params.cfg)) {
const account = inspectTelegramAccount({ cfg: params.cfg, accountId: id });
const token = (account.token ?? "").trim();
if (!token) {
continue;
}
const ownerAccountId = tokenOwners.get(token);
if (!ownerAccountId) {
tokenOwners.set(token, account.accountId);
continue;
}
if (account.accountId === normalizedAccountId) {
return ownerAccountId;
}
}
return null;
}
export function formatDuplicateTelegramTokenReason(params: {
accountId: string;
ownerAccountId: string;
}): string {
return (
`Duplicate Telegram bot token: account "${params.accountId}" shares a token with ` +
`account "${params.ownerAccountId}". Keep one owner account per bot token.`
);
}
export const telegramConfigAccessors = createScopedAccountConfigAccessors({
resolveAccount: ({ cfg, accountId }) => resolveTelegramAccount({ cfg, accountId }),
resolveAllowFrom: (account: ResolvedTelegramAccount) => account.config.allowFrom,
formatAllowFrom: (allowFrom) =>
formatAllowFromLowercase({ allowFrom, stripPrefixRe: /^(telegram|tg):/i }),
resolveDefaultTo: (account: ResolvedTelegramAccount) => account.config.defaultTo,
});
export const telegramConfigBase = createScopedChannelConfigBase<ResolvedTelegramAccount>({
sectionKey: TELEGRAM_CHANNEL,
listAccountIds: listTelegramAccountIds,
resolveAccount: (cfg, accountId) => resolveTelegramAccount({ cfg, accountId }),
inspectAccount: (cfg, accountId) => inspectTelegramAccount({ cfg, accountId }),
defaultAccountId: resolveDefaultTelegramAccountId,
clearBaseFields: ["botToken", "tokenFile", "name"],
});
export function createTelegramPluginBase(params: {
setupWizard: NonNullable<ChannelPlugin<ResolvedTelegramAccount>["setupWizard"]>;
setup: NonNullable<ChannelPlugin<ResolvedTelegramAccount>["setup"]>;
}): Pick<
ChannelPlugin<ResolvedTelegramAccount>,
"id" | "meta" | "setupWizard" | "capabilities" | "reload" | "configSchema" | "config" | "setup"
> {
return {
id: TELEGRAM_CHANNEL,
meta: {
...getChatChannelMeta(TELEGRAM_CHANNEL),
quickstartAllowFrom: true,
},
setupWizard: params.setupWizard,
capabilities: {
chatTypes: ["direct", "group", "channel", "thread"],
reactions: true,
threads: true,
media: true,
polls: true,
nativeCommands: true,
blockStreaming: true,
},
reload: { configPrefixes: ["channels.telegram"] },
configSchema: buildChannelConfigSchema(TelegramConfigSchema),
config: {
...telegramConfigBase,
isConfigured: (account, cfg) => {
if (!account.token?.trim()) {
return false;
}
return !findTelegramTokenOwnerAccountId({ cfg, accountId: account.accountId });
},
unconfiguredReason: (account, cfg) => {
if (!account.token?.trim()) {
return "not configured";
}
const ownerAccountId = findTelegramTokenOwnerAccountId({
cfg,
accountId: account.accountId,
});
if (!ownerAccountId) {
return "not configured";
}
return formatDuplicateTelegramTokenReason({
accountId: account.accountId,
ownerAccountId,
});
},
describeAccount: (account, cfg) => ({
accountId: account.accountId,
name: account.name,
enabled: account.enabled,
configured:
Boolean(account.token?.trim()) &&
!findTelegramTokenOwnerAccountId({ cfg, accountId: account.accountId }),
tokenSource: account.tokenSource,
}),
...telegramConfigAccessors,
},
setup: params.setup,
};
}