grammers icon indicating copy to clipboard operation
grammers copied to clipboard

Can't reply to messages sent in private chats

Open tholoo opened this issue 2 months ago • 10 comments

I'm getting Group(Empty(ChatEmpty{})) for messages sent in private chats and thus can't reply to them, no matter if the message is outgoing or not:

Err(
    Rpc(
        RpcError {
            code: 400,
            name: "CHAT_ID_INVALID",
            value: None,
            caused_by: Some(
                1234,
            ),
        },
    ),
)

Here's an MRE (both the client and the one sending the message must be users not bots, and don't use Saved Messages):

use futures_util::future::{Either, select};
use grammers_client::session::Session;
use grammers_client::{Client, Config, InitParams, SignInError, Update, grammers_tl_types as tl};
use std::io::{self, BufRead, Write};
use std::pin::pin;
use tokio::{runtime, task};

type Result = std::result::Result<(), Box<dyn std::error::Error>>;

const SESSION_FILE: &str = "test.session";

async fn handle_update(client: Client, update: Update) -> Result {
    if let Update::NewMessage(ref message) = update {
        let chat = message.chat();
        if chat.id() == 1234 {
            dbg!(&update);
            message.reply("Pong").await;
        }
    }

    Ok(())
}

async fn async_main() -> Result {
    let api_id = env!("TG_ID").parse().expect("TG_ID invalid");
    let api_hash = env!("TG_HASH").to_string();

    println!("Connecting to Telegram...");
    let client = Client::connect(Config {
        session: Session::load_file_or_create(SESSION_FILE)?,
        api_id,
        api_hash: api_hash.to_string().clone(),
        params: InitParams {
            // Fetch the updates we missed while we were offline
            catch_up: false,
            ..Default::default()
        },
    })
    .await?;
    println!("Connected!");

    if !client.is_authorized().await? {
        println!("Signing in...");
        let phone = prompt("Enter your phone number (international format): ")?;
        let token = client.request_login_code(&phone).await?;
        let code = prompt("Enter the code you received: ")?;
        let signed_in = client.sign_in(&token, &code).await;
        match signed_in {
            Err(SignInError::PasswordRequired(password_token)) => {
                // Note: this `prompt` method will echo the password in the console.
                //       Real code might want to use a better way to handle this.
                let hint = password_token.hint().unwrap();
                let prompt_message = format!("Enter the password (hint {}): ", &hint);
                let password = prompt(prompt_message.as_str())?;

                client
                    .check_password(password_token, password.trim())
                    .await?;
            }
            Ok(_) => (),
            Err(e) => panic!("{}", e),
        };
        println!("Signed in!");
        match client.session().save_to_file(SESSION_FILE) {
            Ok(_) => {}
            Err(e) => {
                println!("NOTE: failed to save the session, will sign out when done: {e}");
            }
        }
    }
    println!("Waiting for messages...");

    loop {
        let exit = pin!(async { tokio::signal::ctrl_c().await });
        let upd = pin!(async { client.next_update().await });

        let update = match select(exit, upd).await {
            Either::Left(_) => break,
            Either::Right((u, _)) => u?,
        };

        let handle = client.clone();
        task::spawn(async move {
            match handle_update(handle, update).await {
                Ok(_) => {}
                Err(e) => eprintln!("Error handling updates!: {e}"),
            }
        });
    }

    println!("Saving session file and exiting...");
    client.session().save_to_file(SESSION_FILE)?;
    Ok(())
}

fn main() -> Result {
    runtime::Builder::new_current_thread()
        .enable_all()
        .build()
        .unwrap()
        .block_on(async_main())
}

fn prompt(message: &str) -> std::result::Result<String, Box<dyn std::error::Error>> {
    let stdout = io::stdout();
    let mut stdout = stdout.lock();
    stdout.write_all(message.as_bytes())?;
    stdout.flush()?;

    let stdin = io::stdin();
    let mut stdin = stdin.lock();

    let mut line = String::new();
    stdin.read_line(&mut line)?;
    Ok(line)
}

Here's the update:

update = NewMessage(
    Message {
        msg: Message {
            id: 2400815,
            outgoing: true,
            date: 2025-10-19T05:11:29Z,
            text: "test",
            chat: Group(
                Empty(
                    ChatEmpty {
                        id: 1234,
                    },
                ),
            ),
            sender: None,
            reply_to_message_id: None,
            via_bot_id: None,
            media: None,
            mentioned: false,
            media_unread: false,
            silent: false,
            post: false,
            from_scheduled: false,
            edit_hide: false,
            pinned: false,
            forward_header: None,
            reply_header: None,
            reply_markup: None,
            fmt_entities: None,
            view_count: None,
            forward_count: None,
            reply_count: None,
            edit_date: None,
            post_author: None,
            grouped_id: None,
            restriction_reason: None,
            action: None,
        },
        raw: NewMessage(
            UpdateNewMessage {
                message: Message(
                    Message {
                        out: true,
                        mentioned: false,
                        media_unread: false,
                        silent: false,
                        post: false,
                        from_scheduled: false,
                        legacy: false,
                        edit_hide: false,
                        pinned: false,
                        noforwards: false,
                        invert_media: false,
                        offline: false,
                        video_processing_pending: false,
                        id: 2400815,
                        from_id: None,
                        from_boosts_applied: None,
                        peer_id: Chat(
                            PeerChat {
                                chat_id: 1234,
                            },
                        ),
                        saved_peer_id: None,
                        fwd_from: None,
                        via_bot_id: None,
                        via_business_bot_id: None,
                        reply_to: None,
                        date: 1760850689,
                        message: "test",
                        media: None,
                        reply_markup: None,
                        entities: None,
                        views: None,
                        forwards: None,
                        replies: None,
                        edit_date: None,
                        post_author: None,
                        grouped_id: None,
                        reactions: None,
                        restriction_reason: None,
                        ttl_period: None,
                        quick_reply_shortcut_id: None,
                        effect: None,
                        factcheck: None,
                        report_delivery_until_date: None,
                        paid_message_stars: None,
                    },
                ),
                pts: 5558849,
                pts_count: 1,
            },
        ),
        state: State {
            date: 1760850689,
            seq: 130,
            message_box: Some(
                Common {
                    pts: 5558849,
                },
            ),
        },
    },
)

It works correctly if catch_up is set to true

tholoo avatar Oct 19 '25 05:10 tholoo

Hi! Have you found any solution? I'm facing the same problem.

PinceredCoder avatar Nov 28 '25 17:11 PinceredCoder

Hi! Have you found any solution? I'm facing the same problem.

Nope. I tried with the latest version and I still have the same problem!

tholoo avatar Nov 29 '25 13:11 tholoo

Small group chats do not need an access hash to be interacted with. What kind is message.peer_ref().id.kind()? If it says Channel, that would probably be the problem.

Lonami avatar Nov 29 '25 14:11 Lonami

Small group chats do not need an access hash to be interacted with. What kind is message.peer_ref().id.kind()? If it says Channel, that would probably be the problem.

&message.peer_ref().id.kind() = Chat

tholoo avatar Nov 30 '25 07:11 tholoo

Thanks. That does imply replies should just work. Unless somehow the chat has been migrated to a megagroup channel, in which case the old chat is read-only. But that would be odd, because you should not be receiving updates from it. So I am frankly not sure what the error is about.

You seem to have censored the caused_by, but I assume it was simply a sendMessage judging from the code using reply.

I don't think https://github.com/Lonami/grammers/commit/594bc613b7c881f6e9e4ea18b42bc208481be21d will help in this specific case, because again small group chats do not need a hash. But it might be worth a try.

It would be interesting to dbg!() the raw invoked SendMessage. But I don't expect more insights.

Lonami avatar Nov 30 '25 11:11 Lonami

I tried again with the new commit but the problem persists. Here's a dbg of the raw SendMessage inside send_message:

Err(
    Rpc(
        RpcError {
            code: 400,
            name: "CHAT_ID_INVALID",
            value: None,
            caused_by: Some(
                1234,
            ),
        },
    ),
)

btw the only thing I changed in the outputs is the id numbers and nothing else. If it helps I also have this problem with gotgproto but not with pyrogram

tholoo avatar Nov 30 '25 13:11 tholoo

That's the dbg!() of the response, not the request. I would be interested in the request, to confirm that it is using InputPeer::Chat.

I haven't used either of those libraries, so I can't really tell - but it is surprising that one would work with small groups and not the other.

Lonami avatar Nov 30 '25 14:11 Lonami

Does the ID belong to a user by any chance, and it's somehow getting confused thinking it's a chat?

Lonami avatar Nov 30 '25 14:11 Lonami

to be clear, the chat is a user and not a group. this is the dbg! of the peer inside SendMessage if that's what you mean:

peer.into() = Chat(
    InputPeerChat {
        chat_id: 1332980361,
    },
)

and yes the chat id is correct and belongs to a user.

tholoo avatar Dec 01 '25 06:12 tholoo

Then that's the bug. We'd need to figure out why and where that user ID is being misinterpreted as a chat ID. I won't be able to check myself for several days but could still review PRs.

Lonami avatar Dec 01 '25 08:12 Lonami