diff --git a/telegram/README.md b/telegram/README.md index 70d2d30..497d2a7 100644 --- a/telegram/README.md +++ b/telegram/README.md @@ -1,57 +1,38 @@ -# Telegram Bot Command +# Telegram Bot -A simple Telegram Bot Command. +A simple Telegram bot built with [grammY](https://grammy.dev). ## Tutorial -1. Follow the - [official Telegram guide](https://core.telegram.org/bots#3-how-do-i-create-a-bot) - for creating a Bot. -2. Deploy the Bot by clicking on this button: - [![Deploy this example](https://deno.com/deno-deploy-button.svg)](https://dash.deno.com/new?url=https://raw.githubusercontent.com/denoland/deploy_examples/main/telegram/mod.ts&env=TOKEN,BOT_NAME) -3. Input `TOKEN` and `BOT_NAME` env variable fields. The token value should be - available from the BotFather and the value `BOT_NAME` is the bot username - that ends with either `_bot` or `Bot`. -4. Click on **Create** to create the project, then on **Deploy** to deploy the +1. Deploy the Bot. + + [![Deploy this example](https://deno.com/deno-deploy-button.svg)](https://dash.deno.com/new?url=https://raw.githubusercontent.com/denoland/deploy_examples/main/telegram/mod.ts&env=BOT_TOKEN) +2. Open Telegram, talk to [BotFather](https://telegram.me/BotFather) and grab a + bot token. Set it as the `BOT_TOKEN` environment variable value. +3. Click on **Create** to create the project, then on **Deploy** to deploy the script. -5. Grab the URL that's displayed under Domains in the Production Deployment - card. -6. Visit the following URL (make sure to replace the template fields): +4. Visit the following URL (make sure to replace the template fields): ``` - https://api.telegram.org/bot/setWebhook?url=/ + https://api.telegram.org/bot/setWebhook?url=/ ``` - > Replace with the token from the BotFather and `` - > with the URL from the previous step. + - Replace `` with the token you got earlier. + - Replace `` with the the URL that is displayed under the + **Production Deployment** card at your project dashboard page. +5. Now send the bot a `/start` or `/ping` command. -7. Add a command to the bot by visiting the following URL: +
- ``` - https://api.telegram.org/bot/setMyCommands?commands=[{"command":"ping","description":"Should return a 'pong' from the Bot."}] - ``` -8. Now you can invite the bot to a Group Chat or just PM the bot with the - following command "/ping". +![preview](https://raw.githubusercontent.com/dcdunkan/deploy_examples/main/telegram/preview.png) -demo of Telegram Bot Command +
-## Run Offline +## Run Locally -You can run the example program on your machine using -[`deno`](https://github.com/denoland/deno): +You can run the example on your machine using [Deno CLI](https://deno.land). ```sh -TOKEN= BOT_NAME= deno run --allow-env --allow-net https://raw.githubusercontent.com/denoland/deploy_examples/main/telegram/mod.ts +BOT_TOKEN="" deno run --allow-env --allow-net https://raw.githubusercontent.com/denoland/deploy_examples/main/telegram/local.ts ``` -You need to use a tool like [ngrok](https://ngrok.com) to tunnel Telegram -requests to the app running on your machine. - -1. Run `ngrok http 8080` (assuming that the application is running on port - `8080`) -2. While registering the bot, use the https URL output by ngrok for **url** - query. - -> Example: -> `https://api.telegram.org/bot/setWebhook?url=/` - -That's it. +Remember to replace the `` with your own bot token. diff --git a/telegram/bot.ts b/telegram/bot.ts new file mode 100644 index 0000000..cf5ff23 --- /dev/null +++ b/telegram/bot.ts @@ -0,0 +1,11 @@ +import { Bot } from "https://deno.land/x/grammy@v1.9.2/mod.ts"; + +export const bot = new Bot(Deno.env.get("BOT_TOKEN") ?? ""); + +bot.command("start", async (ctx) => { + await ctx.reply("Hello!"); +}); + +bot.command("ping", async (ctx) => { + await ctx.reply("Pong!"); +}); diff --git a/telegram/demo.png b/telegram/demo.png deleted file mode 100644 index 2b9b293..0000000 Binary files a/telegram/demo.png and /dev/null differ diff --git a/telegram/local.ts b/telegram/local.ts new file mode 100644 index 0000000..cd74338 --- /dev/null +++ b/telegram/local.ts @@ -0,0 +1,3 @@ +import { bot } from "./bot.ts"; + +bot.start(); diff --git a/telegram/mod.ts b/telegram/mod.ts index d60bd7a..66803b4 100644 --- a/telegram/mod.ts +++ b/telegram/mod.ts @@ -1,130 +1,25 @@ -import { - json, - PathParams, - serve, - validateRequest, -} from "https://deno.land/x/sift@0.4.2/mod.ts"; - -// For all requests to "/" endpoint, we want to invoke handleTelegram() handler. -// Recommend using a secret path in the URL, e.g. https://www.example.com/. -serve({ - "/": () => new Response("Welcome to the Telegram Bot site."), - "/:slug": handleTelegram, -}); - -// The main logic of the Telegram bot is defined in this function. -async function handleTelegram(request: Request, params?: PathParams) { - // Gets the environment variable TOKEN - const TOKEN = Deno.env.get("TOKEN")!; - // Gets the environment variable BOT_NAME - const BOT_NAME = Deno.env.get("BOT_NAME")!; - - // If the environment variable TOKEN is not found, throw an error. - if (!TOKEN) { - throw new Error("environment variable TOKEN is not set"); - } - - // For using a secret path in the URL, e.g. https://www.example.com/. If wrong url return "invalid request". - if (params?.slug != TOKEN) { - return json( - { error: "invalid request" }, - { - status: 401, - }, - ); - } - - // Make sure the request is a POST request. - const { error } = await validateRequest(request, { - POST: {}, - }); - - // validateRequest populates the error if the request doesn't meet - // the schema we defined. - if (error) { - return json({ error: error.message }, { status: error.status }); - } - - // Get the body of the request - const body = await request.text(); - // Parse the raw JSON body from Telegrams webhook. - const data = await JSON.parse(body); - - // Check if the method is a POST request and that there was somthing in the body. - if (request.method === "POST") { - // Cheack if the command was "/ping". - if ( - data && data["message"] && data["message"]["text"] && - (data["message"]["text"].toLowerCase() == "/ping" || - data["message"]["text"].toLowerCase() == - "/ping@" + BOT_NAME.toLowerCase()) - ) { - // Store the chat id of the Group Chat, Channel or PM. - const chatId: number = data["message"]["chat"]["id"]; - - // Calls the API service to Telegram for sending a message. - const { dataTelegram, errors } = await sendMessage( - chatId, - "Pong", - TOKEN, - ); - - if (errors) { - console.error(errors.map((error) => error.message).join("\n")); - return json({ error: "couldn't create the message" }, { - status: 500, - }); +import { serve } from "https://deno.land/std@0.149.0/http/mod.ts"; +import { webhookCallback } from "https://deno.land/x/grammy@v1.9.2/mod.ts"; +import { bot } from "./bot.ts"; + +await bot.init(); + +const handleUpdate = webhookCallback(bot, "std/http"); +serve(async (req) => { + const pathname = new URL(req.url).pathname; + switch (pathname) { + case `/${bot.token}`: + if (req.method === "POST") { + try { + return await handleUpdate(req); + } catch (err) { + console.error(err); + return new Response(); + } } + break; - // Returns the answer and set status code 201. - return json({ dataTelegram }, { status: 201 }); - } - // Returns empty object and set status code 200. - return json({}, { status: 200 }); - } - - // We will return a bad request error as a valid Telegram request - // shouldn't reach here. - return json({ error: "bad request" }, { status: 400 }); -} - -/** What to store for an error message. */ -type TelegramError = { - message?: string; -}; - -/** Sending a POST request to Telegram's API to send a message. */ -async function sendMessage( - chatId: number, - text: string, - token: string, -): Promise<{ - dataTelegram?: unknown; - errors?: TelegramError[]; -}> { - try { - const res = await fetch( - `https://api.telegram.org/bot${token}/sendMessage`, - { - method: "POST", - headers: { - "content-type": "application/json", - }, - body: JSON.stringify({ - chat_id: chatId, - text: text, - }), - }, - ); - const { dataTelegram, errors } = await res.json(); - - if (errors) { - return { dataTelegram, errors }; - } - - return { dataTelegram }; - } catch (error) { - console.error(error); - return { errors: [{ message: "failed to fetch data from Telegram" }] }; + default: + return Response.redirect(`https://telegram.me/${bot.botInfo.username}`); } -} +}); diff --git a/telegram/preview.png b/telegram/preview.png new file mode 100644 index 0000000..3965ec4 Binary files /dev/null and b/telegram/preview.png differ