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

Job Queue

The job queue lets you schedule delayed and recurring tasks. Common use cases include reminders, periodic notifications, and cleanup routines.

Setup

Enable the job-queue feature:

[dependencies]
rust-tg-bot = { version = "1.0.0-rc.1", features = ["job-queue"] }

Create a JobQueue and pass it to the application builder:

#![allow(unused)]
fn main() {
use std::sync::Arc;
use rust_tg_bot::ext::job_queue::JobQueue;
use rust_tg_bot::ext::prelude::ApplicationBuilder;

let jq = Arc::new(JobQueue::new());

let app = ApplicationBuilder::new()
    .token(token)
    .job_queue(Arc::clone(&jq))
    .build();
}

Scheduling a One-Shot Job

Schedule a job that fires once after a delay:

#![allow(unused)]
fn main() {
use std::time::Duration;
use rust_tg_bot::ext::job_queue::{JobCallbackFn, JobContext};

async fn set_timer(
    update: Arc<Update>,
    context: Context,
    timer_store: TimerStore,
) -> HandlerResult {
    let chat_id = update.effective_chat().unwrap().id;
    let seconds: u64 = 30; // from user input

    // Build the callback
    let bot = Arc::clone(context.bot());
    let alarm_callback: JobCallbackFn = Arc::new(move |ctx: JobContext| {
        let bot = Arc::clone(&bot);
        Box::pin(async move {
            let target_chat_id = ctx.chat_id.unwrap_or(0);
            if target_chat_id == 0 {
                return Ok(());
            }
            bot.send_message(target_chat_id, "BEEP! Timer is done!")
                .await
                .map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>)?;
            Ok(())
        })
    });

    // Schedule via the job queue
    let jq = context.job_queue.as_ref().expect("job_queue should be set");

    let job = jq
        .once(alarm_callback, Duration::from_secs(seconds))
        .name(format!("timer_{chat_id}"))
        .chat_id(chat_id)
        .start()
        .await;

    // Store the job ID for later cancellation
    timer_store.write().await.insert(chat_id, job.id);

    context.bot()
        .send_message(chat_id, &format!("Timer set for {seconds} seconds!"))
        .await
        .map_err(|e| HandlerError::Other(Box::new(e)))?;

    Ok(())
}
}

Job Builder Methods

The job builder returned by jq.once() supports these methods:

MethodDescription
.name(name)Human-readable name for the job
.chat_id(id)Associates a chat ID with the job (passed to the callback via JobContext)
.start()Schedules the job and returns a Job handle

Cancelling Jobs

Cancel a job by calling schedule_removal() on its handle:

#![allow(unused)]
fn main() {
async fn unset_timer(
    update: Arc<Update>,
    context: Context,
    timer_store: TimerStore,
) -> HandlerResult {
    let chat_id = update.effective_chat().unwrap().id;

    let jq = context.job_queue.as_ref().unwrap();

    let removed = {
        let mut store = timer_store.write().await;
        if let Some(old_job_id) = store.remove(&chat_id) {
            let jobs = jq.jobs().await;
            for job in jobs {
                if job.id == old_job_id {
                    job.schedule_removal();
                    break;
                }
            }
            true
        } else {
            false
        }
    };

    let reply = if removed {
        "Timer successfully cancelled!"
    } else {
        "You have no active timer."
    };

    context.bot()
        .send_message(chat_id, reply)
        .await
        .map_err(|e| HandlerError::Other(Box::new(e)))?;

    Ok(())
}
}

JobContext

The callback receives a JobContext with metadata about the job:

#![allow(unused)]
fn main() {
let callback: JobCallbackFn = Arc::new(|ctx: JobContext| {
    Box::pin(async move {
        let chat_id = ctx.chat_id.unwrap_or(0);
        // Use chat_id to send messages
        Ok(())
    })
});
}

Tracking Active Jobs

Use a shared store to map chat IDs to job IDs:

#![allow(unused)]
fn main() {
use std::collections::HashMap;
use rust_tg_bot::ext::prelude::{Arc, RwLock};

type TimerStore = Arc<RwLock<HashMap<i64, u64>>>;
}

Before scheduling a new job, cancel any existing one for the same chat:

#![allow(unused)]
fn main() {
{
    let store = timer_store.read().await;
    if let Some(&old_job_id) = store.get(&chat_id) {
        let jobs = jq.jobs().await;
        for job in jobs {
            if job.id == old_job_id {
                job.schedule_removal();
                break;
            }
        }
    }
}
}

Complete Timer Bot

See the timer_bot example in the repository for a full working implementation:

TELEGRAM_BOT_TOKEN="..." cargo run -p rust-tg-bot --example timer_bot --features job-queue

Commands:

  • /start – show usage
  • /set 30 – set a 30-second timer
  • /unset – cancel the active timer

Next Steps