Skip to content

Commit 7a7e88b

Browse files
authored
feat(body): add entities formatting support (#250)
1 parent a553a6b commit 7a7e88b

10 files changed

+278
-1
lines changed

README.md

+16
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,22 @@ const menuTemplate = new MenuTemplate<MyContext>((ctx) => {
160160
});
161161
```
162162

163+
### Can I use `entities` in the message body?
164+
165+
Also see: [`grammyjs/parse-mode`](https://github.com/grammyjs/parse-mode)
166+
167+
```ts
168+
import { bold, fmt, underline } from '@grammyjs/parse-mode';
169+
170+
const menu = new MenuTemplate<MyContext>(async () => {
171+
const message = fmt`${bold(underline('Hello world!'))}`;
172+
return {
173+
text: message.text,
174+
entities: message.entities,
175+
};
176+
});
177+
```
178+
163179
### Can the menu body be some media?
164180

165181
The menu body can be an object containing `media` and `type` for media.

source/body.test.ts

+32
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,21 @@ const EXAMPLE_TEXTS: ReadonlyArray<string | TextBody> = [
112112
text: 'Hello World',
113113
disable_web_page_preview: true,
114114
},
115+
{
116+
text: 'Hello world!',
117+
entities: [
118+
{
119+
type: 'bold',
120+
offset: 0,
121+
length: 5,
122+
},
123+
{
124+
type: 'italic',
125+
offset: 6,
126+
length: 5,
127+
},
128+
],
129+
},
115130
];
116131

117132
const EXAMPLE_MEDIA: readonly MediaBody[] = [
@@ -130,6 +145,23 @@ const EXAMPLE_MEDIA: readonly MediaBody[] = [
130145
text: 'whatever',
131146
parse_mode: 'Markdown',
132147
},
148+
{
149+
media: 'whatever',
150+
type: 'photo',
151+
text: 'Hello world!',
152+
entities: [
153+
{
154+
type: 'bold',
155+
offset: 0,
156+
length: 5,
157+
},
158+
{
159+
type: 'italic',
160+
offset: 6,
161+
length: 5,
162+
},
163+
],
164+
},
133165
];
134166

135167
const EXAMPLE_LOCATION: readonly LocationBody[] = [

source/body.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type {InputFile} from 'grammy';
22
import type {
3-
LabeledPrice, Location, ParseMode, Venue,
3+
LabeledPrice, Location, MessageEntity, ParseMode, Venue,
44
} from 'grammy/types';
55
import type {ReadonlyDeep} from 'type-fest';
66
import {hasTruthyKey, isObject} from './generic-types.js';
@@ -24,6 +24,7 @@ export type MediaType = typeof MEDIA_TYPES[number];
2424

2525
export type TextBody = {
2626
readonly text: string;
27+
readonly entities?: MessageEntity[];
2728
readonly parse_mode?: ParseMode;
2829
readonly disable_web_page_preview?: boolean;
2930
};
@@ -34,6 +35,7 @@ export type MediaBody = {
3435

3536
/** Caption */
3637
readonly text?: string;
38+
readonly entities?: MessageEntity[];
3739
readonly parse_mode?: ParseMode;
3840
};
3941

@@ -160,6 +162,7 @@ export function isInvoiceBody(body: unknown): body is InvoiceBody {
160162
&& typeof invoice.description === 'string';
161163
}
162164

165+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
163166
export function getBodyText(body: TextBody | string): string {
164167
return typeof body === 'string' ? body : body.text;
165168
}

source/send-menu.ts

+4
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,7 @@ export function generateEditMessageIntoMenuFunction<Context>(
361361
type: body.type,
362362
media: body.media,
363363
caption: body.text,
364+
caption_entities: body.entities,
364365
parse_mode: body.parse_mode,
365366
},
366367
createGenericOther(keyboard, other),
@@ -401,12 +402,14 @@ export function generateEditMessageIntoMenuFunction<Context>(
401402
}
402403

403404
function createTextOther(
405+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
404406
body: string | TextBody,
405407
keyboard: InlineKeyboard,
406408
base: Readonly<Record<string, unknown>>,
407409
) {
408410
return {
409411
...base,
412+
entities: typeof body === 'string' ? undefined : body.entities,
410413
parse_mode: typeof body === 'string' ? undefined : body.parse_mode,
411414
disable_web_page_preview: typeof body !== 'string'
412415
&& body.disable_web_page_preview,
@@ -426,6 +429,7 @@ function createSendMediaOther(
426429
...base,
427430
parse_mode: body.parse_mode,
428431
caption: body.text,
432+
caption_entities: body.entities,
429433
reply_markup: {
430434
inline_keyboard: keyboard.map(o => [...o]),
431435
},

test/menu-middleware/reply-to-context.ts

+3
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ await test('menu-middleware reply-to-context replies main menu', async t => {
2929
strictEqual(text, 'whatever');
3030
deepStrictEqual(other, {
3131
disable_web_page_preview: false,
32+
entities: undefined,
3233
parse_mode: undefined,
3334
reply_markup: {
3435
inline_keyboard: [],
@@ -67,6 +68,7 @@ await test('menu-middleware reply-to-context replies main menu explicitly', asyn
6768
strictEqual(text, 'whatever');
6869
deepStrictEqual(other, {
6970
disable_web_page_preview: false,
71+
entities: undefined,
7072
parse_mode: undefined,
7173
reply_markup: {
7274
inline_keyboard: [],
@@ -105,6 +107,7 @@ await test('menu-middleware reply-to-context replies submenu', async t => {
105107
strictEqual(text, 'submenu');
106108
deepStrictEqual(other, {
107109
disable_web_page_preview: false,
110+
entities: undefined,
108111
parse_mode: undefined,
109112
reply_markup: {
110113
inline_keyboard: [],

test/send-menu/context-edit.ts

+9
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ await test('context-edit text reply when not a callback query', async t => {
1313
strictEqual(text, 'whatever');
1414
deepStrictEqual(other, {
1515
disable_web_page_preview: false,
16+
entities: undefined,
1617
parse_mode: undefined,
1718
reply_markup: {
1819
inline_keyboard: [],
@@ -36,6 +37,7 @@ await test('context-edit text reply when no message on callback query', async t
3637
strictEqual(text, 'whatever');
3738
deepStrictEqual(other, {
3839
disable_web_page_preview: false,
40+
entities: undefined,
3941
parse_mode: undefined,
4042
reply_markup: {
4143
inline_keyboard: [],
@@ -65,6 +67,7 @@ await test('context-edit text edit when message is a text message', async t => {
6567
strictEqual(text, 'whatever');
6668
deepStrictEqual(other, {
6769
disable_web_page_preview: false,
70+
entities: undefined,
6871
parse_mode: undefined,
6972
reply_markup: {
7073
inline_keyboard: [],
@@ -106,6 +109,7 @@ await test('context-edit text reply when message is a media message', async t =>
106109
strictEqual(text, 'whatever');
107110
deepStrictEqual(other, {
108111
disable_web_page_preview: false,
112+
entities: undefined,
109113
parse_mode: undefined,
110114
reply_markup: {
111115
inline_keyboard: [],
@@ -151,6 +155,7 @@ await test('context-edit text reply when message is a media message but fails wi
151155
strictEqual(text, 'whatever');
152156
deepStrictEqual(other, {
153157
disable_web_page_preview: false,
158+
entities: undefined,
154159
parse_mode: undefined,
155160
reply_markup: {
156161
inline_keyboard: [],
@@ -193,6 +198,7 @@ await test('context-edit media reply when not a callback query', async t => {
193198
strictEqual(photo, 'whatever');
194199
deepStrictEqual(other, {
195200
caption: undefined,
201+
caption_entities: undefined,
196202
parse_mode: undefined,
197203
reply_markup: {
198204
inline_keyboard: [],
@@ -227,6 +233,7 @@ await test('context-edit media reply when text message', async t => {
227233
strictEqual(photo, 'whatever');
228234
deepStrictEqual(other, {
229235
caption: undefined,
236+
caption_entities: undefined,
230237
parse_mode: undefined,
231238
reply_markup: {
232239
inline_keyboard: [],
@@ -375,6 +382,7 @@ await test('context-edit text edit without webpage preview', async () => {
375382
async editMessageText(_text, other) {
376383
deepStrictEqual(other, {
377384
disable_web_page_preview: true,
385+
entities: undefined,
378386
parse_mode: undefined,
379387
reply_markup: {
380388
inline_keyboard: [],
@@ -409,6 +417,7 @@ await test('context-edit text edit with parse mode', async () => {
409417
async editMessageText(_text, other) {
410418
deepStrictEqual(other, {
411419
disable_web_page_preview: undefined,
420+
entities: undefined,
412421
parse_mode: 'Markdown',
413422
reply_markup: {
414423
inline_keyboard: [],

test/send-menu/context-reply.ts

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ await test('context-reply media', async t => {
1919
strictEqual(media, 'whatever');
2020
deepStrictEqual(other, {
2121
caption: undefined,
22+
caption_entities: undefined,
2223
parse_mode: undefined,
2324
reply_markup: {
2425
inline_keyboard: [],

test/send-menu/context-resend.ts

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ await test('context-resend on callback query', async t => {
1515
strictEqual(text, 'whatever');
1616
deepStrictEqual(other, {
1717
disable_web_page_preview: false,
18+
entities: undefined,
1819
parse_mode: undefined,
1920
reply_markup: {
2021
inline_keyboard: [],
@@ -52,6 +53,7 @@ await test('context-resend on whatever', async t => {
5253
strictEqual(text, 'whatever');
5354
deepStrictEqual(other, {
5455
disable_web_page_preview: false,
56+
entities: undefined,
5557
parse_mode: undefined,
5658
reply_markup: {
5759
inline_keyboard: [],

0 commit comments

Comments
 (0)