NicoNex / echotron

An elegant and concurrent library for the Telegram bot API in Go.
https://t.me/s/echotronnews
GNU Lesser General Public License v3.0
363 stars 26 forks source link

ReplyToMessage is not being detected #44

Closed erkinov-wtf closed 4 months ago

erkinov-wtf commented 4 months ago

hello folks, ive been creating a bot that will publish everything sent to the bot to the channel. the code was working fine until i noticed smth. when users want to reply to messages from channel and selecting reply in another chat option then choosing the bot to send, the messages is not showing the reply message, it is sending just a message. so i added debug messsages and indeed it is reply to message struct is nil. here's my part of code:

package message

import (
    "fmt"
    "github.com/NicoNex/echotron/v3"
    "pu/handlers"
    "pu/pkg/handlers/media"
    "pu/pkg/handlers/reply"
    "pu/pkg/types"
    "sync"
)

func HandleMessage(api *echotron.API, message *echotron.Message, chatID int64, mediaGroupMap *map[string][]echotron.GroupableInputMedia, originalMessages *map[int64]int, mu *sync.Mutex) {
    fmt.Printf("Received message: %+v\n", message)

    switch {
    case message.Text != "":
        // If the message is a text, send it to the channel
        msg, _ := api.SendMessage(message.Text, chatID, nil)
        // Store the original message ID
        mu.Lock()
        (*originalMessages)[int64(message.ID)] = msg.Result.ID
        mu.Unlock()
    case message.Photo != nil:
        // If the message is a photo, handle media group
        media.HandleMediaGroup(api, message, chatID, mediaGroupMap, mu, types.Photo)
    case message.Document != nil:
        // If the message is a document, handle media group
        media.HandleMediaGroup(api, message, chatID, mediaGroupMap, mu, types.Doc)
    }

    // If the message is a reply to another message, handle the reply
    fmt.Printf("ReplyToMessage: %+v\n", message.ReplyToMessage)
    if message.ReplyToMessage != nil {
        reply.HandleForwardedReply(api, message, chatID, originalMessages, mu)
    }

    // Reply to the user
    handlers.ReplyMessage(api, message)
}

and here's the debug messages:

Received message:
{
    MessageAutoDeleteTimerChanged: <nil>,
    Contact: <nil>,
    SenderChat: <nil>,
    WebAppData: <nil>,
    From: {
        ID: 123456789,
        IsBot: false,
        FirstName: "John",
        LastName: "Doe",
        Username: "john_doe",
        LanguageCode: "en"
    },
    VideoChatParticipantsInvited: <nil>,
    Invoice: <nil>,
    SuccessfulPayment: <nil>,
    VideoChatEnded: <nil>,
    VideoChatStarted: <nil>,
    ReplyToMessage: <nil>,
    ViaBot: <nil>,
    Poll: <nil>,
    ProximityAlertTriggered: <nil>,
    ReplyMarkup: <nil>,
    Document: <nil>,
    PinnedMessage: <nil>,
    LeftChatMember: <nil>,
    Animation: <nil>,
    Audio: <nil>,
    Voice: <nil>,
    Location: <nil>,
    Sticker: <nil>,
    Video: <nil>,
    VideoNote: <nil>,
    Venue: <nil>,
    Game: <nil>,
    Dice: <nil>,
    ForumTopicCreated: <nil>,
    ForumTopicEdited: <nil>,
    VideoChatScheduled: <nil>,
    ForumTopicClosed: <nil>,
    ForumTopicReopened: <nil>,
    GeneralForumTopicHidden: <nil>,
    GeneralForumTopicUnhidden: <nil>,
    GiveawayCreated: <nil>,
    Giveaway: <nil>,
    GiveawayWinners: <nil>,
    GiveawayCompleted: <nil>,
    WriteAccessAllowed: <nil>,
    UsersShared: <nil>,
    ChatShared: <nil>,
    Story: <nil>,
    ReplyToStory: <nil>,
    ExternalReply: <nil>,
    Quote: <nil>,
    LinkPreviewOptions: <nil>,
    ForwardOrigin: <nil>,
    BoostAdded: <nil>,
    ChatBackgroundSet: <nil>,
    SenderBusinessBot: <nil>,
    MediaGroupID: "",
    ConnectedWebsite: "",
    NewChatTitle: "",
    AuthorSignature: "",
    Caption: "",
    Text: "test",
    BusinessConnectionID: "",
    CaptionEntities: [],
    NewChatPhoto: [],
    NewChatMembers: [],
    Photo: [],
    Entities: [
        {
            Type: "mention",
            Offset: 0,
            Length: 5,
            URL: "",
            User: <nil>,
            Language: ""
        }
    ],
    Chat: {
        Type: "private",
        Title: "",
        Username: "dummy_username",
        FirstName: "Dummy",
        LastName: "User",
        ID: 987654321,
        IsForum: false
    },
    ID: 576,
    ThreadID: 0,
    MigrateFromChatID: 0,
    Date: 1716120189,
    MigrateToChatID: 0,
    EditDate: 0,
    SenderBoostCount: 0,
    DeleteChatPhoto: false,
    IsTopicMessage: false,
    IsAutomaticForward: false,
    GroupChatCreated: false,
    SupergroupChatCreated: false,
    ChannelChatCreated: false,
    HasProtectedContent: false,
    HasMediaSpoiler: false,
    IsFromOffline: false
}

ReplyToMessage: <nil>
DjMike238 commented 4 months ago

Hi! Apparently, that's intended behavior by the Telegram API: external replies (the ones made with the Reply in other chat options) are handled by Telegram as normal messages with a custom struct.

So, if you want to handle external replies, you might want to check for the ExternalReply (type ExternalReplyInfo) struct in Message.

You can find more info about it here: https://core.telegram.org/bots/api#message:~:text=is%20a%20reply.-,external_reply,-ExternalReplyInfo https://core.telegram.org/bots/api#externalreplyinfo

Also, here's the ExternalReplyInfo struct in Echotron's docs: https://pkg.go.dev/github.com/NicoNex/echotron/v3#ExternalReplyInfo

erkinov-wtf commented 4 months ago

Hi! Apparently, that's intended behavior by the Telegram API: external replies (the ones made with the Reply in other chat options) are handled by Telegram as normal messages with a custom struct.

So, if you want to handle external replies, you might want to check for the ExternalReply (type ExternalReplyInfo) struct in Message.

You can find more info about it here: https://core.telegram.org/bots/api#message:~:text=is%20a%20reply.-,external_reply,-ExternalReplyInfo https://core.telegram.org/bots/api#externalreplyinfo

Also, here's the ExternalReplyInfo struct in Echotron's docs: https://pkg.go.dev/github.com/NicoNex/echotron/v3#ExternalReplyInfo

aha it really is that. i tried to implement it but still cant do it fully, so it would be great if you would help me out here. so here's the func that decided what type of the message is:

func HandleMessage(api *echotron.API, message *echotron.Message, chatID int64, mediaGroupMap *map[string][]echotron.GroupableInputMedia, originalMessages *map[int64]int, mu *sync.Mutex) {
    if message == nil {
        fmt.Println("Received nil message")
        return
    }

    switch {
    case message.Text != "":
        // If the message is a text, send it to the channel without sender information
        sentMsg, err := api.SendMessage(message.Text, chatID, nil)
        if err == nil && sentMsg.Result != nil {
            mu.Lock()
            (*originalMessages)[int64(message.ID)] = sentMsg.Result.ID
            mu.Unlock()
        } else {
            fmt.Println("Error sending message or sentMsg.Result is nil:", err)
        }
    case message.Photo != nil:
        // If the message is a photo, handle media group
        media.HandleMediaGroup(api, message, chatID, mediaGroupMap, mu, types.Photo)
    case message.Document != nil:
        // If the message is a document, handle media group
        media.HandleMediaGroup(api, message, chatID, mediaGroupMap, mu, types.Doc)
    }

    // Check if message.ReplyToMessage is nil before accessing it
    if message.ExternalReply != nil {
        fmt.Println(message.ExternalReply)
        reply.HandleReplyMessage(api, message, chatID, originalMessages, mu)
    }

    // Reply to the user
    handlers.ReplyMessage(api, message)
}

and here's HandleReplyMessage func:

func HandleReplyMessage(api *echotron.API, message *echotron.Message, chatID int64, originalMessages *map[int64]int, mu *sync.Mutex) {
    mu.Lock()
    originalMsgID, ok := (*originalMessages)[int64(message.ReplyToMessage.ID)]
    mu.Unlock()

    if ok {
        externalReplyInfo := echotron.ExternalReplyInfo{
            MessageID: originalMsgID,
            Chat: echotron.Chat{
                ID: chatID,
            },
            Origin: echotron.MessageOrigin{
                Type: "channel",
                Date: message.Date,
            },
        }

        options := &echotron.MessageOptions{
            ReplyParameters: echotron.ReplyParameters{
                MessageID: originalMsgID,
                ChatID:    chatID,
            },
            ParseMode:         echotron.MarkdownV2,
            ExternalReplyInfo: &externalReplyInfo,
        }
        _, err := api.SendMessage(message.Text, chatID, options)
        if err != nil {
            // Log the error if sending the message fails
            api.SendMessage("Error sending the external reply.", message.Chat.ID, nil)
        }
    } else {
        // Handle case where the original message is not found
        api.SendMessage("Original message not found.", message.Chat.ID, nil)
    }
}

i tried to use externalreply strcut inside of the messageOptions but there is error saying no ExternalReplyInfo field (as expected). but SendMessage func only accepts MessageOptions struct so cant think how to proceeed.

DjMike238 commented 4 months ago

Yeah, there's no external_reply_info parameter for send_message in the official API: https://core.telegram.org/bots/api#sendmessage

I'm not really sure what you're trying to do here, can you please explain?

erkinov-wtf commented 4 months ago

Yeah, there's no external_reply_info parameter for send_message in the official API: https://core.telegram.org/bots/api#sendmessage

I'm not really sure what you're trying to do here, can you please explain?

okay, so users can send messages/photos/documents to the bot and bot then sends it to the channel. the idea is to ensure the anonymus comminication. currently it is working fine. but users want to reply messages sent to the channel. since users dont have access to the channel, i thought it would be possible for users to reply any messages via my bot by just using that "reply in another chat" feature

DjMike238 commented 4 months ago

since users dont have access to the channel, i thought it would be possible for users to reply any messages via my bot by just using that "reply in another chat" feature

I'm not sure that's actually feasible, as far as I know a bot does not receive any kind of update when a message from the bot is answered in an external chat (I also just tested it, just to be sure).

I think you might want to implement a custom way to reply to messages through your bot for your use case.