Parse Mode Plugin (parse-mode )
Telegram supports styled messages. This library brings simplified formatting utilities to grammY. It enables you to compose richly formatted messages using a declarative, type-safe API.
In the Telegram Bot API, formatted text is represented using entities—special markers that define which parts of the text should be formatted in specific ways. Each entity has a type (e.g. bold, italic, etc), an offset (where it starts in the text), and a length (how many characters it affects).
Working directly with these entities can be cumbersome as you need to manually track offsets and lengths. The Parse Mode plugin solves this problem by providing a simple, declarative API for formatting text.
Two Approaches: fmt and FormattedString
This library offers two main approaches to text formatting:
fmtTagged Template Function: A template literal tag that allows you to write formatted text in a natural way using template expressions. It internally manages entity offsets and lengths for you.FormattedClass: A class-based approach that allows you to build formatted text through method chaining. This is particularly useful for programmatically constructing complex formatted messages.String
Both approaches produce a unified Formatted object that can be used to manipulate formatted text.
Usage (using fmt)
import { Bot } from "grammy";
import { b, fmt, u } from "@grammyjs/parse-mode";
const bot = new Bot("");
bot.command("demo", async (ctx) => {
// Using return values of fmt
const combined = fmt`${b}bolded${b} ${ctx.msg.text} ${u}underlined${u}`;
await ctx.reply(combined.text, { entities: combined.entities });
await ctx.replyWithPhoto(
"https://raw.githubusercontent.com/grammyjs/website/main/logos/grammY.png",
{ caption: combined.caption, caption_entities: combined.caption_entities },
);
});
bot.start();2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const { Bot } = require("grammy");
const { fmt, b, u } = require("@grammyjs/parse-mode");
const bot = new Bot("");
bot.command("demo", async (ctx) => {
// Using return values of fmt
const combined = fmt`${b}bolded${b} ${ctx.msg.text} ${u}underlined${u}`;
await ctx.reply(combined.text, { entities: combined.entities });
await ctx.replyWithPhoto(
"https://raw.githubusercontent.com/grammyjs/website/main/logos/grammY.png",
{ caption: combined.caption, caption_entities: combined.caption_entities },
);
});
bot.start();2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { Bot } from "https://deno.land/x/grammy@v1.41.1/mod.ts";
import { b, fmt, u } from "https://deno.land/x/grammy_parse_mode@2.2.1/mod.ts";
const bot = new Bot("");
bot.command("demo", async (ctx) => {
// Using return values of fmt
const combined = fmt`${b}bolded${b} ${ctx.msg.text} ${u}underlined${u}`;
await ctx.reply(combined.text, { entities: combined.entities });
await ctx.replyWithPhoto(
"https://raw.githubusercontent.com/grammyjs/website/main/logos/grammY.png",
{ caption: combined.caption, caption_entities: combined.caption_entities },
);
});
bot.start();2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Usage (using FormattedString )
import { Bot } from "grammy";
import { FormattedString } from "@grammyjs/parse-mode";
const bot = new Bot("");
bot.command("demo", async (ctx) => {
// Static method
const staticCombined = FormattedString.b("bolded").plain(` ${ctx.msg.text} `)
.u("underlined");
await ctx.reply(staticCombined.text, { entities: staticCombined.entities });
await ctx.replyWithPhoto(
"https://raw.githubusercontent.com/grammyjs/website/main/logos/grammY.png",
{
caption: staticCombined.caption,
caption_entities: staticCombined.caption_entities,
},
);
// Or constructor
const constructorCombined = (new FormattedString("")).b("bolded").plain(
` ${ctx.msg.text} `,
).u("underlined");
await ctx.reply(constructorCombined.text, {
entities: constructorCombined.entities,
});
await ctx.replyWithPhoto(
"https://raw.githubusercontent.com/grammyjs/website/main/logos/grammY.png",
{
caption: constructorCombined.caption,
caption_entities: constructorCombined.caption_entities,
},
);
});
bot.start();2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
const { Bot } = require("grammy");
const { FormattedString } = require("@grammyjs/parse-mode");
const bot = new Bot("");
bot.command("demo", async (ctx) => {
// Static method
const staticCombined = FormattedString.b("bolded").plain(` ${ctx.msg.text} `)
.u("underlined");
await ctx.reply(staticCombined.text, { entities: staticCombined.entities });
await ctx.replyWithPhoto(
"https://raw.githubusercontent.com/grammyjs/website/main/logos/grammY.png",
{
caption: staticCombined.caption,
caption_entities: staticCombined.caption_entities,
},
);
// Or constructor
const constructorCombined = (new FormattedString("")).b("bolded").plain(
` ${ctx.msg.text} `,
).u("underlined");
await ctx.reply(constructorCombined.text, {
entities: constructorCombined.entities,
});
await ctx.replyWithPhoto(
"https://raw.githubusercontent.com/grammyjs/website/main/logos/grammY.png",
{
caption: constructorCombined.caption,
caption_entities: constructorCombined.caption_entities,
},
);
});
bot.start();2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import { Bot } from "https://deno.land/x/grammy@v1.41.1/mod.ts";
import { FormattedString } from "https://deno.land/x/grammy_parse_mode@2.2.1/mod.ts";
const bot = new Bot("");
bot.command("demo", async (ctx) => {
// Static method
const staticCombined = FormattedString.b("bolded").plain(` ${ctx.msg.text} `)
.u("underlined");
await ctx.reply(staticCombined.text, { entities: staticCombined.entities });
await ctx.replyWithPhoto(
"https://raw.githubusercontent.com/grammyjs/website/main/logos/grammY.png",
{
caption: staticCombined.caption,
caption_entities: staticCombined.caption_entities,
},
);
// Or constructor
const constructorCombined = (new FormattedString("")).b("bolded").plain(
` ${ctx.msg.text} `,
).u("underlined");
await ctx.reply(constructorCombined.text, {
entities: constructorCombined.entities,
});
await ctx.replyWithPhoto(
"https://raw.githubusercontent.com/grammyjs/website/main/logos/grammY.png",
{
caption: constructorCombined.caption,
caption_entities: constructorCombined.caption_entities,
},
);
});
bot.start();2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
Core Concepts
FormattedString as unified return type
The Formatted class is a core component of the parse plugin, providing a unified interface for working with formatted text. The return value of fmt, new Formatted and Formatted returns an instance of Formatted. This means that different style of usages can be combined.
For example, it is possible to use fmt, followed by chainable instance methods of Formatted, and then passing the result into another fmt tagged template.
bot.on("msg:text", async ctx => {
// The result of fmt`${${u}Memory updated!${u}}` is a FormattedString
// whose instance method call of `.plain("\n")` also returns a FormattedString
const header = fmt`${${u}Memory updated!${u}}`.plain("\n");
const body = FormattedString.plain("I will remember this!");
const footer = "\n - by grammy AI";
// This is also legal - you can give FormattedString and string to `fmt`
const response = fmt`${header}${body}${footer}`;
await ctx.reply(response.text, { entities: response.entities });
});2
3
4
5
6
7
8
9
10
11
Things that fmt accepts
The fmt tagged template accepts a wide variety of values for constructing your Formatted, including:
Text(implemented byWith Entities Formattedand regular Telegram text messages)String Caption(implemented byWith Entities Formattedand regular Telegram media messages with captions)String - EntityTag (such as your
b()anda(url)functions) - Nullary functions that returns an EntityTag (such as
bandi) - Any types that implements
to(will be treated as plain text value)String()
TextWithEntities
The Text interface represents text with optional formatting entities.
interface TextWithEntities {
text: string;
entities?: MessageEntity[];
}2
3
4
Notice that the shape of this type implies that regular text messages from Telegram also implement Text implicitly. This means that it is in fact possible to do the following:
bot.on("msg:text", async (ctx) => {
const response = fmt`${ctx.msg}`.plain("\n---\n").bold("This is my response");
await ctx.reply(response.text, { entities: response.entities });
});2
3
4
CaptionWithEntities
The Caption interface represents a caption with optional formatting entities.
interface CaptionWithEntities {
caption: string;
caption_entities?: MessageEntity[];
}2
3
4
Likewise, notice that the shape of this type implies that regular media messages with caption from Telegram also implement Caption implicitly. This means that it is also in fact possible to do the following:
bot.on("msg:caption", async (ctx) => {
const response = fmt`${ctx.msg}`.plain("\n---\n").bold("This is my response");
await ctx.reply(response.text, { entities: response.entities });
});2
3
4