diff --git a/handlers/commands.js b/handlers/commands.js index 921768c..6983063 100644 --- a/handlers/commands.js +++ b/handlers/commands.js @@ -1,14 +1,20 @@ -import { Markup } from "telegraf" -import { startMsg, sponsorsWebsiteUrl, homepage } from "../lib/strings.js" +import { startMsg, mainMenu } from "../lib/constants.js" export default { async start(ctx) { - return await ctx.replyWithMarkdownV2( - startMsg, - Markup.inlineKeyboard([ - Markup.button.url("Learn more on website", sponsorsWebsiteUrl), - Markup.button.url("Run your own bot", homepage) - ]) - ) + if (ctx.updateType == "callback_query") { + return await ctx.editMessageText( + startMsg, + { + parse_mode: "MarkdownV2", + ...mainMenu + } + ) + } else { + return await ctx.replyWithMarkdownV2( + startMsg, + mainMenu + ) + } } } \ No newline at end of file diff --git a/handlers/donationFlow.js b/handlers/donationFlow.js new file mode 100644 index 0000000..dd2c423 --- /dev/null +++ b/handlers/donationFlow.js @@ -0,0 +1,47 @@ +import { donationOptions, donationStep1 } from "../lib/constants.js" + +/* @typedef import("telegraf").Context Context */ + +export default { + async startHere(ctx) { + if (ctx.updateType == "callback_query") { + await ctx.answerCbQuery() + return await ctx.editMessageText( + donationStep1, { + parse_mode: "MarkdownV2", + ...donationOptions + } + ) + } else { + return await ctx.replyWithMarkdownV2( + donationStep1, + donationOptions + ) + } + }, + + async generateInvoice(ctx, amount) { + const invoiceId = crypto.randomUUID() + + if (ctx.updateType == "callback_query") { + await ctx.answerCbQuery() + } + + return await ctx.sendInvoice({ + title: "Tip for Andrei Jiroh", + description: "Support Andrei Jiroh's work by using your Telegram Stars. Non-refundable once paid.", + payload: invoiceId, + provider_token: "", + currency: "XTR", + prices: [{ + label: "To tip jar", + amount: amount + }], + start_parameter: `donate_${amount}xtr` + }) + }, + + async thankYou(ctx) { + return ctx.sendMessage("Thank you!") + } +} \ No newline at end of file diff --git a/index.js b/index.js index 2981133..afd8ad2 100644 --- a/index.js +++ b/index.js @@ -1,18 +1,79 @@ import {Telegraf} from "telegraf"; -import express from "express"; import botCommands from "./handlers/commands.js" +import donationFlow from "./handlers/donationFlow.js"; import { configDotenv } from "dotenv"; +import { commands } from "./lib/constants.js"; configDotenv({ debug: process.env.DEBUG ? true : false }) -const app = express() +if (!process.env.TELEGRAM_BOT_TOKEN) throw new Error('Missing Telegram bot token'); +const bot = new Telegraf(process.env.TELEGRAM_BOT_TOKEN || "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11") -const bot = new Telegraf(process.env.TELEGRAM_BOT_TOKEN) -app.use(await bot.createWebhook({ - domain: process.env.TELEGRAM_BOT_WEBHOOK_DOMAIN, - path: "/api/tgbot" -})); +bot.use((ctx, next) => { + const start = Date.now(); + return next().then(() => { + const ms = Date.now() - start; + console.log(`response time ${ms} for update ${ctx.updateType}`); + }); +}); -bot.start(async (ctx) =>{ return botCommands.start(ctx)}) +bot.telegram.setMyCommands(commands) + +bot.start(async (ctx) => { + switch (ctx.payload) { + case "donate": + return donationFlow.startHere(ctx) + case "donate_50xtr": + return donationFlow.generateInvoice(ctx, 50) + default: + return botCommands.start(ctx) + } +}) + +bot.command("donate", async (ctx) => { return donationFlow.startHere(ctx)}) + +bot.action("home", async (ctx) => { return botCommands.start(ctx)}) +bot.action("donate", async (ctx) => { return donationFlow.startHere(ctx)}) + +// TG Stars donation spaghetti code +bot.action("50_XTR", async (ctx) => { return donationFlow.generateInvoice(ctx, 50)}) +bot.action("75_XTR", async (ctx) => { return donationFlow.generateInvoice(ctx, 70)}) +bot.action("100_XTR", async (ctx) => { return donationFlow.generateInvoice(ctx, 100)}) +bot.action("150_XTR", async (ctx) => { return donationFlow.generateInvoice(ctx, 150)}) +bot.action("250_XTR", async (ctx) => { return donationFlow.generateInvoice(ctx, 250)}) +bot.action("350_XTR", async (ctx) => { return donationFlow.generateInvoice(ctx, 350)}) +bot.action("500_XTR", async (ctx) => { return donationFlow.generateInvoice(ctx, 500)}) +bot.action("750_XTR", async (ctx) => { return donationFlow.generateInvoice(ctx, 750)}) +bot.action("1000_XTR", async (ctx) => { return donationFlow.generateInvoice(ctx, 1000)}) + + +bot.on('pre_checkout_query', (ctx) => { + const preCheckoutQuery = ctx.update.pre_checkout_query; + console.log(preCheckoutQuery); + + ctx + .answerPreCheckoutQuery(true) + .then(() => { + console.log('Payment approved'); + }) + .catch((error) => { + console.log('Failed to approve payment:', error); + }); + }); + +bot.launch(); + +process.once("SIGINT", () => { + if (process.env.NODE_ENV == "development") { + bot.telegram.deleteMyCommands() + } + bot.stop("SIGINT") +}); +process.once("SIGTERM", () => { + if (process.env.NODE_ENV == "development") { + bot.telegram.deleteMyCommands() + } + bot.stop("SIGTERM") +}); diff --git a/lib/constants.js b/lib/constants.js index 9abe7fe..bde9681 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -1,5 +1,30 @@ import { Markup } from "telegraf" + +// LINKS +export const sponsorsWebsiteUrl = "https://sponsors.andreijiroh.xyz" +export const homepage = "https://mau.dev/andreijiroh-dev/tgdonatebot" + +export const commands = [ + { + command: "start", + description: "main menu" + }, + { + command: "donate", + description: "donate some money" + }, + { + command: "help", + description: "show bot help" + }, + { + command: "terms", + description: "legalese" + } + ] + +// start command export const startMsg = `\ *Hello there\\!* @@ -14,19 +39,50 @@ terms of AGPL license\\. To get started, tap the *Donate to the tipjar* button \ below or use /donate command\\. ` +export const mainMenu = Markup.inlineKeyboard([ + [ + Markup.button.url("Learn more", sponsorsWebsiteUrl), + ], + [ + Markup.button.callback("Donate to the tipjar", "donate"), + ], + [ + Markup.button.callback("Manage your sponsorship", "manage_sponsorship") + ], + [ + Markup.button.url("Run your own bot", homepage), + ] +]) -const donationStep1 = `\ - +// donate button +export const donationStep1 = `\ +*Choose how you donate within the Telegram app* +Select the amount of Telegram Stars listed below to chip in \ +to @ajhalili2006's tip jar via this bot\\. Alternatively, \ +select *Show alternative ways* \\(or via /more\\_donate\\_options\\) \ +to get even more ways to donate\\. ` - export const donationOptions = Markup.inlineKeyboard([ [ Markup.button.callback("50", "50_XTR"), - Markup.button.callback("75", "75_XTR") + Markup.button.callback("75", "75_XTR"), + Markup.button.callback("100", "100_XTR") + ], + [ + Markup.button.callback("150", "150_XTR"), + Markup.button.callback("250", "250_XTR"), + Markup.button.callback("350", "350_XTR") + ], + [ + Markup.button.callback("500", "500_XTR"), + Markup.button.callback("750", "750_XTR"), + Markup.button.callback("1000", "1000_XTR") + ], + [ + Markup.button.callback("Show alternative options", "showMoreOptions_donations") + ], + [ + Markup.button.callback("Return to homepage", "home") ] ]) - -// LINKS -export const sponsorsWebsiteUrl = "https://sponsors.andreijiroh.xyz" -export const homepage = "https://mau.dev/andreijiroh-dev/tgdonatebot" \ No newline at end of file diff --git a/lib/payments.js b/lib/payments.js new file mode 100644 index 0000000..7ab174c --- /dev/null +++ b/lib/payments.js @@ -0,0 +1 @@ +// noop for now \ No newline at end of file diff --git a/lib/types/dataPresistence.ts b/lib/types/dataPresistence.ts new file mode 100644 index 0000000..01acf75 --- /dev/null +++ b/lib/types/dataPresistence.ts @@ -0,0 +1,9 @@ +export interface InvoicePayload { + invoice_id: string, // TODO: Validate if its UUID v4 here + user: BigInt, // Telegram user ID here + currency: "XTR", // only use Telegram Stars here + amount: number, + status: "pending" | "paid" | "refunded", + created_at: string, + updated_at?: string +} \ No newline at end of file