fix(03-04): web_app menu button on deployed origin + wire CTA actions on both denied-shaped cards

This commit is contained in:
araharah-executor
2026-10-23 01:00:27 +08:00
parent fc183216c3
commit b1d8768866
2 changed files with 76 additions and 8 deletions
+61
View File
@@ -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);
+15 -8
View File
@@ -113,14 +113,21 @@ async function boot(): Promise<void> {
let tickStarted = false; let tickStarted = false;
let chainInFlight = false; let chainInFlight = false;
const retryButton = deniedCard.querySelector(".btn-secondary"); // D-06: BOTH denied-shaped instances get live CTAs. 'denied-then-waiting'
if (retryButton instanceof HTMLButtonElement) { // is a second buildDeniedCard instance (03-03 stub note) — wiring only the
retryButton.addEventListener("click", () => void runFromSession()); // D-06: re-runs from link 1 // first left TRY AGAIN / ASK VIA CHAT dead on the awaiting-pin card.
} const wireDeniedActions = (card: HTMLElement): void => {
const askButton = deniedCard.querySelector(".btn-primary"); const retryButton = card.querySelector(".btn-secondary");
if (askButton instanceof HTMLButtonElement) { if (retryButton instanceof HTMLButtonElement) {
askButton.addEventListener("click", () => void askViaChat()); // D-06: the Desktop Telegram path 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 { function showCard(card: HTMLElement | null, register: "center" | "bottom" = "center"): void {
if (card !== null) { if (card !== null) {