Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Update Types

All Telegram events flow through stream_updates() as variants of the Update enum. Here is every variant, what it carries, and how to handle it.

#![allow(unused)]
fn main() {
use layer_client::update::Update;

let mut updates = client.stream_updates();
while let Some(update) = updates.next().await {
    match update {
        Update::NewMessage(msg)     => { /* ... */ }
        Update::MessageEdited(msg)  => { /* ... */ }
        Update::MessageDeleted(del) => { /* ... */ }
        Update::CallbackQuery(cb)   => { /* ... */ }
        Update::InlineQuery(iq)     => { /* ... */ }
        Update::InlineSend(is)      => { /* ... */ }
        Update::Raw(raw)            => { /* ... */ }
        _ => {}
    }
}
}

NewMessage

Fires for every new message received in any chat the account participates in.

#![allow(unused)]
fn main() {
Update::NewMessage(msg) => {
    // Filter out your own sent messages
    if msg.outgoing() { return; }

    let text     = msg.text().unwrap_or("");
    let msg_id   = msg.id();
    let date     = msg.date_utc();  // chrono::DateTime<Utc>
    let is_post  = msg.post();      // from a channel
    let has_media = msg.media().is_some();

    println!("[{msg_id}] {text}");
}
}

Key accessors on IncomingMessage:

MethodReturnsNotes
id()i32Unique message ID within the chat
text()Option<&str>Plain text content
peer_id()Option<&Peer>The chat it was sent in
sender_id()Option<&Peer>Who sent it
outgoing()boolSent by the logged-in account
date()i32Unix timestamp
date_utc()Option<DateTime<Utc>>Parsed chrono datetime
edit_date()Option<i32>When last edited
media()Option<&MessageMedia>Attached media
entities()Option<&Vec<MessageEntity>>Formatting regions
mentioned()boolAccount was @mentioned
silent()boolSent without notification
pinned()boolA pin notification
post()boolFrom a channel
noforwards()boolCannot be forwarded
reply_to_message_id()Option<i32>ID of replied-to message
reply_markup()Option<&ReplyMarkup>Keyboard attached to this message
forward_count()Option<i32>How many times forwarded
view_count()Option<i32>View count (channels)
reply_count()Option<i32>Comment count
grouped_id()Option<i64>Album group ID
forward_header()Option<&MessageFwdHeader>Forward origin info

MessageEdited

Same structure as NewMessage — carries the updated version of the message.

#![allow(unused)]
fn main() {
Update::MessageEdited(msg) => {
    println!("Message {} was edited: {}", msg.id(), msg.text().unwrap_or(""));
    if let Some(edit_time) = msg.edit_date_utc() {
        println!("Edited at: {edit_time}");
    }
}
}

MessageDeleted

Contains only message IDs — content is gone by the time this fires.

#![allow(unused)]
fn main() {
Update::MessageDeleted(del) => {
    println!("Deleted {} messages", del.messages().len());
    println!("IDs: {:?}", del.messages());

    // For channel deletions, the channel ID is available
    if let Some(ch_id) = del.channel_id() {
        println!("In channel: {ch_id}");
    }
}
}

CallbackQuery

Fired when a user presses an inline keyboard button on a bot message.

#![allow(unused)]
fn main() {
Update::CallbackQuery(cb) => {
    let data    = cb.data().unwrap_or("");
    let qid     = cb.query_id;
    let from    = cb.sender_id();
    let msg_id  = cb.msg_id;

    match data {
        "action:confirm" => {
            // answer() shows a brief toast to the user
            cb.answer(&client, "✅ Confirmed!").await?;
        }
        "action:cancel" => {
            // answer_alert() shows a modal popup
            cb.answer_alert(&client, "❌ Cancelled").await?;
        }
        _ => {
            // Must always answer — otherwise spinner shows forever
            client.answer_callback_query(qid, None, false).await?;
        }
    }
}
}

WARNING: You must call answer_callback_query for every CallbackQuery. If you don’t, the button shows a loading spinner to the user indefinitely.


InlineQuery

Fired when a user types @yourbot something in any chat.

#![allow(unused)]
fn main() {
Update::InlineQuery(iq) => {
    let query  = iq.query();   // the typed text
    let qid    = iq.query_id;
    let offset = iq.offset();  // for pagination

    let results = vec![
        make_article("1", "Result title", "Result text"),
    ];

    // cache_time = seconds to cache the results (0 = no cache)
    // is_personal = true if results differ per user
    // next_offset = Some("page2") for pagination
    client.answer_inline_query(qid, results, 300, false, None).await?;
}
}

InlineSend

Fired when the user selects one of your inline results and it gets sent.

#![allow(unused)]
fn main() {
Update::InlineSend(is) => {
    println!("User chose result id: {}", is.id());
    // Useful for analytics or follow-up actions
}
}

Raw

Any TL update variant not mapped to one of the above. Carries the constructor ID for identification.

#![allow(unused)]
fn main() {
Update::Raw(raw) => {
    println!("Unhandled update: 0x{:08x}", raw.constructor_id);

    // You can decode it manually using the TL types
    // if you know the constructor:
    // let upd: tl::enums::Update = ...;
}
}

Use this as a catch-all for new update types as the Telegram API evolves, or to handle specialized updates like updateBotChatInviteRequester, updateBotStopped, etc.


Concurrent handling pattern

#![allow(unused)]
fn main() {
use std::sync::Arc;

let client = Arc::new(client);
let mut updates = client.stream_updates();

while let Some(update) = updates.next().await {
    let c = client.clone();
    tokio::spawn(async move {
        if let Err(e) = handle(update, &c).await {
            eprintln!("Error: {e}");
        }
    });
}
}

This ensures slow handlers don’t block the receive loop.