/** * 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__`', '`')) }