Handling Updates

MTKruto uses an update middleware inspired by grammY.

You can handle updates by assigning update handlers to the client. A handler is a function that takes two arguments: ctx and next.

ctx includes the received update at ctx.update and provides context-aware methods and shortcuts. See all types of updates here.

next is a function that passes the update to the next handlers.

Once an update reaches a handler, it won’t reach others unless it calls next. When next is called, the same thing goes on again: the update won’t reach the next handler until next is called, and so on.

The methods responsible for composing update handlers are:

use

use is the main method to assign a handler, and all others depend on it. It assigns an unconditional handler, meaning that it will always get called unless it is blocked by a handler preceding it.

|
use -------- handler
               |
              next
               |
               |
               |
               |

The following are some usage examples.

/** Logging */
client.use(async (ctx, next) => {
  console.log("I always get logged before an update is handled.");
  console.log("Update", ctx.toJSON(), "received at", Date.now());
  await next(); // move to the next handlers
});

/** Error handling */
client.use(async (_ctx, next) => {
  try {
    await next(); // call the next handlers
  } catch (err) {
    console.error("Failed to handle an update:");
    console.trace(err);
  }
});

/** Ignoring updates */
client.use(async (ctx, next) => {
  const message = ctx.msg;
  const date = message?.date;
  const isTooOld = date !== undefined && Date.now() - date.getTime() > 5 * 60 * 1_000;
  if (message !== undefined && isTooOld) {
    // message is older than 5 minutes, not interesting
    console.log(
      "Ignoring messsage",
      message.id,
      "in",
      message.chat.id,
      "because it is too old.",
    );
  } else {
    await next(); // handle it if newer than 5 minutes
  }
});

/** Performance monitoring */
client.use(async (_ctx, next) => {
  const then = performance.now();
  await next(); // call the next handlers
  const elapsed = performance.now() - then;
  console.log(`Update handled in ${Math.round(elapsed)}ms`);
});

branch

branch takes 3 functions:

  1. One that checks for a specific condition.
  2. A handler function that gets called when the specific condition is met, the true handler.
  3. Another one that gets called when it is not met, the false handler.
|
branch -------- condition
                   |
              ------------
             |            |
           true        false
             |            |
          handler      handler
             |            |
           next        next
              \        /
               \      /
                \    /
                 \  /
                  \/
                  |

Here’s an example.

client.branch(
  /** condition: update is from a special user */
  (ctx) => ctx.from?.username?.toLowerCase() == "onetimeusername",
  /** true handler */
  () => {
    console.log("Using more compute power: Update from someone special.");
  },
  /** false handler */
  () => {
    console.log("Handling postponed: Update from regular user.");
  },
);

filter

filter is almost the same as branch except that it does not have a false handler. It automatically moves to the next handlers if the condition is not met.

|
filter -------- condition
                   |
              ------------
             |            |
           true        false
             |            |
          handler         |
             |            |
            next <--------/
             |

Here’s an example.

client.filter(
  /* condition: update is from @crow */
  (ctx) => {
    return ctx.from?.username === "crow";
  },
  (ctx) => {
    console.log("Received an update from @crow.");
  },
);

on

on works like filter but instead of providing a function that checks for a condition, you provide it a filter query. If the update matched the provided filter, the handler gets called.

|
on -------------------- check
                         |
                    -------------
                   |             |
                passes      does not
                   |            |
                handler -------/
                   |
                 next
                   |
                   |

Here are some examples.

/** When the client's connection state changes */
client.on("connectionState", (ctx)) => {
  console.log("New connection state:", ctx.update.connectionState);
});

/** Handles text messages */
client.on("message:text", (message) => {
  // do something with ctx.message.text
});

/** Handles callback queries */
client.on("callbackQuery", (callbackQuery) => {
  // do something with ctx.callbackQuery
});

command

command works like on("message:text") except it filters commands.

client.command("start", async (ctx) => {
  await ctx.reply("Hey, you just started me!");
});

You can specify multiple commands.

client.command(["start", "ping"], async (ctx) => {
  await ctx.reply("Command received!");
});

You can also specify custom prefixes.

client.command({
  names: ["start", "ping"],
  prefixes: ["/", "!"],
}, async (ctx) => {
  await ctx.reply("Command received!");
});

callbackQuery

callbackQuery filters incoming updates by a specific callback query data.

client.callbackQuery("btn_1", async (ctx) => {
  // ctx.callbackQuery.data === "btn_1"
});

inlineQuery

inlineQuery filters incoming updates by a specific inline query.

client.inlineQuery("query", async (ctx) => {
  // ctx.inlineQuery.query === "query"
});

chosenInlineResult

chosenInlineResult filters incoming updates by a chosen inline result’s query.

client.chosenInlineResult("query", async (ctx) => {
  // ctx.chosenInlineResult.query === "query"
});

chatType

chatType filters incoming updates by their chat type.

client.chatType("private", async (ctx) => {
  await ctx.reply("Received a message from a private chat!");
});

You can specify multiple chat types.

client.chatType(["supergroup", "channel"], async (ctx) => {
  await ctx.reply("Message received!");
});