1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
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
|
+
+
|
use crate::{
Arc,
Mutex,
core::FeedList,
};
use std::{
borrow::Cow,
fmt,
time::Duration,
};
use lazy_static::lazy_static;
use serde::{
Deserialize,
Serialize,
};
use regex::Regex;
use smol::Timer;
use stacked_errors::{
bail,
Result,
StackableErr,
};
use tgbot::{
|
| ︙ | | |
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
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
|
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
|
Message,
ParseMode,
SendMessage,
},
};
const CB_VERSION: u8 = 0;
lazy_static! {
pub static ref RE_CLOSING: Regex = Regex::new(r"</[ \t]*(pre|code)[ \t]*>").unwrap();
}
// validate input as being postable in preformatted block, all html tags are fine, except tags that
// break the block - </pre> and </code>, we don't need to escape anything else, as telegram manages
// that
pub fn validate (text: &str) -> Result<&str> {
if RE_CLOSING.is_match(text) {
bail!("Telegram closing tag found.");
} else {
Ok(text)
}
}
#[derive(Serialize, Deserialize, Debug)]
pub enum Callback {
// Edit one feed (version, name)
Edit(u8, String),
// List all feeds (version, name to show, page number)
List(u8, String, usize),
|
| ︙ | | |
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
|
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
|
-
+
|
}
}
if long {
kb.push(vec![
InlineKeyboardButton::for_callback_data("<<",
Callback::list("", if *page == 0 { *page } else { page - 1 } ).to_string()),
InlineKeyboardButton::for_callback_data(">>",
Callback::list("", page + 1).to_string()),
Callback::list("", page.saturating_add(1)).to_string()),
]);
}
InlineKeyboardMarkup::from(kb)
},
Callback::Menu(_) => {
let kb = vec![
vec![
|
| ︙ | | |
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
|
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
|
-
-
+
+
-
+
|
pub fn html_to_kb <'a, S> (text: S, to: ChatPeerId, kb: InlineKeyboardMarkup) -> MyMessage<'a>
where S: Into<Cow<'a, str>> {
let text = text.into();
MyMessage::HtmlToKb { text, to, kb }
}
fn req (&self, tg: &Tg) -> Result<SendMessage> {
Ok(match self {
fn req (&self, tg: &Tg) -> SendMessage {
match self {
MyMessage::Html { text } =>
SendMessage::new(tg.owner, text.as_ref())
.with_parse_mode(ParseMode::Html),
MyMessage::HtmlTo { text, to } =>
SendMessage::new(*to, text.as_ref())
.with_parse_mode(ParseMode::Html),
MyMessage::HtmlToKb { text, to, kb } =>
SendMessage::new(*to, text.as_ref())
.with_parse_mode(ParseMode::Html)
.with_reply_markup(kb.clone()),
})
}
}
}
#[derive(Clone)]
pub struct Tg {
pub me: Bot,
pub owner: ChatPeerId,
|
| ︙ | | |
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
|
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
|
-
+
|
}
/// Send a text message to a chat, using an optional target and parse mode.
///
/// # Returns
/// The sent `Message` on success.
pub async fn send (&self, msg: MyMessage<'_>) -> Result<Message> {
self.client.execute(msg.req(self)?).await.stack()
self.client.execute(msg.req(self)).await.stack()
}
pub async fn answer_cb (&self, id: String, text: String) -> Result<bool> {
self.client.execute(
AnswerCallbackQuery::new(id)
.with_text(text)
).await.stack()
|
| ︙ | | |
276
277
278
279
280
281
282
283
284
285
286
287
288
289
|
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
|
+
|
where O: Into<i64> {
Tg {
owner: ChatPeerId::from(owner.into()),
..self.clone()
}
}
// XXX Can loop indefinitely if API calls results retry_after, add max retries?
pub async fn update_message (&self, chat_id: i64, message_id: i64, text: String, feeds: &Arc<Mutex<FeedList>>, cb: Callback) -> Result<EditMessageResult> {
loop {
let req = EditMessageText::for_chat_message(chat_id, message_id, &text)
.with_reply_markup(get_kb(&cb, feeds).await.stack()?);
let res = self.client.execute(req).await;
match res {
Ok(res) => return Ok(res),
|
| ︙ | | |