grammy-media-groups
v0.0.5
Published
Media Groups Storage Plugin for grammY
Maintainers
Readme
Media Groups Storage Plugin for grammY
A grammY plugin that stores media group messages using
the storages protocol. It collects all
messages that share the same media_group_id from both incoming updates and
outgoing API responses, and lets you retrieve the full group at any time.
Features
- Middleware — automatically stores every incoming message that has a
media_group_id. - Transformer — intercepts Telegram API responses (
sendMediaGroup,forwardMessage,editMessageMedia,editMessageCaption,editMessageReplyMarkup) and stores returned messages. - Context hydration — adds
ctx.mediaGroups.getForMsg()to fetch the current message's media group. - Reply/pinned helpers —
ctx.mediaGroups.getForReply()andctx.mediaGroups.getForPinned()for sub-messages. - Programmatic access — the returned composer exposes
getMediaGroup(mediaGroupId)for use outside of middleware. - Manual mode — pass
{ autoStore: false }to disable automatic storing and usectx.mediaGroups.store(message)for full control. - Delete —
ctx.mediaGroups.delete(mediaGroupId)ormg.deleteMediaGroup(mediaGroupId)removes a media group from storage. - Convert —
toInputMedia(messages)orctx.mediaGroups.toInputMedia(messages)converts stored messages intoInputMedia[]ready forsendMediaGroup. Supports photo, video, document, audio and animation, with optional caption/parse_mode override.
Installation
Node.js
npm install grammy-media-groupsDeno
import {
mediaGroups,
type MediaGroupsFlavor,
toInputMedia,
} from "npm:grammy-media-groups";Usage
import { Bot, Context, InlineKeyboard } from "grammy";
import { mediaGroups, type MediaGroupsFlavor } from "grammy-media-groups";
type MyContext = Context & MediaGroupsFlavor;
const bot = new Bot<MyContext>("<your-bot-token>");
// Uses MemorySessionStorage by default — pass a custom adapter for persistence
const mg = mediaGroups();
bot.use(mg);
// Install transformer for outgoing API responses
bot.api.config.use(mg.transformer);
// Reply once when the first message of a media group arrives
bot.on("message", async (ctx) => {
const group = await ctx.mediaGroups.getForMsg();
if (group?.length === 1) {
await ctx.reply("Media group detected", {
reply_parameters: { message_id: ctx.msg.message_id },
reply_markup: new InlineKeyboard().text("Copy", "copy"),
});
}
});
// Handle inline keyboard button to resend a media group
bot.callbackQuery("copy", async (ctx) => {
const group = await ctx.mediaGroups.getForReply();
if (group) {
await ctx.replyWithMediaGroup(
ctx.mediaGroups.toInputMedia(group),
);
}
await ctx.answerCallbackQuery();
});
// Reply to an album message with /copy to resend the full media group
bot.command("copy", async (ctx) => {
const group = await ctx.mediaGroups.getForReply();
if (group) {
await ctx.replyWithMediaGroup(
ctx.mediaGroups.toInputMedia(group),
);
}
});
// Programmatic access outside middleware
const messages = await mg.getMediaGroup("some-media-group-id");Manual Mode
To disable automatic storing, pass { autoStore: false }. This gives you full
control over which messages get stored via ctx.mediaGroups.store():
const mg = mediaGroups(undefined, { autoStore: false });
bot.use(mg);
bot.on("message", async (ctx) => {
// Only store messages you care about
if (ctx.msg.media_group_id) {
await ctx.mediaGroups.store(ctx.msg);
}
// You can also manually store reply_to_message
const reply = ctx.msg.reply_to_message;
if (reply?.media_group_id) {
await ctx.mediaGroups.store(reply);
}
// Delete a media group when no longer needed
// await ctx.mediaGroups.delete("some-media-group-id");
});
// Delete from outside middleware
// await mg.deleteMediaGroup("some-media-group-id");