mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-21 15:01:03 +00:00
refactor(telegram): share plugin base config
This commit is contained in:
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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),
|
||||
|
||||
137
extensions/telegram/src/shared.ts
Normal file
137
extensions/telegram/src/shared.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user