diff --git a/src/homepage.html b/src/homepage.html new file mode 100644 index 0000000..e6b49e5 --- /dev/null +++ b/src/homepage.html @@ -0,0 +1,10 @@ + + + + This is the title of the webpage! + + + +

welcome page

+ + diff --git a/src/telegram-boy.js b/src/telegram-boy.js new file mode 100644 index 0000000..63813eb --- /dev/null +++ b/src/telegram-boy.js @@ -0,0 +1,243 @@ +/** + * https://github.com/cvzi/telegram-bot-cloudflare + */ + +const TOKEN = ENV_BOT_TOKEN // Get it from @BotFather https://core.telegram.org/bots#6-botfather +const WEBHOOK = '/endpoint' +const SECRET = ENV_BOT_SECRET // A-Z, a-z, 0-9, _ and - + +/** + * Wait for requests to the worker + */ +addEventListener('fetch', event => { + const url = new URL(event.request.url) + if (url.pathname === WEBHOOK) { + event.respondWith(handleWebhook(event)) + } else if (url.pathname === '/registerWebhook') { + event.respondWith(registerWebhook(event, url, WEBHOOK, SECRET)) + } else if (url.pathname === '/unRegisterWebhook') { + event.respondWith(unRegisterWebhook(event)) + } else { + event.respondWith(new Response('No handler for this request')) + } +}) + +/** + * Handle requests to WEBHOOK + * https://core.telegram.org/bots/api#update + */ +async function handleWebhook (event) { + // Check secret + if (event.request.headers.get('X-Telegram-Bot-Api-Secret-Token') !== SECRET) { + return new Response('Unauthorized', { status: 403 }) + } + + // Read request body synchronously + const update = await event.request.json() + // Deal with response asynchronously + event.waitUntil(onUpdate(update)) + + return new Response('Ok') +} + +/** + * Handle incoming Update + * supports messages and callback queries (inline button presses) + * https://core.telegram.org/bots/api#update + */ +async function onUpdate (update) { + if ('message' in update) { + await onMessage(update.message) + } + if ('callback_query' in update) { + await onCallbackQuery(update.callback_query) + } +} + +/** + * Set webhook to this worker's url + * https://core.telegram.org/bots/api#setwebhook + */ +async function registerWebhook (event, requestUrl, suffix, secret) { + // https://core.telegram.org/bots/api#setwebhook + const webhookUrl = `${requestUrl.protocol}//${requestUrl.hostname}${suffix}` + const r = await (await fetch(apiUrl('setWebhook', { url: webhookUrl, secret_token: secret }))).json() + return new Response('ok' in r && r.ok ? 'Ok' : JSON.stringify(r, null, 2)) +} + +/** + * Remove webhook + * https://core.telegram.org/bots/api#setwebhook + */ +async function unRegisterWebhook (event) { + const r = await (await fetch(apiUrl('setWebhook', { url: '' }))).json() + return new Response('ok' in r && r.ok ? 'Ok' : JSON.stringify(r, null, 2)) +} + +/** + * Return url to telegram api, optionally with parameters added + */ +function apiUrl (methodName, params = null) { + let query = '' + if (params) { + query = '?' + new URLSearchParams(params).toString() + } + return `https://api.telegram.org/bot${TOKEN}/${methodName}${query}` +} + +/** + * Send plain text message + * https://core.telegram.org/bots/api#sendmessage + */ +async function sendPlainText (chatId, text) { + return (await fetch(apiUrl('sendMessage', { + chat_id: chatId, + text + }))).json() +} + +/** + * Send text message formatted with MarkdownV2-style + * Keep in mind that any markdown characters _*[]()~`>#+-=|{}.! that + * are not part of your formatting must be escaped. Incorrectly escaped + * messages will not be sent. See escapeMarkdown() + * https://core.telegram.org/bots/api#sendmessage + */ +async function sendMarkdownV2Text (chatId, text) { + return (await fetch(apiUrl('sendMessage', { + chat_id: chatId, + text, + parse_mode: 'MarkdownV2' + }))).json() +} + +/** + * Escape string for use in MarkdownV2-style text + * if `except` is provided, it should be a string of characters to not escape + * https://core.telegram.org/bots/api#markdownv2-style + */ +function escapeMarkdown (str, except = '') { + const all = '_*[]()~`>#+-=|{}.!\\'.split('').filter(c => !except.includes(c)) + const regExSpecial = '^$*+?.()|{}[]\\' + const regEx = new RegExp('[' + all.map(c => (regExSpecial.includes(c) ? '\\' + c : c)).join('') + ']', 'gim') + return str.replace(regEx, '\\$&') +} + +/** + * Send a message with a single button + * `button` must be an button-object like `{ text: 'Button', callback_data: 'data'}` + * https://core.telegram.org/bots/api#sendmessage + */ +async function sendInlineButton (chatId, text, button) { + return sendInlineButtonRow(chatId, text, [button]) +} + +/** + * Send a message with buttons, `buttonRow` must be an array of button objects + * https://core.telegram.org/bots/api#sendmessage + */ +async function sendInlineButtonRow (chatId, text, buttonRow) { + return sendInlineButtons(chatId, text, [buttonRow]) +} + +/** + * Send a message with buttons, `buttons` must be an array of arrays of button objects + * https://core.telegram.org/bots/api#sendmessage + */ +async function sendInlineButtons (chatId, text, buttons) { + return (await fetch(apiUrl('sendMessage', { + chat_id: chatId, + reply_markup: JSON.stringify({ + inline_keyboard: buttons + }), + text + }))).json() +} + +/** + * Answer callback query (inline button press) + * This stops the loading indicator on the button and optionally shows a message + * https://core.telegram.org/bots/api#answercallbackquery + */ +async function answerCallbackQuery (callbackQueryId, text = null) { + const data = { + callback_query_id: callbackQueryId + } + if (text) { + data.text = text + } + return (await fetch(apiUrl('answerCallbackQuery', data))).json() +} + +/** + * Handle incoming callback_query (inline button press) + * https://core.telegram.org/bots/api#message + */ +async function onCallbackQuery (callbackQuery) { + await sendMarkdownV2Text(callbackQuery.message.chat.id, escapeMarkdown(`You pressed the button with data=\`${callbackQuery.data}\``, '`')) + return answerCallbackQuery(callbackQuery.id, 'Button press acknowledged!') +} + +/** + * Handle incoming Message + * https://core.telegram.org/bots/api#message + */ +function onMessage (message) { + if (message.text.startsWith('/start') || message.text.startsWith('/help')) { + return sendMarkdownV2Text(message.chat.id, '*Functions:*\n' + + escapeMarkdown( + '`/help` - This message\n' + + '/button2 - Sends a message with two button\n' + + '/button4 - Sends a message with four buttons\n' + + '/markdown - Sends some MarkdownV2 examples\n', + '`')) + } else if (message.text.startsWith('/button2')) { + return sendTwoButtons(message.chat.id) + } else if (message.text.startsWith('/button4')) { + return sendFourButtons(message.chat.id) + } else if (message.text.startsWith('/markdown')) { + return sendMarkdownExample(message.chat.id) + } else { + return sendMarkdownV2Text(message.chat.id, escapeMarkdown('*Unknown command:* `' + message.text + '`\n' + + 'Use /help to see available commands.', '*`')) + } +} + +function sendTwoButtons (chatId) { + return sendInlineButtonRow(chatId, 'Press one of the two button', [{ + text: 'Button One', + callback_data: 'data_1' + }, { + text: 'Button Two', + callback_data: 'data_2' + }]) +} + +function sendFourButtons (chatId) { + return sendInlineButtons(chatId, 'Press a button', [ + [ + { + text: 'Button top left', + callback_data: 'Utah' + }, { + text: 'Button top right', + callback_data: 'Colorado' + } + ], + [ + { + text: 'Button bottom left', + callback_data: 'Arizona' + }, { + text: 'Button bottom right', + callback_data: 'New Mexico' + } + ] + ]) +} + +async function sendMarkdownExample (chatId) { + await sendMarkdownV2Text(chatId, 'This is *bold* and this is _italic_') + await sendMarkdownV2Text(chatId, escapeMarkdown('You can write it like this: *bold* and _italic_')) + return sendMarkdownV2Text(chatId, escapeMarkdown('...but users may write ** and __ e.g. `**bold**` and `__italic__`', '`')) +} diff --git a/wrangler.jsonc b/wrangler.jsonc index 14f7a05..e804ab6 100644 --- a/wrangler.jsonc +++ b/wrangler.jsonc @@ -1,6 +1,6 @@ { "$schema": "node_modules/wrangler/config-schema.json", - "name": "cloud-code", + "name": "io", "main": "./src/index.ts", "compatibility_date": "2026-01-01", "compatibility_flags": ["nodejs_compat"],