import { Bot } from "gramio"; import { stream, Back, Button, Cancel, Column, Const, ContactRequest, Countdown, Dialog, LiveProgress, LocationButton, Multi, Poll, Reactions, RequestChat, RequestUser, Row, Spinner, StarsButton, SwitchInlineQuery, SwitchTo, Url, WebApp, createDialogs, getReactions, getSharedChat, getSharedUsers, typing, } from "../src/index.ts"; /** * Real-time & Telegram-native surfaces in one bot: * - live widgets (Spinner / Countdown / LiveProgress) animated from OUTSIDE the * handler via the background manager โ€” the right way to "tick"; * - native Poll / quiz, Reactions, and reply-keyboard pickers (Request*); * - Telegram Stars payments + AI token streaming (`sendMessageDraft`). * Also shows WebApp / SwitchInlineQuery / Cancel buttons and a custom callback * prefix via the `callback` plugin option (internally `makeCodec({ name })`). */ // Swap for your LLM's streaming response โ€” any `AsyncIterable`. async function* fakeTokens(): AsyncGenerator { const text = "GramIO dialogs make rich, stateful Telegram UIs effortless."; for (const word of text.split(" ")) { await new Promise((r) => setTimeout(r, 150)); yield `${word} `; } } const rt = new Dialog("rt", { onStart: (m) => { m.data.deadline = Date.now() + 30_000; m.data.progress = 0; }, }) .window("menu", { text: "โšก Real-time & native demos:", keyboard: Column([ SwitchTo("๐ŸŽฌ Live widgets", "live"), SwitchTo("๐Ÿ“Š Poll & quiz", "poll"), SwitchTo("โค๏ธ Reactions", "react"), SwitchTo("๐Ÿ‘ค Native pickers", "pickers"), SwitchTo("โญ Buy with Stars", "buy"), SwitchTo("๐Ÿค– Stream AI", "ai"), Row([ Url("๐Ÿ“ˆ Docs", "https://gramio.dev"), WebApp("๐Ÿ–ฅ Dashboard", "https://gramio.dev"), SwitchInlineQuery({ text: "โ†—๏ธ Share", query: "gramio dialogs" }), ]), Cancel("โœ– Close"), ]), }) // LIVE โ€” re-rendered on a timer by the background manager (see command below) .window("live", { text: Multi([ Const("โšก Updating every 2s from the background manager:"), Countdown({ until: (d) => (d.dialogData as { deadline?: number }).deadline ?? 0, doneText: "โฐ Time's up!", }), LiveProgress({ value: (d) => (d.dialogData as { progress?: number }).progress ?? 0, label: "Build", }), Spinner({ id: "spin", label: "workingโ€ฆ" }), ]), keyboard: Column([Back("โ—€ Back")]), }) // NATIVE โ€” Poll / quiz sent to the chat .window("poll", { text: "Send a native poll or quiz to the chat:", keyboard: Column([ Poll({ id: "poll", text: "๐Ÿ“Š Send a poll", question: "Favorite language?", options: ["TypeScript", "Rust", "Go", "Python"], }), Poll({ id: "quiz", text: "๐Ÿง  Send a quiz", question: "What is 2 + 2?", options: ["3", "4", "5"], quiz: true, correctOptionId: 1, }), Back("โ—€ Back"), ]), }) .window("react", { getter: (ctx) => ({ picked: getReactions(ctx.dialog, "rx") }), text: (d) => d.picked.length ? `You reacted ${d.picked.join(" ")}` : "How do you feel?", keyboard: Column([ Reactions({ id: "rx", items: ["๐Ÿ‘", "โค๏ธ", "๐Ÿ”ฅ", "๐Ÿ˜ฎ", "๐Ÿ˜ข"] }), Back("โ—€ Back"), ]), }) // Request buttons must live in a `reply: true` window; results arrive as // service messages, read in `onMessage`. .window("pickers", { reply: true, text: "Use the buttons below to share something ๐Ÿ‘‡", keyboard: Column([ RequestUser({ text: "๐Ÿ‘ค Pick a user" }), RequestChat({ text: "๐Ÿ’ฌ Pick a group" }), ContactRequest({ text: "๐Ÿ“ž Share my contact" }), LocationButton({ text: "๐Ÿ“ Share my location" }), Back("โ—€ Back"), ]), onMessage: (ctx) => { const users = getSharedUsers(ctx); const chat = getSharedChat(ctx); if (users.length) return ctx.send(`โœ… Got user id(s): ${users.join(", ")}`); if (chat !== undefined) return ctx.send(`โœ… Got chat id: ${chat}`); }, }) // PAYMENTS โ€” Telegram Stars .window("buy", { text: "Unlock Pro for a month โ€” 1 โญ", keyboard: Column([ StarsButton({ id: "pro", text: "โญ Buy Pro ยท 1", title: "Pro plan", description: "Unlimited everything for 30 days.", stars: 1, onInvoice: (ctx) => ctx.answer("Invoice sent โ€” complete the payment โญ"), }), Back("โ—€ Back"), ]), }) // AI โ€” token streaming via sendMessageDraft (private chats) .window("ai", { text: "๐Ÿค– Streams a reply via sendMessageDraft:", keyboard: Column([ Button("โœจ Generate", { id: "gen", onClick: (ctx) => stream(ctx, fakeTokens(), { throttleMs: 600 }), }), Back("โ—€ Back"), ]), }); // `callback: { name }` swaps the callback_data prefix tag (internally makeCodec). const { plugin, background } = createDialogs([rt], { callback: { name: "rt" }, }); const bot = new Bot(process.env.BOT_TOKEN as string).extend(plugin); // โš ๏ธ Stars payments need TWO bot-level handlers, or the purchase never completes: bot.on("pre_checkout_query", (ctx) => ctx.answerPreCheckoutQuery({ ok: true })); bot.on("successful_payment", (ctx) => ctx.send("โœ… Payment received โ€” Pro unlocked! ๐ŸŽ‰"), ); bot.command("start", async (ctx) => { await typing(ctx); // show "typingโ€ฆ" while we boot the demo await ctx.dialog.start("rt"); // Re-render the live window on a timer to animate its widgets. The key must // match the plugin's default getStackKey: `grd:`. const key = `grd:${ctx.senderId}`; let progress = 0; const timer = setInterval(async () => { progress = Math.min(100, progress + 10); try { const manager = await background(bot, key); manager.data.progress = progress; await manager.show(); // edits the message in place } catch { clearInterval(timer); return; } if (progress >= 100) clearInterval(timer); }, 2000); }); bot.start();