This repository was archived by the owner on Feb 10, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 163
Expand file tree
/
Copy pathinline-pagination.rs
More file actions
168 lines (147 loc) · 5.53 KB
/
inline-pagination.rs
File metadata and controls
168 lines (147 loc) · 5.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
//! Example to showcase how to achieve pagination with inline buttons.
//!
//! The `TG_ID` and `TG_HASH` environment variables must be set (learn how to do it for
//! [Windows](https://ss64.com/nt/set.html) or [Linux](https://ss64.com/bash/export.html))
//! to Telegram's API ID and API hash respectively.
//!
//! Then, run it as:
//!
//! ```sh
//! cargo run --example echo -- BOT_TOKEN
//! ```
//!
//! In order to achieve pagination, the button data must contain enough information for the code
//! to determine which "offset" should it use when querying additional data.
//!
//! For this example, the button contains the last two values of the fibonacci sequence where it
//! last left off, separated by a comma. This way, the code can resume.
//!
//! If there is no comma, then there is a single number, which we use to know what was clicked.
//!
//! If it's the special string "done", then we know we've reached the end (there is a limit to
//! how much data a button's payload can contain, and to keep it simple, we're storing it inline
//! in decimal, so the numbers can't get too large).
use std::env;
use std::sync::Arc;
use grammers_client::Client;
use grammers_client::message::{Button, InputMessage, ReplyMarkup};
use grammers_client::update::Update;
use grammers_mtsender::SenderPool;
use grammers_session::storages::SqliteSession;
use simple_logger::SimpleLogger;
use tokio::{runtime, task};
type Result = std::result::Result<(), Box<dyn std::error::Error>>;
const SESSION_FILE: &str = "inline-pagination.session";
const NUMBERS_PER_PAGE: usize = 4;
// https://core.telegram.org/bots/api#inlinekeyboardbutton
const MAX_PAYLOAD_DATA_LEN: usize = 64;
/// Generate the inline keyboard reply markup with a few more numbers from the sequence.
fn fib_markup(mut a: u128, mut b: u128) -> ReplyMarkup {
let mut rows = Vec::with_capacity(NUMBERS_PER_PAGE + 1);
for _ in 0..NUMBERS_PER_PAGE {
let text = a.to_string();
rows.push(vec![Button::data(&text, text.as_bytes())]);
let bb = b;
b += a;
a = bb;
}
let next = format!("{a},{b}");
if next.len() > MAX_PAYLOAD_DATA_LEN {
rows.push(vec![Button::data("I'm satisfied!!", b"done".to_vec())]);
} else {
rows.push(vec![
Button::data("Restart!", b"0,1".to_vec()),
Button::data("More!", format!("{a},{b}").into_bytes()),
]);
}
ReplyMarkup::from_buttons(&rows)
}
async fn handle_update(_client: Client, update: Update) -> Result {
match update {
Update::NewMessage(message) if message.text() == "/start" => {
message
.respond(
InputMessage::new()
.text("Here's a fibonacci")
.reply_markup(fib_markup(0, 1)),
)
.await?;
}
Update::CallbackQuery(query) => {
let data = std::str::from_utf8(query.data()).unwrap();
println!("Got callback query for {data}");
// First check special-case.
if data == "done" {
query.answer().edit("Glad you liked it 👍").await?;
return Ok(());
}
// Otherwise get the stored number(s).
let mut parts = data.split(',');
let a = parts.next().unwrap().parse::<u128>().unwrap();
if let Some(b) = parts.next() {
let os = (0..b.len()).map(|_| 'o').collect::<String>();
let b = b.parse::<u128>().unwrap();
query
.answer()
.edit(
InputMessage::from(format!("S{os} much fibonacci 🔢"))
.reply_markup(fib_markup(a, b)),
)
.await?;
} else if a % 2 == 0 {
query.answer().text("Even that's a number!").send().await?;
} else {
query.answer().alert("That's odd…").send().await?;
}
}
_ => {}
}
Ok(())
}
async fn async_main() -> Result {
SimpleLogger::new()
.with_level(log::LevelFilter::Debug)
.init()
.unwrap();
let api_id = env!("TG_ID").parse().expect("TG_ID invalid");
let token = env::args().nth(1).expect("token missing");
let session = Arc::new(SqliteSession::open(SESSION_FILE).await?);
let SenderPool {
runner,
handle,
updates,
} = SenderPool::new(Arc::clone(&session), api_id);
let client = Client::new(handle);
let _ = tokio::spawn(runner.run());
if !client.is_authorized().await? {
println!("Signing in...");
client.bot_sign_in(&token, env!("TG_HASH")).await?;
println!("Signed in!");
}
println!("Waiting for messages...");
let mut updates = client.stream_updates(updates, Default::default()).await;
loop {
tokio::select! {
_ = tokio::signal::ctrl_c() => break,
update = updates.next() => {
let update = update?;
let handle = client.clone();
task::spawn(async move {
match handle_update(handle, update).await {
Ok(_) => {}
Err(e) => eprintln!("Error handling updates!: {e}"),
}
});
}
}
}
// `runner.run()`'s task will be dropped (and disconnect occur) once the runtime exits.
Ok(())
}
fn main() -> Result {
runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(async_main())
}