npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

grammy-edit-or-reply

v1.2.0

Published

Exports utilities for grammy to edit a message or reply to a message based on the context

Downloads

81

Readme

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

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

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());

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.

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.

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:

  • InputFiles 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, 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`,
};

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.