hanki.dev

Telegraf tips

I recently finish my first freelance project (which I loved doing!), which was a Telegram bot. Since Javascript is my choice of poison, I decided to use telegraf.js library. There's also node-telegram-bot-api which I tried briefly but Telegraf just seemed way superior.

Telegraf.js has comprehensive documentation but to me it just feels like auto generated crap which doesn't even have any examples, so I kinda had hard time understanding it. So at the start of the project I started writing down useful stuff I found out. Obviously I forgot to write down anything after the first 5 hours, but hey at least I got something. Hopefully these are at least somewhat useful to someone :)

Inline buttons

I absolutely love inline buttons and imo they're easily one of the best features of Telegram bots. Here's how you can create an inline button:

Markup.inlineKeyboard([
  [Markup.button.callback("Awesome button 🔥", "callbackFunction")]
]);

Note there are 2 nested arrays, this is because you can have multiple rows of buttons, and every row can have multiple buttons next to each other. Btw the max buttons per row is 8, and max amount of rows is 100.

Here's how you react to the button press:

bot.action("callbackFunction", (ctx) => {
  ctx.reply("I am the one who KNOCKS!");
  ctx.answerCbQuery(); // Hide spinner
});

Unfortunately there is no way to pass any callback data :/ So I had to find a workaround. Fortunately this was easy with regex. First you add the callback data to your callback function string, seperated with your character of choice (I used underscore):

Markup.inlineKeyboard([
  [Markup.button.callback("Awesome button 🔥", "callbackFunction_myData")]
]);

And then you just use regex inside the bot.action. You can find the callback function string from the context object, and then scrape the callback data from there.

bot.action(/callbackFunction_(.+)/, (ctx) => {
  const callbackString = ctx.match[0];
  const callbackData = callbackData.split("_")[1];

  ctx.reply(`I disagree, ${callbackData} is a shitty choice.`);

  ctx.answerCbQuery(); // Hide spinner
});

What if you want to, for example hide the inline button after on usage? This is simple, bot.action context object has editMessageReplyMarkup which can be used for this.

bot.action("callbackFunction", async (ctx) => {
  await ctx.reply("I'm in the empire business 🛢");
  await ctx.editMessageText("Button already clicked :)", Markup.inlineKeyboard([]))
  ctx.answerCbQuery(); // Hide spinner
});

You could probably also just use null or something instead of passing empty Markup.inlineKeyboard. Was too lazy to test that while writing this post, lol

Get user input

If you want to react to regular text which user types to chat, you can use bot.on. "text" parameter catches everything the user types.

bot.on("text", (ctx) => {
  const userInput = ctx.update.message.text;
  ctx.reply(`Hello ${userInput}!`);
});

Text formatting

Telegram messages has 2 different parse modes, MarkdownV2 and HTML. I'd prefer to use MarkdownV2 but unfortunately, for some reason I never got it working. If I tried to, for example, do bold text with * it always threw an error about unescaped characters. And no, escaping the characters didn't help, so I ended up using HTML 🙃

ctx.reply("<b>Bold</b> and <i>italic</i> text 🥵", { parse_mode: "HTML" });

Util functions

It's pretty tiring to retrieve the same stuff from context over and over again. Here are some util functions I wrote which helps a ton. They're pretty self-explanatory, so no need to explain them.

// Use format "functionName_callbackData" with callback functions
const getCallbackData = (ctx) => ctx.match[0].split("_")[1];

// Regular message & inline button answer
const getUserId = (ctx) =>
  ctx?.update?.message?.from?.id || ctx?.update?.callback_query?.from?.id || 0;

const getUserName = (ctx) =>
  ctx?.update?.message?.from?.first_name ||
  ctx?.update?.message?.chat?.first_name ||
  ctx?.update?.callback_query?.from?.first_name ||
  ctx?.update?.callback_query?.chat?.first_name ||
  ctx?.update?.message?.from?.username ||
  ctx?.update?.message?.chat?.username ||
  ctx?.update?.callback_query?.from?.username ||
  ctx?.update?.callback_query?.chat?.username ||
  "???";

const getParseModeHTML = () => ({ parse_mode: "HTML" });
const bold = (text) => `<b>${text}</b>`;
const italic = (text) => `<i>${text}</i>`;
const underline = (text) => `<u>${text}</u>`;

const getUserInput = (ctx) => ctx?.update?.message?.text;

const getChatId = (ctx) => ctx?.update?.message?.chat?.id;

That's it! 🍻

#javascript