All checks were successful
deploy / deploy (push) Successful in 7s
Live HL data → in-browser TA → 4-agent committee → LONG/SHORT/SKIP verdict. Terminal grid command bar + interactive agent-workflow graph. Paper-trading worker reuses the browser brain (dry-run + Telegram). Forgejo static deploy. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
66 lines
2.3 KiB
TypeScript
66 lines
2.3 KiB
TypeScript
/**
|
|
* Telegram — the real L6 "Comms" layer, in ~60 lines via the raw Bot API (no
|
|
* dependency). Outbound: tgSend() broadcasts signals/closes. Inbound: tgPoll()
|
|
* long-polls getUpdates for slash commands (/status, /pnl, /positions, /help).
|
|
*
|
|
* If no token/chat is configured the worker still runs — messages print to stdout
|
|
* so you can demo the whole loop with zero Telegram setup.
|
|
*/
|
|
import { CONFIG } from './config.ts'
|
|
|
|
const api = (method: string) => `https://api.telegram.org/bot${CONFIG.tgToken}/${method}`
|
|
|
|
export const TG_ENABLED = Boolean(CONFIG.tgToken && CONFIG.tgChat)
|
|
|
|
const stripHtml = (s: string) => s.replace(/<[^>]+>/g, '')
|
|
|
|
/** Send a message (HTML). Defaults to the configured chat; pass chatId to reply. */
|
|
export async function tgSend(text: string, chatId: string | number = CONFIG.tgChat): Promise<void> {
|
|
if (!CONFIG.tgToken || !chatId) {
|
|
console.log('\n' + stripHtml(text) + '\n')
|
|
return
|
|
}
|
|
try {
|
|
const res = await fetch(api('sendMessage'), {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
chat_id: chatId,
|
|
text,
|
|
parse_mode: 'HTML',
|
|
disable_web_page_preview: true,
|
|
}),
|
|
})
|
|
if (!res.ok) console.error('[tg] send failed', res.status, await res.text())
|
|
} catch (e) {
|
|
console.error('[tg] send error', (e as Error).message)
|
|
}
|
|
}
|
|
|
|
let offset = 0
|
|
|
|
/** Poll once for new commands and dispatch them. Cheap to call on an interval. */
|
|
export async function tgPoll(
|
|
handler: (cmd: string, chatId: number) => Promise<void>,
|
|
): Promise<void> {
|
|
if (!CONFIG.tgToken) return
|
|
try {
|
|
const res = await fetch(api('getUpdates') + `?timeout=0&offset=${offset}`)
|
|
if (!res.ok) return
|
|
const json = (await res.json()) as {
|
|
result?: { update_id: number; message?: { text?: string; chat?: { id: number } } }[]
|
|
}
|
|
for (const u of json.result ?? []) {
|
|
offset = u.update_id + 1
|
|
const text = u.message?.text
|
|
const chatId = u.message?.chat?.id
|
|
if (text && chatId != null && text.startsWith('/')) {
|
|
// strip @BotName suffix and arguments → bare command
|
|
const cmd = text.trim().split(/\s+/)[0].split('@')[0].toLowerCase()
|
|
await handler(cmd, chatId)
|
|
}
|
|
}
|
|
} catch {
|
|
/* transient — ignore, next poll retries */
|
|
}
|
|
}
|