Can't reply to messages sent in private chats
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
Hi! Have you found any solution? I'm facing the same problem.
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!
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.
Small group chats do not need an access hash to be interacted with. What kind is
message.peer_ref().id.kind()? If it saysChannel, that would probably be the problem.
&message.peer_ref().id.kind() = Chat
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.
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
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.
Does the ID belong to a user by any chance, and it's somehow getting confused thinking it's a chat?
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.
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.