grammy-edit-or-reply
Version:
Exports utilities for grammy to edit a message or reply to a message based on the context
183 lines (141 loc) • 6.46 kB
Markdown
# Grammy Edit or Reply
✨ 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
```typescript
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.
## Installation and Setup
```bash
npm install grammy-edit-or-reply
# or
yarn add grammy-edit-or-reply
```
You can then add the middleware as follows:
```typescript
// extend your context
type MyContext = Context & EditOrReplyFlavor;
const bot = new Bot<MyContext>('12345:ABCDE');
// register the middleware
bot.use(editOrReplyMiddleware());
```
## Unified Message Data Interface
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.
```typescript
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.
## Return type
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.
```typescript
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);
});
```
## Generating Inline Query Results
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 in `sendInlineQuery`, the type of media must therefore be narrowed down in case it's too broad using `messageDataHasNoInputFile`.
- 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](https://github.com/telegraf/telegraf/issues/884#issuecomment-582278542), 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.
```typescript
const messageData = getMenuMessage();
assert(messageDataHasNoInputFile(messageData)); // if necessary
const result = {
...makeInlineResult(getMenuMessage()),
id: `0`,
title: `Main menu`,
};
```
## How it works
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`.
## License
Grammy Edit or Reply is available under the **MIT License**.