From b1d8768866d553a2c89bf9c6cd9b8140cd2a2b20 Mon Sep 17 00:00:00 2001 From: araharah-executor Date: Fri, 23 Oct 2026 01:00:27 +0800 Subject: [PATCH] fix(03-04): web_app menu button on deployed origin + wire CTA actions on both denied-shaped cards --- bot/scripts/set-menu-button.mjs | 61 +++++++++++++++++++++++++++++++++ web/src/main.ts | 23 ++++++++----- 2 files changed, 76 insertions(+), 8 deletions(-) create mode 100644 bot/scripts/set-menu-button.mjs diff --git a/bot/scripts/set-menu-button.mjs b/bot/scripts/set-menu-button.mjs new file mode 100644 index 0000000..aa2cd88 --- /dev/null +++ b/bot/scripts/set-menu-button.mjs @@ -0,0 +1,61 @@ +#!/usr/bin/env node +/* + * set-menu-button.mjs — point the bot's menu button at the Mini App (phase 3). + * --------------------------------------------------------------------------- + * Phase 2 deliberately armed the menu button as type `commands` so the command + * keyboard became visible. The Mini App replaces that as the menu button's + * DEFAULT action for private chats: type `web_app` opens the deployed origin + * directly from the bot's attach/menu area. + * + * Behavior: reads WEBHOOK_URL from bot/.dev.vars (gitignored) and derives the + * app origin by stripping the /telegram/webhook path — the same origin every + * other phase-3 surface deploys to. Default scope (all private chats). + * Exit 0 only when Telegram answers ok:true; the token is never echoed. + */ +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; + +if (!token || !webhookUrl) { + console.error("Missing BOT_TOKEN or WEBHOOK_URL (bot/.dev.vars or env)"); + process.exit(1); +} + +const origin = new URL(webhookUrl).origin; +const appUrl = `${origin}/`; +const res = await fetch(`https://api.telegram.org/bot${token}/setChatMenuButton`, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + menu_button: { type: "web_app", text: "araharah", web_app: { url: appUrl } }, + }), +}); +const body = await res.json(); +if (!body.ok) { + console.error(`setChatMenuButton failed: ${body.error_code} ${body.description}`); + process.exit(1); +} +console.log("menu button -> web_app", appUrl); diff --git a/web/src/main.ts b/web/src/main.ts index 52e9fea..f5175cd 100644 --- a/web/src/main.ts +++ b/web/src/main.ts @@ -113,14 +113,21 @@ async function boot(): Promise { let tickStarted = false; let chainInFlight = false; - const retryButton = deniedCard.querySelector(".btn-secondary"); - if (retryButton instanceof HTMLButtonElement) { - retryButton.addEventListener("click", () => void runFromSession()); // D-06: re-runs from link 1 - } - const askButton = deniedCard.querySelector(".btn-primary"); - if (askButton instanceof HTMLButtonElement) { - askButton.addEventListener("click", () => void askViaChat()); // D-06: the Desktop Telegram path - } + // D-06: BOTH denied-shaped instances get live CTAs. 'denied-then-waiting' + // is a second buildDeniedCard instance (03-03 stub note) — wiring only the + // first left TRY AGAIN / ASK VIA CHAT dead on the awaiting-pin card. + const wireDeniedActions = (card: HTMLElement): void => { + const retryButton = card.querySelector(".btn-secondary"); + if (retryButton instanceof HTMLButtonElement) { + retryButton.addEventListener("click", () => void runFromSession()); // re-runs from link 1 + } + const askButton = card.querySelector(".btn-primary"); + if (askButton instanceof HTMLButtonElement) { + askButton.addEventListener("click", () => void askViaChat()); // the Desktop Telegram path + } + }; + wireDeniedActions(deniedCard); + wireDeniedActions(awaitingCard); function showCard(card: HTMLElement | null, register: "center" | "bottom" = "center"): void { if (card !== null) {