2026-01-18 08:32:19 +00:00
import {
applyAccountNameToChannelSection ,
buildChannelConfigSchema ,
2026-02-23 21:25:20 +00:00
buildTokenChannelStatusSummary ,
2026-01-18 08:32:19 +00:00
collectTelegramStatusIssues ,
DEFAULT_ACCOUNT_ID ,
deleteAccountFromConfigSection ,
formatPairingApproveHint ,
getChatChannelMeta ,
2026-03-05 23:07:13 -06:00
inspectTelegramAccount ,
2026-01-18 08:32:19 +00:00
listTelegramAccountIds ,
listTelegramDirectoryGroupsFromConfig ,
listTelegramDirectoryPeersFromConfig ,
looksLikeTelegramTargetId ,
migrateBaseNameToDefaultAccount ,
normalizeAccountId ,
normalizeTelegramMessagingTarget ,
PAIRING_APPROVED_MESSAGE ,
2026-02-15 01:07:37 +00:00
parseTelegramReplyToMessageId ,
parseTelegramThreadId ,
2026-03-05 23:07:13 -06:00
projectCredentialSnapshotFields ,
resolveConfiguredFromCredentialStatuses ,
2026-01-18 08:32:19 +00:00
resolveDefaultTelegramAccountId ,
2026-02-22 12:35:02 +01:00
resolveAllowlistProviderRuntimeGroupPolicy ,
2026-02-22 12:44:02 +01:00
resolveDefaultGroupPolicy ,
2026-01-18 08:32:19 +00:00
resolveTelegramAccount ,
resolveTelegramGroupRequireMention ,
2026-01-24 15:35:05 +13:00
resolveTelegramGroupToolPolicy ,
2026-01-18 08:32:19 +00:00
setAccountEnabledInConfigSection ,
telegramOnboardingAdapter ,
TelegramConfigSchema ,
2026-01-18 11:00:19 +00:00
type ChannelMessageActionAdapter ,
2026-01-18 08:32:19 +00:00
type ChannelPlugin ,
2026-01-30 03:15:10 +01:00
type OpenClawConfig ,
2026-01-18 08:32:19 +00:00
type ResolvedTelegramAccount ,
2026-02-04 10:09:28 +00:00
type TelegramProbe ,
2026-03-04 02:58:48 +02:00
} from "openclaw/plugin-sdk/telegram" ;
2026-01-18 11:00:19 +00:00
import { getTelegramRuntime } from "./runtime.js" ;
2026-01-18 08:32:19 +00:00
const meta = getChatChannelMeta ( "telegram" ) ;
2026-02-21 15:40:38 +01:00
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 ) ) {
2026-03-05 23:07:13 -06:00
const account = inspectTelegramAccount ( { cfg : params.cfg , accountId : id } ) ;
2026-03-03 01:31:45 +08:00
const token = ( account . token ? ? "" ) . trim ( ) ;
2026-02-21 15:40:38 +01:00
if ( ! token ) {
continue ;
}
const ownerAccountId = tokenOwners . get ( token ) ;
if ( ! ownerAccountId ) {
tokenOwners . set ( token , account . accountId ) ;
continue ;
}
if ( account . accountId === normalizedAccountId ) {
return ownerAccountId ;
}
}
return null ;
}
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. `
) ;
}
2026-01-18 11:00:19 +00:00
const telegramMessageActions : ChannelMessageActionAdapter = {
2026-02-09 10:05:38 -08:00
listActions : ( ctx ) = >
getTelegramRuntime ( ) . channel . telegram . messageActions ? . listActions ? . ( ctx ) ? ? [ ] ,
2026-01-18 11:00:19 +00:00
extractToolSend : ( ctx ) = >
2026-02-09 10:05:38 -08:00
getTelegramRuntime ( ) . channel . telegram . messageActions ? . extractToolSend ? . ( ctx ) ? ? null ,
handleAction : async ( ctx ) = > {
const ma = getTelegramRuntime ( ) . channel . telegram . messageActions ;
if ( ! ma ? . handleAction ) {
throw new Error ( "Telegram message actions not available" ) ;
}
return ma . handleAction ( ctx ) ;
} ,
2026-01-18 11:00:19 +00:00
} ;
2026-02-04 10:09:28 +00:00
export const telegramPlugin : ChannelPlugin < ResolvedTelegramAccount , TelegramProbe > = {
2026-01-18 08:32:19 +00:00
id : "telegram" ,
meta : {
. . . meta ,
quickstartAllowFrom : true ,
} ,
onboarding : telegramOnboardingAdapter ,
pairing : {
idLabel : "telegramUserId" ,
normalizeAllowEntry : ( entry ) = > entry . replace ( /^(telegram|tg):/i , "" ) ,
notifyApproval : async ( { cfg , id } ) = > {
2026-01-18 11:00:19 +00:00
const { token } = getTelegramRuntime ( ) . channel . telegram . resolveTelegramToken ( cfg ) ;
2026-01-31 22:13:48 +09:00
if ( ! token ) {
throw new Error ( "telegram token not configured" ) ;
}
2026-01-31 21:13:13 +09:00
await getTelegramRuntime ( ) . channel . telegram . sendMessageTelegram (
id ,
PAIRING_APPROVED_MESSAGE ,
{
token ,
} ,
) ;
2026-01-18 08:32:19 +00:00
} ,
} ,
capabilities : {
chatTypes : [ "direct" , "group" , "channel" , "thread" ] ,
reactions : true ,
threads : true ,
media : true ,
2026-02-14 18:34:30 +01:00
polls : true ,
2026-01-18 08:32:19 +00:00
nativeCommands : true ,
blockStreaming : true ,
} ,
reload : { configPrefixes : [ "channels.telegram" ] } ,
configSchema : buildChannelConfigSchema ( TelegramConfigSchema ) ,
config : {
listAccountIds : ( cfg ) = > listTelegramAccountIds ( cfg ) ,
resolveAccount : ( cfg , accountId ) = > resolveTelegramAccount ( { cfg , accountId } ) ,
2026-03-05 23:07:13 -06:00
inspectAccount : ( cfg , accountId ) = > inspectTelegramAccount ( { cfg , accountId } ) ,
2026-01-18 08:32:19 +00:00
defaultAccountId : ( cfg ) = > resolveDefaultTelegramAccountId ( cfg ) ,
setAccountEnabled : ( { cfg , accountId , enabled } ) = >
setAccountEnabledInConfigSection ( {
cfg ,
sectionKey : "telegram" ,
accountId ,
enabled ,
allowTopLevel : true ,
} ) ,
deleteAccount : ( { cfg , accountId } ) = >
deleteAccountFromConfigSection ( {
cfg ,
sectionKey : "telegram" ,
accountId ,
clearBaseFields : [ "botToken" , "tokenFile" , "name" ] ,
} ) ,
2026-02-21 15:40:38 +01:00
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 ) = > ( {
2026-01-18 08:32:19 +00:00
accountId : account.accountId ,
name : account.name ,
enabled : account.enabled ,
2026-02-21 15:40:38 +01:00
configured :
Boolean ( account . token ? . trim ( ) ) &&
! findTelegramTokenOwnerAccountId ( { cfg , accountId : account.accountId } ) ,
2026-01-18 08:32:19 +00:00
tokenSource : account.tokenSource ,
} ) ,
resolveAllowFrom : ( { cfg , accountId } ) = >
( resolveTelegramAccount ( { cfg , accountId } ) . config . allowFrom ? ? [ ] ) . map ( ( entry ) = >
String ( entry ) ,
) ,
formatAllowFrom : ( { allowFrom } ) = >
allowFrom
. map ( ( entry ) = > String ( entry ) . trim ( ) )
. filter ( Boolean )
. map ( ( entry ) = > entry . replace ( /^(telegram|tg):/i , "" ) )
. map ( ( entry ) = > entry . toLowerCase ( ) ) ,
2026-02-19 23:37:19 -05:00
resolveDefaultTo : ( { cfg , accountId } ) = > {
const val = resolveTelegramAccount ( { cfg , accountId } ) . config . defaultTo ;
return val != null ? String ( val ) : undefined ;
} ,
2026-01-18 08:32:19 +00:00
} ,
security : {
resolveDmPolicy : ( { cfg , accountId , account } ) = > {
const resolvedAccountId = accountId ? ? account . accountId ? ? DEFAULT_ACCOUNT_ID ;
const useAccountPath = Boolean ( cfg . channels ? . telegram ? . accounts ? . [ resolvedAccountId ] ) ;
const basePath = useAccountPath
? ` channels.telegram.accounts. ${ resolvedAccountId } . `
: "channels.telegram." ;
return {
policy : account.config.dmPolicy ? ? "pairing" ,
allowFrom : account.config.allowFrom ? ? [ ] ,
policyPath : ` ${ basePath } dmPolicy ` ,
allowFromPath : basePath ,
approveHint : formatPairingApproveHint ( "telegram" ) ,
normalizeEntry : ( raw ) = > raw . replace ( /^(telegram|tg):/i , "" ) ,
} ;
} ,
collectWarnings : ( { account , cfg } ) = > {
2026-02-22 12:44:02 +01:00
const defaultGroupPolicy = resolveDefaultGroupPolicy ( cfg ) ;
2026-02-22 12:35:02 +01:00
const { groupPolicy } = resolveAllowlistProviderRuntimeGroupPolicy ( {
2026-02-22 12:17:44 +01:00
providerConfigPresent : cfg.channels?.telegram !== undefined ,
groupPolicy : account.config.groupPolicy ,
defaultGroupPolicy ,
} ) ;
2026-01-31 22:13:48 +09:00
if ( groupPolicy !== "open" ) {
return [ ] ;
}
2026-01-18 08:32:19 +00:00
const groupAllowlistConfigured =
account . config . groups && Object . keys ( account . config . groups ) . length > 0 ;
if ( groupAllowlistConfigured ) {
return [
` - Telegram groups: groupPolicy="open" allows any member in allowed groups to trigger (mention-gated). Set channels.telegram.groupPolicy="allowlist" + channels.telegram.groupAllowFrom to restrict senders. ` ,
] ;
}
return [
` - Telegram groups: groupPolicy="open" with no channels.telegram.groups allowlist; any group can add + ping (mention-gated). Set channels.telegram.groupPolicy="allowlist" + channels.telegram.groupAllowFrom or configure channels.telegram.groups. ` ,
] ;
} ,
} ,
groups : {
resolveRequireMention : resolveTelegramGroupRequireMention ,
2026-01-24 15:35:05 +13:00
resolveToolPolicy : resolveTelegramGroupToolPolicy ,
2026-01-18 08:32:19 +00:00
} ,
threading : {
2026-02-13 22:11:55 -08:00
resolveReplyToMode : ( { cfg } ) = > cfg . channels ? . telegram ? . replyToMode ? ? "off" ,
2026-01-18 08:32:19 +00:00
} ,
messaging : {
normalizeTarget : normalizeTelegramMessagingTarget ,
targetResolver : {
looksLikeId : looksLikeTelegramTargetId ,
hint : "<chatId>" ,
} ,
} ,
directory : {
self : async ( ) = > null ,
listPeers : async ( params ) = > listTelegramDirectoryPeersFromConfig ( params ) ,
listGroups : async ( params ) = > listTelegramDirectoryGroupsFromConfig ( params ) ,
} ,
actions : telegramMessageActions ,
setup : {
resolveAccountId : ( { accountId } ) = > normalizeAccountId ( accountId ) ,
applyAccountName : ( { cfg , accountId , name } ) = >
applyAccountNameToChannelSection ( {
cfg ,
channelKey : "telegram" ,
accountId ,
name ,
} ) ,
validateInput : ( { accountId , input } ) = > {
if ( input . useEnv && accountId !== DEFAULT_ACCOUNT_ID ) {
return "TELEGRAM_BOT_TOKEN can only be used for the default account." ;
}
if ( ! input . useEnv && ! input . token && ! input . tokenFile ) {
return "Telegram requires token or --token-file (or --use-env)." ;
}
return null ;
} ,
applyAccountConfig : ( { cfg , accountId , input } ) = > {
const namedConfig = applyAccountNameToChannelSection ( {
cfg ,
channelKey : "telegram" ,
accountId ,
name : input.name ,
} ) ;
const next =
accountId !== DEFAULT_ACCOUNT_ID
? migrateBaseNameToDefaultAccount ( {
cfg : namedConfig ,
channelKey : "telegram" ,
} )
: namedConfig ;
if ( accountId === DEFAULT_ACCOUNT_ID ) {
return {
. . . next ,
channels : {
. . . next . channels ,
telegram : {
. . . next . channels ? . telegram ,
enabled : true ,
. . . ( input . useEnv
? { }
: input . tokenFile
? { tokenFile : input.tokenFile }
: input . token
? { botToken : input.token }
: { } ) ,
} ,
} ,
} ;
}
return {
. . . next ,
channels : {
. . . next . channels ,
telegram : {
. . . next . channels ? . telegram ,
enabled : true ,
accounts : {
. . . next . channels ? . telegram ? . accounts ,
[ accountId ] : {
. . . next . channels ? . telegram ? . accounts ? . [ accountId ] ,
enabled : true ,
. . . ( input . tokenFile
? { tokenFile : input.tokenFile }
: input . token
? { botToken : input.token }
: { } ) ,
} ,
} ,
} ,
} ,
} ;
} ,
} ,
outbound : {
deliveryMode : "direct" ,
2026-01-18 11:00:19 +00:00
chunker : ( text , limit ) = > getTelegramRuntime ( ) . channel . text . chunkMarkdownText ( text , limit ) ,
2026-01-25 04:05:14 +00:00
chunkerMode : "markdown" ,
2026-01-18 08:32:19 +00:00
textChunkLimit : 4000 ,
2026-02-14 18:34:30 +01:00
pollMaxOptions : 10 ,
2026-03-04 00:20:44 -06:00
sendText : async ( { cfg , to , text , accountId , deps , replyToId , threadId , silent } ) = > {
2026-01-31 21:13:13 +09:00
const send = deps ? . sendTelegram ? ? getTelegramRuntime ( ) . channel . telegram . sendMessageTelegram ;
2026-02-15 01:07:37 +00:00
const replyToMessageId = parseTelegramReplyToMessageId ( replyToId ) ;
const messageThreadId = parseTelegramThreadId ( threadId ) ;
2026-01-18 08:32:19 +00:00
const result = await send ( to , text , {
verbose : false ,
2026-03-04 00:20:44 -06:00
cfg ,
2026-01-18 08:32:19 +00:00
messageThreadId ,
replyToMessageId ,
accountId : accountId ? ? undefined ,
2026-02-14 18:34:30 +01:00
silent : silent ? ? undefined ,
2026-01-18 08:32:19 +00:00
} ) ;
return { channel : "telegram" , . . . result } ;
} ,
2026-02-22 22:52:49 +01:00
sendMedia : async ( {
2026-03-04 00:20:44 -06:00
cfg ,
2026-02-22 22:52:49 +01:00
to ,
text ,
mediaUrl ,
mediaLocalRoots ,
accountId ,
deps ,
replyToId ,
threadId ,
silent ,
} ) = > {
2026-01-31 21:13:13 +09:00
const send = deps ? . sendTelegram ? ? getTelegramRuntime ( ) . channel . telegram . sendMessageTelegram ;
2026-02-15 01:07:37 +00:00
const replyToMessageId = parseTelegramReplyToMessageId ( replyToId ) ;
const messageThreadId = parseTelegramThreadId ( threadId ) ;
2026-01-18 08:32:19 +00:00
const result = await send ( to , text , {
verbose : false ,
2026-03-04 00:20:44 -06:00
cfg ,
2026-01-18 08:32:19 +00:00
mediaUrl ,
2026-02-22 22:52:49 +01:00
mediaLocalRoots ,
2026-01-18 08:32:19 +00:00
messageThreadId ,
replyToMessageId ,
accountId : accountId ? ? undefined ,
2026-02-14 18:34:30 +01:00
silent : silent ? ? undefined ,
2026-01-18 08:32:19 +00:00
} ) ;
return { channel : "telegram" , . . . result } ;
} ,
2026-03-04 00:20:44 -06:00
sendPoll : async ( { cfg , to , poll , accountId , threadId , silent , isAnonymous } ) = >
2026-02-14 18:34:30 +01:00
await getTelegramRuntime ( ) . channel . telegram . sendPollTelegram ( to , poll , {
2026-03-04 00:20:44 -06:00
cfg ,
2026-02-14 18:34:30 +01:00
accountId : accountId ? ? undefined ,
2026-02-15 01:07:37 +00:00
messageThreadId : parseTelegramThreadId ( threadId ) ,
2026-02-14 18:34:30 +01:00
silent : silent ? ? undefined ,
isAnonymous : isAnonymous ? ? undefined ,
} ) ,
2026-01-18 08:32:19 +00:00
} ,
status : {
defaultRuntime : {
accountId : DEFAULT_ACCOUNT_ID ,
running : false ,
lastStartAt : null ,
lastStopAt : null ,
lastError : null ,
} ,
collectStatusIssues : collectTelegramStatusIssues ,
2026-02-23 21:25:20 +00:00
buildChannelSummary : ( { snapshot } ) = > buildTokenChannelStatusSummary ( snapshot ) ,
2026-01-18 08:32:19 +00:00
probeAccount : async ( { account , timeoutMs } ) = >
2026-01-18 11:00:19 +00:00
getTelegramRuntime ( ) . channel . telegram . probeTelegram (
account . token ,
timeoutMs ,
account . config . proxy ,
) ,
2026-01-18 08:32:19 +00:00
auditAccount : async ( { account , timeoutMs , probe , cfg } ) = > {
const groups =
cfg . channels ? . telegram ? . accounts ? . [ account . accountId ] ? . groups ? ?
cfg . channels ? . telegram ? . groups ;
const { groupIds , unresolvedGroups , hasWildcardUnmentionedGroups } =
2026-01-18 11:00:19 +00:00
getTelegramRuntime ( ) . channel . telegram . collectUnmentionedGroupIds ( groups ) ;
2026-01-18 08:32:19 +00:00
if ( ! groupIds . length && unresolvedGroups === 0 && ! hasWildcardUnmentionedGroups ) {
return undefined ;
}
2026-02-04 10:09:28 +00:00
const botId = probe ? . ok && probe . bot ? . id != null ? probe.bot.id : null ;
2026-01-18 08:32:19 +00:00
if ( ! botId ) {
return {
ok : unresolvedGroups === 0 && ! hasWildcardUnmentionedGroups ,
checkedGroups : 0 ,
unresolvedGroups ,
hasWildcardUnmentionedGroups ,
groups : [ ] ,
elapsedMs : 0 ,
} ;
}
2026-01-18 11:00:19 +00:00
const audit = await getTelegramRuntime ( ) . channel . telegram . auditGroupMembership ( {
2026-01-18 08:32:19 +00:00
token : account.token ,
botId ,
groupIds ,
proxyUrl : account.config.proxy ,
timeoutMs ,
} ) ;
return { . . . audit , unresolvedGroups , hasWildcardUnmentionedGroups } ;
} ,
buildAccountSnapshot : ( { account , cfg , runtime , probe , audit } ) = > {
2026-03-05 23:07:13 -06:00
const configuredFromStatus = resolveConfiguredFromCredentialStatuses ( account ) ;
2026-02-21 15:40:38 +01:00
const ownerAccountId = findTelegramTokenOwnerAccountId ( {
cfg ,
accountId : account.accountId ,
} ) ;
const duplicateTokenReason = ownerAccountId
? formatDuplicateTelegramTokenReason ( {
accountId : account.accountId ,
ownerAccountId ,
} )
: null ;
2026-03-05 23:07:13 -06:00
const configured =
( configuredFromStatus ? ? Boolean ( account . token ? . trim ( ) ) ) && ! ownerAccountId ;
2026-01-18 08:32:19 +00:00
const groups =
cfg . channels ? . telegram ? . accounts ? . [ account . accountId ] ? . groups ? ?
cfg . channels ? . telegram ? . groups ;
const allowUnmentionedGroups =
2026-02-04 10:09:28 +00:00
groups ? . [ "*" ] ? . requireMention === false ||
2026-01-18 08:32:19 +00:00
Object . entries ( groups ? ? { } ) . some (
2026-02-04 10:09:28 +00:00
( [ key , value ] ) = > key !== "*" && value ? . requireMention === false ,
2026-01-18 08:32:19 +00:00
) ;
return {
accountId : account.accountId ,
name : account.name ,
enabled : account.enabled ,
configured ,
2026-03-05 23:07:13 -06:00
. . . projectCredentialSnapshotFields ( account ) ,
2026-01-18 08:32:19 +00:00
running : runtime?.running ? ? false ,
lastStartAt : runtime?.lastStartAt ? ? null ,
lastStopAt : runtime?.lastStopAt ? ? null ,
2026-02-21 15:40:38 +01:00
lastError : runtime?.lastError ? ? duplicateTokenReason ,
2026-01-18 08:32:19 +00:00
mode : runtime?.mode ? ? ( account . config . webhookUrl ? "webhook" : "polling" ) ,
probe ,
audit ,
allowUnmentionedGroups ,
lastInboundAt : runtime?.lastInboundAt ? ? null ,
lastOutboundAt : runtime?.lastOutboundAt ? ? null ,
} ;
} ,
} ,
gateway : {
startAccount : async ( ctx ) = > {
const account = ctx . account ;
2026-02-21 15:40:38 +01:00
const ownerAccountId = findTelegramTokenOwnerAccountId ( {
cfg : ctx.cfg ,
accountId : account.accountId ,
} ) ;
if ( ownerAccountId ) {
const reason = formatDuplicateTelegramTokenReason ( {
accountId : account.accountId ,
ownerAccountId ,
} ) ;
ctx . log ? . error ? . ( ` [ ${ account . accountId } ] ${ reason } ` ) ;
throw new Error ( reason ) ;
}
2026-03-03 01:31:45 +08:00
const token = ( account . token ? ? "" ) . trim ( ) ;
2026-01-18 08:32:19 +00:00
let telegramBotLabel = "" ;
try {
2026-01-18 11:00:19 +00:00
const probe = await getTelegramRuntime ( ) . channel . telegram . probeTelegram (
token ,
2500 ,
account . config . proxy ,
) ;
2026-01-18 08:32:19 +00:00
const username = probe . ok ? probe . bot ? . username ? . trim ( ) : null ;
2026-01-31 22:13:48 +09:00
if ( username ) {
telegramBotLabel = ` (@ ${ username } ) ` ;
}
2026-01-18 08:32:19 +00:00
} catch ( err ) {
2026-01-18 11:00:19 +00:00
if ( getTelegramRuntime ( ) . logging . shouldLogVerbose ( ) ) {
2026-01-18 08:32:19 +00:00
ctx . log ? . debug ? . ( ` [ ${ account . accountId } ] bot probe failed: ${ String ( err ) } ` ) ;
}
}
ctx . log ? . info ( ` [ ${ account . accountId } ] starting provider ${ telegramBotLabel } ` ) ;
2026-01-18 11:00:19 +00:00
return getTelegramRuntime ( ) . channel . telegram . monitorTelegramProvider ( {
2026-01-18 08:32:19 +00:00
token ,
accountId : account.accountId ,
config : ctx.cfg ,
runtime : ctx.runtime ,
abortSignal : ctx.abortSignal ,
useWebhook : Boolean ( account . config . webhookUrl ) ,
webhookUrl : account.config.webhookUrl ,
webhookSecret : account.config.webhookSecret ,
webhookPath : account.config.webhookPath ,
2026-02-14 01:39:56 +10:00
webhookHost : account.config.webhookHost ,
2026-02-22 17:49:59 +01:00
webhookPort : account.config.webhookPort ,
2026-01-18 08:32:19 +00:00
} ) ;
} ,
logoutAccount : async ( { accountId , cfg } ) = > {
const envToken = process . env . TELEGRAM_BOT_TOKEN ? . trim ( ) ? ? "" ;
2026-01-30 03:15:10 +01:00
const nextCfg = { . . . cfg } as OpenClawConfig ;
2026-01-18 08:32:19 +00:00
const nextTelegram = cfg . channels ? . telegram ? { . . . cfg . channels . telegram } : undefined ;
let cleared = false ;
let changed = false ;
if ( nextTelegram ) {
if ( accountId === DEFAULT_ACCOUNT_ID && nextTelegram . botToken ) {
delete nextTelegram . botToken ;
cleared = true ;
changed = true ;
}
const accounts =
nextTelegram . accounts && typeof nextTelegram . accounts === "object"
? { . . . nextTelegram . accounts }
: undefined ;
if ( accounts && accountId in accounts ) {
const entry = accounts [ accountId ] ;
if ( entry && typeof entry === "object" ) {
const nextEntry = { . . . entry } as Record < string , unknown > ;
if ( "botToken" in nextEntry ) {
const token = nextEntry . botToken ;
if ( typeof token === "string" ? token . trim ( ) : token ) {
cleared = true ;
}
delete nextEntry . botToken ;
changed = true ;
}
if ( Object . keys ( nextEntry ) . length === 0 ) {
delete accounts [ accountId ] ;
changed = true ;
} else {
accounts [ accountId ] = nextEntry as typeof entry ;
}
}
}
if ( accounts ) {
if ( Object . keys ( accounts ) . length === 0 ) {
delete nextTelegram . accounts ;
changed = true ;
} else {
nextTelegram . accounts = accounts ;
}
}
}
if ( changed ) {
if ( nextTelegram && Object . keys ( nextTelegram ) . length > 0 ) {
nextCfg . channels = { . . . nextCfg . channels , telegram : nextTelegram } ;
} else {
const nextChannels = { . . . nextCfg . channels } ;
delete nextChannels . telegram ;
if ( Object . keys ( nextChannels ) . length > 0 ) {
nextCfg . channels = nextChannels ;
} else {
delete nextCfg . channels ;
}
}
}
const resolved = resolveTelegramAccount ( {
cfg : changed ? nextCfg : cfg ,
accountId ,
} ) ;
const loggedOut = resolved . tokenSource === "none" ;
if ( changed ) {
2026-01-18 11:00:19 +00:00
await getTelegramRuntime ( ) . config . writeConfigFile ( nextCfg ) ;
2026-01-18 08:32:19 +00:00
}
return { cleared , envToken : Boolean ( envToken ) , loggedOut } ;
} ,
} ,
} ;