1use layer_tl_types as tl;
8use layer_tl_types::{Cursor, Deserializable};
9
10use crate::{Client, InvocationError as Error};
11
12#[derive(Debug, Clone)]
16pub struct IncomingMessage {
17 pub raw: tl::enums::Message,
19}
20
21impl IncomingMessage {
22 pub(crate) fn from_raw(raw: tl::enums::Message) -> Self {
23 Self { raw }
24 }
25
26 pub fn text(&self) -> Option<&str> {
28 match &self.raw {
29 tl::enums::Message::Message(m) => {
30 if m.message.is_empty() { None } else { Some(&m.message) }
31 }
32 _ => None,
33 }
34 }
35
36 pub fn id(&self) -> i32 {
38 match &self.raw {
39 tl::enums::Message::Message(m) => m.id,
40 tl::enums::Message::Service(m) => m.id,
41 tl::enums::Message::Empty(m) => m.id,
42 }
43 }
44
45 pub fn peer_id(&self) -> Option<&tl::enums::Peer> {
47 match &self.raw {
48 tl::enums::Message::Message(m) => Some(&m.peer_id),
49 tl::enums::Message::Service(m) => Some(&m.peer_id),
50 _ => None,
51 }
52 }
53
54 pub fn sender_id(&self) -> Option<&tl::enums::Peer> {
56 match &self.raw {
57 tl::enums::Message::Message(m) => m.from_id.as_ref(),
58 tl::enums::Message::Service(m) => m.from_id.as_ref(),
59 _ => None,
60 }
61 }
62
63 pub fn outgoing(&self) -> bool {
65 match &self.raw {
66 tl::enums::Message::Message(m) => m.out,
67 tl::enums::Message::Service(m) => m.out,
68 _ => false,
69 }
70 }
71
72 pub async fn reply(&self, client: &mut Client, text: impl Into<String>) -> Result<(), Error> {
74 let peer = match self.peer_id() {
75 Some(p) => p.clone(),
76 None => return Err(Error::Deserialize("cannot reply: unknown peer".into())),
77 };
78 client.send_message_to_peer(peer, &text.into()).await
79 }
80}
81
82#[derive(Debug, Clone)]
86pub struct MessageDeletion {
87 pub message_ids: Vec<i32>,
89 pub channel_id: Option<i64>,
91}
92
93#[derive(Debug, Clone)]
97pub struct CallbackQuery {
98 pub query_id: i64,
99 pub user_id: i64,
100 pub message_id: Option<i32>,
101 pub chat_instance: i64,
102 pub data_raw: Option<Vec<u8>>,
104 pub game_short_name: Option<String>,
106}
107
108impl CallbackQuery {
109 pub fn data(&self) -> Option<&str> {
111 self.data_raw.as_ref().and_then(|d| std::str::from_utf8(d).ok())
112 }
113
114 pub async fn answer(
116 &self,
117 client: &mut Client,
118 text: Option<&str>,
119 ) -> Result<(), Error> {
120 client.answer_callback_query(self.query_id, text, false).await.map(|_| ())
121 }
122
123 pub async fn answer_alert(
125 &self,
126 client: &mut Client,
127 text: &str,
128 ) -> Result<(), Error> {
129 client.answer_callback_query(self.query_id, Some(text), true).await.map(|_| ())
130 }
131}
132
133#[derive(Debug, Clone)]
137pub struct InlineQuery {
138 pub query_id: i64,
139 pub user_id: i64,
140 pub query: String,
141 pub offset: String,
142}
143
144impl InlineQuery {
145 pub fn query(&self) -> &str { &self.query }
147}
148
149#[derive(Debug, Clone)]
153pub struct RawUpdate {
154 pub constructor_id: u32,
156}
157
158#[non_exhaustive]
164#[derive(Debug, Clone)]
165pub enum Update {
166 NewMessage(IncomingMessage),
168 MessageEdited(IncomingMessage),
170 MessageDeleted(MessageDeletion),
172 CallbackQuery(CallbackQuery),
174 InlineQuery(InlineQuery),
176 Raw(RawUpdate),
178}
179
180const ID_UPDATES_TOO_LONG: u32 = 0xe317af7e;
183const ID_UPDATE_SHORT_MESSAGE: u32 = 0x313bc7f8;
184const ID_UPDATE_SHORT_CHAT_MSG: u32 = 0x4d6deea5;
185const ID_UPDATE_SHORT: u32 = 0x78d4dec1;
186const ID_UPDATES: u32 = 0x74ae4240;
187const ID_UPDATES_COMBINED: u32 = 0x725b04c3;
188
189pub(crate) fn parse_updates(bytes: &[u8]) -> Vec<Update> {
195 if bytes.len() < 4 {
196 return vec![];
197 }
198 let cid = u32::from_le_bytes(bytes[..4].try_into().unwrap());
199
200 match cid {
201 ID_UPDATES_TOO_LONG => {
202 log::warn!("updatesTooLong received — some updates may be missed; call getDifference if gap-free delivery is required");
203 vec![]
204 }
205
206 ID_UPDATE_SHORT_MESSAGE => {
208 let mut cur = Cursor::from_slice(bytes);
209 match tl::types::UpdateShortMessage::deserialize(&mut cur) {
210 Ok(m) => {
211 vec![Update::NewMessage(make_short_dm(m))]
212 }
213 Err(e) => { log::warn!("updateShortMessage parse error: {e}"); vec![] }
214 }
215 }
216
217 ID_UPDATE_SHORT_CHAT_MSG => {
219 let mut cur = Cursor::from_slice(bytes);
220 match tl::types::UpdateShortChatMessage::deserialize(&mut cur) {
221 Ok(m) => {
222 vec![Update::NewMessage(make_short_chat(m))]
223 }
224 Err(e) => { log::warn!("updateShortChatMessage parse error: {e}"); vec![] }
225 }
226 }
227
228 ID_UPDATE_SHORT => {
230 let mut cur = Cursor::from_slice(bytes);
231 match tl::types::UpdateShort::deserialize(&mut cur) {
232 Ok(u) => {
233 from_single_update(u.update)
234 }
235 Err(e) => { log::warn!("updateShort parse error: {e}"); vec![] }
236 }
237 }
238
239 ID_UPDATES => {
241 let mut cur = Cursor::from_slice(bytes);
242 match tl::enums::Updates::deserialize(&mut cur) {
243 Ok(tl::enums::Updates::Updates(u)) => {
244 u.updates.into_iter().flat_map(from_single_update).collect()
245 }
246 Err(e) => { log::warn!("Updates parse error: {e}"); vec![] }
247 _ => vec![],
248 }
249 }
250
251 ID_UPDATES_COMBINED => {
252 let mut cur = Cursor::from_slice(bytes);
253 match tl::enums::Updates::deserialize(&mut cur) {
254 Ok(tl::enums::Updates::Combined(u)) => {
255 u.updates.into_iter().flat_map(from_single_update).collect()
256 }
257 Err(e) => { log::warn!("UpdatesCombined parse error: {e}"); vec![] }
258 _ => vec![],
259 }
260 }
261
262 _ => vec![], }
264}
265
266fn from_single_update(upd: tl::enums::Update) -> Vec<Update> {
268 use tl::enums::Update::*;
269 match upd {
270 NewMessage(u) => vec![Update::NewMessage(IncomingMessage::from_raw(u.message))],
271 NewChannelMessage(u) => vec![Update::NewMessage(IncomingMessage::from_raw(u.message))],
272 EditMessage(u) => vec![Update::MessageEdited(IncomingMessage::from_raw(u.message))],
273 EditChannelMessage(u) => vec![Update::MessageEdited(IncomingMessage::from_raw(u.message))],
274 DeleteMessages(u) => vec![Update::MessageDeleted(MessageDeletion { message_ids: u.messages, channel_id: None })],
275 DeleteChannelMessages(u) => vec![Update::MessageDeleted(MessageDeletion { message_ids: u.messages, channel_id: Some(u.channel_id) })],
276 BotCallbackQuery(u) => vec![Update::CallbackQuery(CallbackQuery {
277 query_id: u.query_id,
278 user_id: u.user_id,
279 message_id: Some(u.msg_id),
280 chat_instance: u.chat_instance,
281 data_raw: u.data,
282 game_short_name: u.game_short_name,
283 })],
284 InlineBotCallbackQuery(u) => vec![Update::CallbackQuery(CallbackQuery {
285 query_id: u.query_id,
286 user_id: u.user_id,
287 message_id: None,
288 chat_instance: u.chat_instance,
289 data_raw: u.data,
290 game_short_name: u.game_short_name,
291 })],
292 BotInlineQuery(u) => vec![Update::InlineQuery(InlineQuery {
293 query_id: u.query_id,
294 user_id: u.user_id,
295 query: u.query,
296 offset: u.offset,
297 })],
298 other => {
299 let cid = tl_constructor_id(&other);
301 vec![Update::Raw(RawUpdate { constructor_id: cid })]
302 }
303 }
304}
305
306fn tl_constructor_id(upd: &tl::enums::Update) -> u32 {
308 use tl::enums::Update::*;
309 match upd {
310 UserStatus(_) => 0x1bfbd823,
311 ContactsReset => 0xdeaf4e67,
312 NewEncryptedMessage(_) => 0x12bcbd9a,
313 EncryptedChatTyping(_) => 0x1710f156,
314 Encryption(_) => 0xb4a2e88d,
315 EncryptedMessagesRead(_) => 0x38fe25b7,
316 ChatParticipants(_) => 0x07761198,
317 NewMessage(_) => 0x1f2b0afd,
318 MessageId(_) => 0x4e90bfd6,
319 ReadMessagesContents(_) => 0x68c13933,
320 DeleteMessages(_) => 0xa20db0e5,
321 UserTyping(_) => 0x5c486927,
322 ChatUserTyping(_) => 0x9a65ea1f,
323 ChatParticipantAdd(_) => 0xea4cb65b,
324 ChatParticipantDelete(_) => 0x6e5f2de1,
325 DcOptions(_) => 0x8e5e9873,
326 NotifySettings(_) => 0xbec268ef,
327 ServiceNotification(_) => 0xebe46819,
328 Privacy(_) => 0xee3b272a,
329 UserPhone(_) => 0x05492a13,
330 ReadHistoryInbox(_) => 0x9961fd5c,
331 ReadHistoryOutbox(_) => 0x2f2f21bf,
332 WebPage(_) => 0x7f891213,
333 EditMessage(_) => 0xe40370a3,
334 EditChannelMessage(_) => 0x1b3f4df7,
335 NewChannelMessage(_) => 0x62ba04d9,
336 DeleteChannelMessages(_) => 0xc32d5b12,
337 ChannelMessageViews(_) => 0x98a12b4b,
338 BotCallbackQuery(_) => 0xe9ff1938,
339 InlineBotCallbackQuery(_) => 0x691e9f68,
340 BotInlineQuery(_) => 0x54826690,
341 BotInlineSend(_) => 0x0e48f964,
342 _ => 0x00000000,
343 }
344}
345
346fn make_short_dm(m: tl::types::UpdateShortMessage) -> IncomingMessage {
349 let msg = tl::types::Message {
350 out: m.out,
351 mentioned: m.mentioned,
352 media_unread: m.media_unread,
353 silent: m.silent,
354 post: false,
355 from_scheduled: false,
356 legacy: false,
357 edit_hide: false,
358 pinned: false,
359 noforwards: false,
360 invert_media: false,
361 offline: false,
362 video_processing_pending: false,
363 id: m.id,
364 from_id: Some(tl::enums::Peer::User(tl::types::PeerUser { user_id: m.user_id })),
365 peer_id: tl::enums::Peer::User(tl::types::PeerUser { user_id: m.user_id }),
366 saved_peer_id: None,
367 fwd_from: m.fwd_from,
368 via_bot_id: m.via_bot_id,
369 via_business_bot_id: None,
370 reply_to: m.reply_to,
371 date: m.date,
372 message: m.message,
373 media: None,
374 reply_markup: None,
375 entities: m.entities,
376 views: None,
377 forwards: None,
378 replies: None,
379 edit_date: None,
380 post_author: None,
381 grouped_id: None,
382 reactions: None,
383 restriction_reason: None,
384 ttl_period: None,
385 quick_reply_shortcut_id: None,
386 effect: None,
387 factcheck: None,
388 report_delivery_until_date: None,
389 paid_message_stars: None,
390 suggested_post: None,
391 from_boosts_applied: None,
392 paid_suggested_post_stars: false,
393 paid_suggested_post_ton: false,
394 schedule_repeat_period: None,
395 summary_from_language: None,
396 };
397 IncomingMessage { raw: tl::enums::Message::Message(msg) }
398}
399
400fn make_short_chat(m: tl::types::UpdateShortChatMessage) -> IncomingMessage {
401 let msg = tl::types::Message {
402 out: m.out,
403 mentioned: m.mentioned,
404 media_unread: m.media_unread,
405 silent: m.silent,
406 post: false,
407 from_scheduled: false,
408 legacy: false,
409 edit_hide: false,
410 pinned: false,
411 noforwards: false,
412 invert_media: false,
413 offline: false,
414 video_processing_pending: false,
415 id: m.id,
416 from_id: Some(tl::enums::Peer::User(tl::types::PeerUser { user_id: m.from_id })),
417 peer_id: tl::enums::Peer::Chat(tl::types::PeerChat { chat_id: m.chat_id }),
418 saved_peer_id: None,
419 fwd_from: m.fwd_from,
420 via_bot_id: m.via_bot_id,
421 via_business_bot_id: None,
422 reply_to: m.reply_to,
423 date: m.date,
424 message: m.message,
425 media: None,
426 reply_markup: None,
427 entities: m.entities,
428 views: None,
429 forwards: None,
430 replies: None,
431 edit_date: None,
432 post_author: None,
433 grouped_id: None,
434 reactions: None,
435 restriction_reason: None,
436 ttl_period: None,
437 quick_reply_shortcut_id: None,
438 effect: None,
439 factcheck: None,
440 report_delivery_until_date: None,
441 paid_message_stars: None,
442 suggested_post: None,
443 from_boosts_applied: None,
444 paid_suggested_post_stars: false,
445 paid_suggested_post_ton: false,
446 schedule_repeat_period: None,
447 summary_from_language: None,
448 };
449 IncomingMessage { raw: tl::enums::Message::Message(msg) }
450}