Nanfeng

Notes on software development, code, and curious ideas

Setting Up a Bot for Telegram Mini App and Game Development

I recently discovered that Telegram can also host Mini Apps and games. It felt like discovering a whole new world, so I decided to experiment with it.

Reference guide

If you want to build a Mini App or game on Telegram, you first need to create a bot of your own.

Create a bot

Search for @BotFather on Telegram, or open BotFather.

BotFather chat

In the chat, select Menu/newbot, or enter /newbot directly. Then follow BotFather’s instructions step by step to create your bot.

Create a Mini App or game

After creating the bot, create a Mini App or game according to your needs. The process is similar: send the relevant command to @BotFather. You can also find the steps in the reference guide above.

Set up the development environment

This environment is used to develop the bot, rather than the Mini App or game itself. You can use either Node.js or Deno; this article uses Deno.

See the Deno documentation for installation and setup. The rest of this article focuses on the bot implementation.

Reply with a game

  1. Create a TypeScript file and paste in the following code. Replace TOKEN and GAME_SHORT_NAME with your own values.
1
2
3
4
5
6
7
8
9
10
import {Bot} from "https://deno.land/x/grammy@v1.25.0/mod.ts";
const TOKEN = ''; // Your bot token
const GAME_SHORT_NAME = ''; // The short name assigned to your game
const bot = new Bot(TOKEN);

bot.command("start", async (ctx) => {
await ctx.replyWithGame(GAME_SHORT_NAME)
});

bot.start();
  1. Open a terminal and run the script:
1
deno run --allow-net your-script.ts
  1. Return to Telegram and send /start to your own bot—not to BotFather. The bot will reply with a game card:

Game dialog

At this point, clicking the Play button does nothing because the callback has not been handled yet.

  1. Add the following callback handler to the script:
1
2
3
4
5
6
7
const GAME_URL = "https://lengmo714.top";
bot.on("callback_query:game_short_name", async (ctx) => {
console.log("ctx.callbackQuery.game_short_name = ",ctx.callbackQuery.game_short_name)
if (ctx.callbackQuery.game_short_name === GAME_SHORT_NAME) {
await ctx.answerCallbackQuery({ url: GAME_URL });
}
});

Save the file, restart the script, and send /start again. The Play button will now open the game URL you configured.

Reply with a Mini App

  1. Create a TypeScript file with the following code. Replace TOKEN and the Mini App URL with your own values.
1
2
3
4
5
6
7
8
9
10
11
12
13
import {Bot} from "https://deno.land/x/grammy@v1.25.0/mod.ts";

const TOKEN = '';
const bot = new Bot(TOKEN);

bot.command("start", async (ctx) => {
await ctx.reply(
'<b>Hi!</b> <i>Welcome</i> to <a href="https://lengmo714.top">grammY</a>.',
{ parse_mode: "HTML" },
)
});

bot.start();
  1. Run the script:
1
deno run --allow-net your-script.ts
  1. Send /start to your bot. It will reply with the Mini App dialog shown below:

Mini App dialog

  1. Select the option to open the app. Telegram will navigate to the Mini App URL you configured earlier.

Add inline buttons

Sometimes you want the bot to offer several actions after a user sends /start. You can do this with an inline keyboard.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import {Bot,InlineKeyboard} from "https://deno.land/x/grammy@v1.25.0/mod.ts";

const TOKEN = '';
const bot = new Bot(TOKEN);

const keyboard = new InlineKeyboard().text("Start game 1", "one").row()
.text("Start game 2", "two").row()
.text("Start game 3", "three");

bot.command("start", async (ctx) => {
await ctx.reply("Choose an option", {
reply_markup: keyboard,
})
});

bot.start();

After the script starts, the result looks like this:

Inline keyboard

The three buttons are arranged vertically. To place them on one row, remove the row() calls:

1
2
3
const keyboard = new InlineKeyboard().text("Start game 1", "one")
.text("Start game 2", "two")
.text("Start game 3", "three");

There are several button types. The four I have used are .text, .game, .url, and .webApp; grammY may support additional types.

Reply with an image, text, and buttons

I once saw a Telegram bot response that combined a GIF, text, and three buttons. It could also access the sender’s user ID.

Combined bot response

I liked the result, so I built something similar:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { Bot,InlineKeyboard } from "https://deno.land/x/grammy@v1.25.0/mod.ts";
const TOKEN = '';
const TELEGRAM_GAME_URL = 'https://lengmo714.top';
const bot = new Bot(TOKEN);

const keyboard = new InlineKeyboard()
.url("Start game 1", TELEGRAM_GAME_URL).row()
.url("Start game 2", TELEGRAM_GAME_URL).row()
.url("Start game 3", TELEGRAM_GAME_URL);

bot.command("start", async (ctx) => {
console.log("ctx.update.message = ", ctx.update.message.from.first_name);
const gifUrl = "https://media.giphy.com/media/JIX9t2j0ZTN9S/giphy.gif";
const caption = `Hi ${ctx.update.message.from.first_name}!\n\nHere is a great game for you.\n\n🎰 Enjoy the game\n\n💸 Have fun playing\n\n🎁 Good luck!`;

await ctx.replyWithAnimation(gifUrl, {
caption: caption,
reply_markup: keyboard,
});
});

bot.start();

The final result looks like this:

Final bot response

Refinement

The example above creates three URL buttons, each with an arrow icon in its upper-right corner. This works, but it differs from the reference design, which uses an icon resembling two overlapping windows. To achieve that appearance, change the relevant buttons from .url to .webApp:

1
2
3
4
const keyboard = new InlineKeyboard()
.webApp("Start game", TELEGRAM_GAME_URL).row()
.webApp("Start game", TELEGRAM_GAME_URL).row()
.url("Start game", TELEGRAM_GAME_URL);

The updated result is shown below:

Web App buttons

This is the final button style we wanted.

+