✨ Provides a plugin for grammy to easily edit a message or reply to a message based on the context.
This plugin aims to greatly simplify the way you write code by unifying handlers for commands, callbacks, and inline mode. It can seamlessly edit to and from:
-
📝 Text messages
-
🖼️ Photos
-
🎞️ Animations
-
📹 Videos
-
📄 Documents
-
🎵 Audio
function makeMediaGallery(selectedIdx?: number) {
const keyboard: InlineKeyboardButton[][] = [
myMedias.map((x, i) => ({
text: prettyMediaTypes[x.type],
callback_data: `media_${i}`,
})),
];
const media =
selectedIdx !== undefined ? myMedias.at(selectedIdx) : undefined;
if (media) {
return {
text: `Media id: <code>${media.media}</code>`,
parse_mode: 'HTML',
media,
keyboard,
} satisfies MessageDataMedia;
}
return {
text: 'Pick the media from the options',
keyboard,
} satisfies MessageData;
}
// generate inlineQueryResults based on the message data
bot.inlineQuery(/.*/, async (ctx) => {
const results = myMedias.map((x, i) => ({
...makeInlineResult(makeMediaGallery(i)),
id: `media-${i}`,
title: `Send ${x.type} ${prettyMediaTypes[x.type]}`,
}));
await ctx.answerInlineQuery(results);
});
// handle the command
bot.command('start', async (ctx) => {
await ctx.editOrReply(makeMediaGallery());
});
// and also the callback query (including in inline mode)
bot.callbackQuery(/media_(\d+)/, async (ctx) => {
const selectedIdx = Number(ctx.match[1]);
await ctx.editOrReply(makeMediaGallery(selectedIdx));
});
Tip
You can run this example yourself, the complete code is present in examples/gallery.ts
, simply run $ deno run --allow-net examples/gallery.ts
after building with $ npm run build
.
When editing from a text message to one containing media, the previous message will be deleted and the new one with the media will be sent. The same behavior also happens when replacing a message containing media with one without media. If an error occurs, no message is deleted.
You can import the editOrReply
function to use the same functionality when a Context
object is unavailable.
npm install grammy-edit-or-reply
# or
yarn add grammy-edit-or-reply
You can then add the middleware as follows:
// extend your context
type MyContext = Context & EditOrReplyFlavor;
const bot = new Bot<MyContext>('12345:ABCDE');
// register the middleware
bot.use(editOrReplyMiddleware());
This library offers a simple unified interface for defining messages and inline messages, both for sending and editing, with and without media, see MessageData
for all the options.
function getMenuMessage(ctx: MyContext) {
return {
text: 'This is a very complete example of the things that edit-or-reply can do for you!',
media: {
type: 'animation',
media: 'file-id-here',
},
has_spoiler: true,
show_caption_above_media: true,
// only inline keyboards are supported since other kinds
// of keyboards can't be passed to message-editing endpoints
keyboard: [[{ text: 'Hello World', url: 'https://example.com' }]],
disable_notification: true,
protect_content: true,
reply_parameters: ctx.msgId ? { message_id: ctx.msgId } : undefined,
entities: [
{ offset: 10, length: 4, type: 'bold' },
{
offset: 51,
length: 13,
type: 'text_link',
url: 'https://github.com/rayz1065/grammy-edit-or-reply',
},
],
// explicitly set parse_mode to undefined if you're using the parseMode plugin,
// otherwise entities will not work!
parse_mode: undefined,
// using `satisfies` helps keep the type narrow
} satisfies MessageData;
}
This allows you to describe messages in a method-agnostic way, editOrReply
will then be tasked to pick the right one out of the 11 available and correctly structure the data to call it.
The return type depends on the method used, most of them will return the message, except for inline methods that return true
.
A simple type check (or assertion if you're sure no inline method will be used) will allow you to access the message's data.
bot.command('start', async (ctx) => {
const result = await ctx.editOrReply(getMenuMessage(ctx));
// notice how inline-mode methods can return True
assert(typeof result !== 'boolean');
console.log(result.message_id);
});
This plugin can also help you generate inline query results out of your messages through the makeInlineResult
function, with a few caveats:
InputFile
s are not supported insendInlineQuery
, the type of media must therefore be narrowed down in case it's too broad usingmessageDataHasNoInputFile
.- Permitting URLs as media would require adding a lot of extra metadata (like thumbnails and mime types). In order to keep message definition simple only file ids are allowed;
- sending an audio by file id can run into issues with metadata, even though the file is already stored on Telegram's server. Audios are therefore sent as documents, changing the appearance of the inline result, but not that of the sent message.
const messageData = getMenuMessage();
assert(messageDataHasNoInputFile(messageData)); // if necessary
const result = {
...makeInlineResult(getMenuMessage()),
id: `0`,
title: `Main menu`,
};
Under the hood editOrReply
uses a function called getMessageInfo
to determine the type of the message in the current context, three types are possible:
-
OldMessageInfoChat
, only the chat is available -
OldMessageInfoChatMessage
, the chat and a message to edit are available -
OldMessageInfoInline
, an inline message is available
OldMessageInfoChatMessage
and OldMessageInfoInline
further specify whether the previous message has a media by using getMessageMediaInfo
on the message, if the message is not available (inline mode or inaccessible message) a guess will be made.
Call ctx.getMessageInfo
directly if you want more control over the guess and pass the result to ctx.editOrReply
.
Grammy Edit or Reply is available under the MIT License.