#!/usr/bin/env node /* * set-webhook.mjs — one-time setWebhook registration (D-16, D-17). * ----------------------------------------------------------------- * Points Telegram at the Worker's /telegram/webhook route and arms the * secret-token channel: the `secret_token` value sent here is the one * Telegram echoes back in X-Telegram-Bot-Api-Secret-Token on every update, * which the Worker's webhook adapter constant-time-compares against * env.WEBHOOK_SECRET (T-02-03). * * Inputs come from bot/.dev.vars (gitignored, D-15): BOT_TOKEN, * WEBHOOK_URL, WEBHOOK_SECRET. The bot token is never echoed — on failure * only the API's error_code/description are printed (T-02-04). * WEBHOOK_URL is normalized: if the configured origin points at the * workers.dev root (no path), /telegram/webhook is appended automatically — * a bare origin passed the 2026-10-22 deploy walkthrough caused Telegram to * answer ok but deliver nothing (updates routed to "/" → 404). * Exit 0 only when Telegram answers ok:true. */ import { readFileSync } from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; function readDevVars() { const devVarsPath = path.join(fileURLToPath(new URL(import.meta.url)), "..", "..", ".dev.vars"); let raw = ""; try { raw = readFileSync(devVarsPath, "utf8"); } catch { return {}; } const vars = {}; for (const line of raw.split("\n")) { const trimmed = line.trim(); if (trimmed.length === 0 || trimmed.startsWith("#")) continue; const eq = trimmed.indexOf("="); if (eq === -1) continue; vars[trimmed.slice(0, eq).trim()] = trimmed.slice(eq + 1).trim(); } return vars; } const devVars = readDevVars(); const token = process.env.BOT_TOKEN ?? devVars.BOT_TOKEN; const webhookUrl = process.env.WEBHOOK_URL ?? devVars.WEBHOOK_URL; const secret = process.env.WEBHOOK_SECRET ?? devVars.WEBHOOK_SECRET; const missing = ["BOT_TOKEN", "WEBHOOK_URL", "WEBHOOK_SECRET"].filter( (name) => !{ BOT_TOKEN: token, WEBHOOK_URL: webhookUrl, WEBHOOK_SECRET: secret }[name], ); if (missing.length > 0) { console.error(`Missing ${missing.join(", ")} — set them in bot/.dev.vars (see .dev.vars.example; never commit).`); process.exit(1); } // Auto-append the route path when the URL has none — prevents the recurring // misconfiguration where a bare workers.dev origin is registered and the // Worker answers ok:true but Telegram updates land on "/" (404, silent drop). const WEBHOOK_ROUTE = "/telegram/webhook"; let normalizedUrl; try { const parsed = new URL(webhookUrl); if (parsed.pathname === "/" || parsed.pathname === "") { parsed.pathname = WEBHOOK_ROUTE; normalizedUrl = parsed.toString(); console.log(`WEBHOOK_URL had no path — normalized to ...${WEBHOOK_ROUTE}`); } else { normalizedUrl = webhookUrl; } } catch { console.error(`WEBHOOK_URL is not a valid URL — check bot/.dev.vars (value never echoed).`); process.exit(1); } const payload = { url: normalizedUrl, secret_token: secret, allowed_updates: ["message"], }; const res = await fetch(`https://api.telegram.org/bot${token}/setWebhook`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(payload), }); const body = await res.json(); if (!body.ok) { console.error(`setWebhook failed: ${body.error_code ?? "?"} ${body.description ?? "unknown error"}`); process.exit(1); } console.log(`setWebhook ok:true — updates will be delivered to the configured URL with the secret-token header.`); process.exit(0);