Parsing Command Line Arguments
Hello again. In our last session, we explored the grammY Context object and how to use it to create personalized responses and handle basic commands like /start and /help. We saw that ctx is the key to understanding who sent a message and where it came from.
This lesson takes the next logical step. We'll move beyond simple, parameter-less commands and learn how to create interactive, CLI-style bots that can accept arguments. You'll learn how to process user input like /search an article or /add an item, which is fundamental to building useful and dynamic bots. We will focus on grammY's built-in mechanisms for capturing and parsing these arguments.
Capturing Arguments with ctx.match
When a user sends a message like /echo Hello world, the command is /echo and the argument is the string "Hello world". grammY makes it incredibly simple to access this argument string.
Whenever you register a command handler with bot.command(), any text that follows the command is automatically captured and made available in the Context object via the ctx.match property.
This is best explained with the official grammY documentation.
This short section from the grammY guide gets straight to the point, introducing the ctx.match property and how it works.
Please read the section titled Arguments. The code example with /add apple pie is a perfect illustration of the concept.
As you've just read, ctx.match contains the remainder of the message text after the command itself. If the user sends just the command with no arguments, ctx.match will be an empty string.
Let's implement a simple /echo command to see this in action.
// in index.ts
bot.command("echo", (ctx) => {
const textToEcho = ctx.match;
if (!textToEcho) {
// Handle the case where no argument is provided.
ctx.reply("Usage: /echo <text>");
} else {
// The user provided some text, so echo it back.
ctx.reply(textToEcho);
}
});
Don't forget to add the new command to your list for autocompletion:
// in index.ts, before bot.start()
await bot.api.setMyCommands([
{ command: "start", description: "Start the bot" },
{ command: "help", description: "Show help text" },
{ command: "id", description: "Show user and chat IDs" },
{ command: "echo", description: "Echo back your message" }, // Add the new command
]);
After restarting your bot, try sending /echo Hello, bot!. The bot should reply with "Hello, bot!". Now try sending just /echo. The bot should correctly respond with the usage instructions. This pattern of checking for an empty ctx.match is crucial for creating robust commands.
Parsing Multiple Arguments
The ctx.match property is always a single string. If your command expects multiple parameters, it is your responsibility to parse that string. Given your extensive front-end development experience, you are already very familiar with string manipulation techniques in JavaScript/TypeScript.
Let's create a /sum command that takes two numbers and replies with their sum. The user would invoke it like /sum 5 12.
// in index.ts
bot.command("sum", (ctx) => {
// Split the argument string by spaces
const args = ctx.match.split(" ");
// We expect exactly two arguments
if (args.length !== 2) {
return ctx.reply("Usage: /sum <number1> <number2>");
}
// Convert string arguments to numbers
const num1 = parseFloat(args[0]);
const num2 = parseFloat(args[1]);
// Check if the conversion was successful
if (isNaN(num1) || isNaN(num2)) {
return ctx.reply("Please provide two valid numbers. Usage: /sum <number1> <number2>");
}
const result = num1 + num2;
ctx.reply(`The sum is: ${result}`);
});
// Remember to add "sum" to setMyCommands
This example demonstrates a complete flow for a multi-argument command:
- Splitting: We use
ctx.match.split(" ")to break the input string into an array of potential arguments. - Validation: We check the number of arguments and whether they are valid numbers using
isNaN. - Execution: If validation passes, we perform the calculation and reply.
- User Feedback: At each failure point, we return a reply that tells the user how to use the command correctly.
A More Advanced Parsing Example: Dice Roller
Let's build a more complex parser that leverages your interest in RPGs. We'll create a /roll command that understands standard dice notation, like 2d6 (roll two 6-sided dice) or 1d20+5 (roll one 20-sided die and add 5).
Parsing 2d6+5 with split() would be cumbersome. This is a perfect use case for a regular expression.
// in index.ts
bot.command("roll", (ctx) => {
const diceNotation = ctx.match;
if (!diceNotation) {
return ctx.reply("Usage: /roll <dice_notation> (e.g., 2d6, 1d20+5)");
}
// Regex to parse dice notation: e.g., "2d6+3"
// Group 1: Number of dice (\d+)
// Group 2: Sides per die (\d+)
// Group 3 (optional): Modifier ([\+\-]\d+)
const regex = /(\d+)d(\d+)([\+\-]\d+)?/;
const match = diceNotation.match(regex);
if (!match) {
return ctx.reply("Invalid dice notation. Use format like 2d6 or 1d20+5.");
}
const numDice = parseInt(match[1]);
const numSides = parseInt(match[2]);
const modifier = match[3] ? parseInt(match[3]) : 0;
if (numDice > 100) { // Let's add a reasonable limit
return ctx.reply("I can't roll more than 100 dice at once!");
}
let total = 0;
const rolls = [];
for (let i = 0; i < numDice; i++) {
const roll = Math.floor(Math.random() * numSides) + 1;
rolls.push(roll);
total += roll;
}
total += modifier;
// Construct a nice reply
const rollsStr = rolls.join(", ");
let reply = `You rolled: [${rollsStr}]`;
if (modifier !== 0) {
reply += ` ${modifier > 0 ? '+' : '-'} ${Math.abs(modifier)}`;
}
reply += `\nTotal: ${total}`;
ctx.reply(reply);
});
// And add "roll" to setMyCommands
This example shows how ctx.match can be the input for more sophisticated logic. The principles remain the same: get the string, parse it, validate it, and then act on it.
A Note on Deep Linking
There's another interesting way to populate ctx.match, which may resonate with your web development background. Telegram supports "deep linking," where a special URL can open a chat with your bot and pre-fill a command.
For example, the URL https://t.me/your_bot_name?start=payload123 will open a chat with your bot and a "START" button. When the user clicks it, your bot receives the message /start payload123. grammY is smart enough to see this and will put "payload123" into ctx.match for your /start handler. This is extremely useful for tracking where users are coming from (e.g., different links on your website) or for referral systems.
You can read more about this in the grammY guide if you're curious.
This section explains how URL parameters can be used to pass arguments to the /start command.
Read the section on Deep Linking Support to understand how ctx.match is used in this context.
Conclusion
You now have the tools to build bots that can take specific instructions from users. By combining bot.command() with ctx.match, you can create powerful, CLI-like interfaces directly within Telegram.
Key Takeaways:
- The
ctx.matchproperty contains the string of text that follows a command. - If no argument is given,
ctx.matchis an empty string, which you should always check for. - For multiple arguments, use string manipulation methods like
split()to parsectx.match. - For complex argument structures, regular expressions are a powerful tool for parsing
ctx.match. - Always validate user input and provide helpful feedback on incorrect usage.
So far, we've focused entirely on commands—messages that start with a /. However, users can send much more than just commands. In our next lesson, we will explore how to handle any type of message, whether it's plain text, a photo, a document, or a sticker, using grammY's powerful filtering capabilities.
Can't find a good explanation? Sign up and we'll make it for you
Sign up