Files
goodbuddy/docs/features/telegram-channel/telegram-probe.mjs
T
mesalogo cf598121b1 feat: add Telegram private messaging channel
Connect a user-created Telegram bot to channel projects with encrypted credentials, numeric sender allowlists, unified execution and text replies. Use Electron networking for system proxy support, with polling recovery, cancellable delivery retries and live status updates.

Add schema 61 channel support, lifecycle regression tests, Electron integration probes and feature documentation. Require the performance principles in AGENTS.md and record three alternating baseline/current Main benchmark rounds.

Validation: typecheck, lint and diff checks passed. Full suite: 6087 passed, 87 skipped, one intermittent inline-help Electron failure that passed unchanged in isolation. Real Bot plus model acceptance remains pending; the local TLS proxy probe is opt-in.

Release note: 新增 Telegram 私聊通道,可配置 Bot Token 和用户白名单,通过通道项目处理文字请求并回复结果。支持系统代理、断线重连和停用取消;真实 Bot 与模型的完整联调仍待完成。
2026-10-05 02:42:28 +08:00

87 lines
3.5 KiB
JavaScript

/* global process, fetch, AbortSignal, console, setTimeout */
// Credentials stay in the process environment; never print raw API errors.
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '1'
const token = process.env.TELEGRAM_BOT_TOKEN?.trim()
const listen = process.argv.includes('--listen')
const pending = process.argv.includes('--pending')
const startedAt = Math.floor(Date.now() / 1000)
const marker = '/goodbuddy_test'
async function call(method, body = {}, timeout = 15000) {
let response
try {
response = await fetch(`https://api.telegram.org/bot${token}/${method}`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(body),
signal: AbortSignal.timeout(timeout)
})
} catch (error) {
const code = error?.cause?.code ?? error?.name
const safeCode = /^[A-Z_a-z0-9]+$/.test(code ?? '') ? code : 'NETWORK_ERROR'
// Raw network errors can embed the credential-bearing request URL.
// eslint-disable-next-line preserve-caught-error
throw new Error(`${method}: ${safeCode}`)
}
let data
try {
data = await response.json()
} catch {
throw new Error(`${method}: invalid JSON (HTTP ${response.status})`)
}
if (!response.ok || !data.ok) {
const code = Number.isInteger(data.error_code) ? data.error_code : response.status
const wait = Number.isInteger(data.parameters?.retry_after)
? `; retry_after=${data.parameters.retry_after}` : ''
throw new Error(`${method}: API error ${code}${wait}`)
}
return data.result
}
async function main() {
if (!token || !/^\d+:[A-Za-z0-9_-]+$/.test(token)) {
throw new Error('Set TELEGRAM_BOT_TOKEN to a valid BotFather token.')
}
const bot = await call('getMe')
console.log(JSON.stringify({ check: 'getMe', ok: true, username: bot.username }))
const webhook = await call('getWebhookInfo')
console.log(JSON.stringify({
check: 'getWebhookInfo', ok: true,
webhookConfigured: Boolean(webhook.url), pendingUpdates: webhook.pending_update_count
}))
if (!listen && !pending) return
if (webhook.url) throw new Error('Webhook is configured; polling skipped. No configuration changed.')
console.log(pending
? `Checking queued private ${marker} messages from the last hour.`
: `Send ${marker} in a private chat with @${bot.username} within 45 seconds.`)
const deadline = Date.now() + 45000
while (Date.now() < deadline) {
// Do not advance offset or change allowed_updates on a diagnostic run.
const updates = await call('getUpdates', { timeout: pending ? 0 : 10, limit: 100 }, 15000)
const message = updates.map((update) => update.message).reverse().find((item) =>
item?.chat?.type === 'private' && !item.from?.is_bot &&
item.date >= startedAt - (pending ? 3600 : 0) && item.text === marker
)
if (message) {
await call('sendMessage', {
chat_id: message.chat.id,
text: 'GoodBuddy Telegram API test passed. This is a connectivity test, not an AI task.'
})
console.log(JSON.stringify({ check: 'privateMessageRoundTrip', ok: true }))
return
}
if (updates.length >= 100) {
throw new Error('Pending update batch is full; stopped without confirming existing updates.')
}
if (pending) break
if (updates.length) await new Promise((resolve) => setTimeout(resolve, 2000))
}
console.log(JSON.stringify({ check: 'privateMessageRoundTrip', ok: false, reason: 'no_matching_message' }))
process.exitCode = 2
}
main().catch((error) => {
console.error(error.message)
process.exitCode = 1
})