From 231fc96b5ffa0746c3e3ce9bec2fa2437a33d84d Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Fri, 9 Oct 2026 14:38:17 +0700 Subject: [PATCH] docs(groups): explain reading other bots' messages via admin and bot-to-bot mode --- docs/aliases.md | 14 +++++++------- docs/deploy-coolify-selfhosted.md | 24 ++++++++++++++++++++++++ docs/sticker-packs.md | 4 ++++ internal/modules/alias/alias_media.go | 16 ++++++++++------ internal/modules/alias/handlers_test.go | 2 +- 5 files changed, 46 insertions(+), 14 deletions(-) diff --git a/docs/aliases.md b/docs/aliases.md index 68667e3..0a181a3 100644 --- a/docs/aliases.md +++ b/docs/aliases.md @@ -177,20 +177,20 @@ so they stay valid. They are sent back as entities rather than re-rendered as markup, which avoids escaping and re-parsing content the user never wrote as markup. -**Another bot's message cannot be saved.** Telegram's own rule: *"Bots will not -be able to see messages from other bots regardless of mode."* The reply arrives -with its content stripped, so there is nothing to store and no setting that -would change it. +**Another bot's message needs group setup.** By default Telegram strips another +bot's message out of a reply, so there is nothing to store. It arrives intact +once the bot has Bot-to-Bot Communication Mode enabled and is a group admin; see +[Group setup](deploy-coolify-selfhosted.md#4-group-setup). Every refusal for a message that could not be *read* — as opposed to one whose -kind is unsupported — ends with the same advice, because it is the only thing -that works: **forward it into the chat and reply to your copy.** A forwarded +kind is unsupported — ends with the same advice, because it works without any +setup: **forward it into the chat and reply to your copy.** A forwarded copy is a new message sent by a user, so it arrives intact. Three shapes reach that advice, and they are told apart deliberately: | What arrived | Answer | | --- | --- | -| Reply from a sender marked as a bot | Telegram does not let bots read other bots' messages | +| Reply from a sender marked as a bot | Telegram hid that bot's message; admin rights plus Bot-to-Bot Communication fix it | | Reply with a message id but no content field at all | That message reached me with no content | | No reply attached at all | Reply to the message you want to save — and if you did, Telegram did not pass it along | diff --git a/docs/deploy-coolify-selfhosted.md b/docs/deploy-coolify-selfhosted.md index da38e86..5ff969f 100644 --- a/docs/deploy-coolify-selfhosted.md +++ b/docs/deploy-coolify-selfhosted.md @@ -205,6 +205,30 @@ truth, so no separate command-menu file or manual registration step is required. See [Command discovery](../README.md#command-discovery) for how the menu text is built. +## 4. Group setup + +By default a bot in a group runs in privacy mode. It receives only commands +addressed to it, replies to its own messages, and service messages, and it +cannot read messages sent by other bots. That breaks two things: + +- A bare `/command` typed as a reply to another bot's message is routed to that + other bot. Address this bot explicitly, as `/command@`. +- `/addsticker` and `/alias` replying to another bot's message (for example, an + image another bot posted) get the reply with its content stripped. + +To make both work in a group: + +1. In @BotFather, enable **Bot-to-Bot Communication Mode** for this bot. +2. Promote the bot to **admin** in the group. Admins receive every message, so + no permissions beyond the defaults are needed. + +Disabling privacy mode in BotFather (`/setprivacy`) is the alternative to admin +rights, but Telegram applies it only after the bot is removed from the group and +added back. Admin rights take effect immediately. Verified on 2026-10-09: +`/addsticker@` replying to another bot's image failed with privacy +mode off and the bot not re-added, and worked once the bot was promoted to admin. +See Telegram's [privacy mode and bot-to-bot rules](https://core.telegram.org/bots/features#privacy-mode). + ## Operations The live deployment is the Coolify container and MongoDB is the sole system of diff --git a/docs/sticker-packs.md b/docs/sticker-packs.md index ec1560c..4e6e77c 100644 --- a/docs/sticker-packs.md +++ b/docs/sticker-packs.md @@ -113,6 +113,10 @@ exactly 512px with the aspect ratio preserved, uploaded as a PNG, and then added — attributed to the pack owner, matching the set it is about to join. Sources above 2 MB, or with either side above 4096px, are rejected. +**Replying to another bot's image** works only after the +[group setup](deploy-coolify-selfhosted.md#4-group-setup); otherwise Telegram +strips the image out of the reply. + ## Video and GIF A replied video, GIF, animation or video note is downloaded and transcoded with diff --git a/internal/modules/alias/alias_media.go b/internal/modules/alias/alias_media.go index 3ad65ac..d3d249a 100644 --- a/internal/modules/alias/alias_media.go +++ b/internal/modules/alias/alias_media.go @@ -34,13 +34,17 @@ const unsupportedRefusal = "That message cannot be saved. Reply to a sticker, ph // the answer to all of them — only the reason differs. const forwardAdvice = "Forward it into this chat, then reply to your copy with /alias ." -// otherBotRefusal explains a refusal no change here can lift. +// otherBotRefusal explains why another bot's message arrived empty. // -// Telegram's own rule: "Bots will not be able to see messages from other bots -// regardless of mode." The reply arrives with its content stripped, so there is -// nothing to save and no setting that would help — worth saying outright rather -// than letting unsupportedRefusal imply the format was wrong. -const otherBotRefusal = "Telegram does not let bots read other bots' messages, so I cannot save that one. " + forwardAdvice +// Telegram strips another bot's message out of a reply unless this bot has +// Bot-to-Bot Communication Mode enabled in BotFather and receives every group +// message — as a group admin, or with privacy mode off. Both are chat and +// BotFather settings, not code, so the refusal names them alongside the +// forward workaround rather than letting unsupportedRefusal imply the format +// was wrong. +const otherBotRefusal = "Telegram hid that bot's message from me, so I cannot save it. " + + "I can read other bots' messages only when I am an admin in this chat and Bot-to-Bot Communication is on for me in @BotFather. " + + forwardAdvice // strippedReplyRefusal answers a reply Telegram delivered empty: it carried a // message id but no content field at all. diff --git a/internal/modules/alias/handlers_test.go b/internal/modules/alias/handlers_test.go index fd20a5a..b67d06c 100644 --- a/internal/modules/alias/handlers_test.go +++ b/internal/modules/alias/handlers_test.go @@ -448,7 +448,7 @@ func TestAlias_ExplainsAnotherBotsMessage(t *testing.T) { From: &models.User{ID: 555, IsBot: true, FirstName: "OtherBot"}, })) - rb.AssertSentText(t, "does not let bots read other bots") + rb.AssertSentText(t, "hid that bot's message") } // A human's unsupported message still gets the format advice, not the bot one.