Lines of
src/main.rs
from check-in 39ee25f5c3
that are changed by the sequence of edits moving toward
check-in f322efafd9:
1: use std::collections::BTreeMap;
2:
3: use config;
4:
5: use tokio;
6:
7: use rss;
8:
9: use chrono::DateTime;
10:
11: use regex::Regex;
12:
13: use telegram_bot::*;
14: use tokio::stream::StreamExt;
15:
16: use sqlx::postgres::PgPoolOptions;
17: use sqlx::Row;
18: use sqlx::Done; // .rows_affected()
19:
20: #[macro_use]
21: extern crate lazy_static;
22:
39ee25f5c3 2020-11-29 23: use anyhow::{anyhow, Context, Result};
24:
25: #[derive(Clone)]
26: struct Core {
27: owner: i64,
28: api_key: String,
29: owner_chat: UserId,
30: tg: telegram_bot::Api,
31: my: User,
32: pool: sqlx::Pool<sqlx::Postgres>,
33: }
34:
35: impl Core {
36: async fn new(settings: config::Config) -> Result<Core> {
37: let owner = settings.get_int("owner")?;
38: let api_key = settings.get_str("api_key")?;
39: let tg = Api::new(&api_key);
40: let core = Core {
41: owner: owner,
42: api_key: api_key.clone(),
43: my: tg.send(telegram_bot::GetMe).await?,
44: tg: tg,
45: owner_chat: UserId::new(owner),
46: pool: PgPoolOptions::new()
47: .max_connections(5)
48: .connect_timeout(std::time::Duration::new(300, 0))
49: .idle_timeout(std::time::Duration::new(60, 0))
50: .connect_lazy(&settings.get_str("pg")?)?,
51: };
52: let clone = core.clone();
53: tokio::spawn(async move {
54: if let Err(err) = &clone.autofetch().await {
39ee25f5c3 2020-11-29 55: if let Err(err) = clone.debug(&format!("{:?}", err)) {
56: eprintln!("Autofetch error: {}", err);
57: };
58: }
59: });
60: Ok(core)
61: }
62:
63: fn stream(&self) -> telegram_bot::UpdatesStream {
64: self.tg.stream()
65: }
66:
67: fn debug(&self, msg: &str) -> Result<()> {
68: self.tg.spawn(SendMessage::new(self.owner_chat, msg));
69: Ok(())
70: }
71:
72: async fn check<S>(&self, id: &i32, owner: S, real: bool) -> Result<()>
73: where S: Into<i64> {
74: let owner: i64 = owner.into();
75: let mut conn = self.pool.acquire().await
39ee25f5c3 2020-11-29 76: .with_context(|| format!("π Query queue fetch conn:\n{:?}", &self.pool))?;
77: let row = sqlx::query("select source_id, channel_id, url, iv_hash, owner from rsstg_source where source_id = $1 and owner = $2")
78: .bind(id)
79: .bind(owner)
80: .fetch_one(&mut conn).await
39ee25f5c3 2020-11-29 81: .with_context(|| format!("π Query source:\n{:?}", &self.pool))?;
82: drop(conn);
83: let channel_id: i64 = row.try_get("channel_id")?;
84: let destination = match real {
85: true => UserId::new(channel_id),
86: false => UserId::new(row.try_get("owner")?),
87: };
88: let url: &str = row.try_get("url")?;
89: let mut this_fetch: Option<DateTime<chrono::FixedOffset>> = None;
90: let iv_hash: Option<&str> = row.try_get("iv_hash")?;
91: let mut posts: BTreeMap<DateTime<chrono::FixedOffset>, String> = BTreeMap::new();
92: let feed = rss::Channel::from_url(url)
39ee25f5c3 2020-11-29 93: .with_context(|| format!("π Problem opening feed url:\n{}", &url))?;
94: for item in feed.items() {
95: let date = match item.pub_date() {
96: Some(feed_date) => DateTime::parse_from_rfc2822(feed_date),
97: None => DateTime::parse_from_rfc3339(&item.dublin_core_ext().unwrap().dates()[0]),
98: }?;
99: let url = item.link().unwrap().to_string();
100: posts.insert(date.clone(), url.clone());
101: };
102: for (date, url) in posts.iter() {
103: let mut conn = self.pool.acquire().await
39ee25f5c3 2020-11-29 104: .with_context(|| format!("π Check post fetch conn:\n{:?}", &self.pool))?;
105: let row = sqlx::query("select exists(select true from rsstg_post where url = $1 and source_id = $2) as exists;")
106: .bind(&url)
107: .bind(id)
108: .fetch_one(&mut conn).await
39ee25f5c3 2020-11-29 109: .with_context(|| format!("π Check post:\n{:?}", &conn))?;
110: let exists: bool = row.try_get("exists")?;
111: if ! exists {
112: if this_fetch == None || *date > this_fetch.unwrap() {
113: this_fetch = Some(*date);
114: };
115: self.tg.send( match iv_hash {
116: Some(x) => SendMessage::new(destination, format!("<a href=\"https://t.me/iv?url={}&rhash={}\"> </a>{0}", url, x)),
117: None => SendMessage::new(destination, format!("{}", url)),
118: }.parse_mode(types::ParseMode::Html)).await
39ee25f5c3 2020-11-29 119: .context("π Can't post message:")?;
120: sqlx::query("insert into rsstg_post (source_id, posted, url) values ($1, $2, $3);")
121: .bind(id)
122: .bind(date)
123: .bind(url)
124: .execute(&mut conn).await
39ee25f5c3 2020-11-29 125: .with_context(|| format!("πRecord post:\n{:?}", &conn))?;
126: drop(conn);
127: tokio::time::delay_for(std::time::Duration::new(4, 0)).await;
128: };
129: };
130: posts.clear();
131: let mut conn = self.pool.acquire().await
39ee25f5c3 2020-11-29 132: .with_context(|| format!("π Update scrape fetch conn:\n{:?}", &self.pool))?;
133: sqlx::query("update rsstg_source set last_scrape = now() where source_id = $1;")
134: .bind(id)
135: .execute(&mut conn).await
39ee25f5c3 2020-11-29 136: .with_context(|| format!("π Update scrape:\n{:?}", &conn))?;
137: Ok(())
138: }
139:
39ee25f5c3 2020-11-29 140: async fn clean<S>(&self, source_id: &i32, id: S) -> Result<String>
39ee25f5c3 2020-11-29 141: where S: Into<i64> {
39ee25f5c3 2020-11-29 142: let id: i64 = id.into();
39ee25f5c3 2020-11-29 143: let mut conn = self.pool.acquire().await
39ee25f5c3 2020-11-29 144: .with_context(|| format!("π Clean fetch conn:\n{:?}", &self.pool))?;
145: match sqlx::query("delete from rsstg_post p using rsstg_source s where p.source_id = $1 and owner = $2 and p.source_id = s.source_id;")
146: .bind(source_id)
39ee25f5c3 2020-11-29 147: .bind(id)
148: .execute(&mut conn).await
39ee25f5c3 2020-11-29 149: .with_context(|| format!("π Clean seen posts:\n{:?}", &self.pool))?
150: .rows_affected() {
151: 0 => { Ok("No data found found\\.".to_string()) },
152: x => { Ok(format!("{} posts purged\\.", x)) },
153: }
154: }
155:
39ee25f5c3 2020-11-29 156: async fn enable<S>(&self, source_id: &i32, id: S) -> Result<&str>
157: where S: Into<i64> {
39ee25f5c3 2020-11-29 158: let id: i64 = id.into();
159: let mut conn = self.pool.acquire().await
39ee25f5c3 2020-11-29 160: .with_context(|| format!("π Enable fetch conn:\n{:?}", &self.pool))?;
161: match sqlx::query("update rsstg_source set enabled = true where source_id = $1 and owner = $2")
162: .bind(source_id)
39ee25f5c3 2020-11-29 163: .bind(id)
39ee25f5c3 2020-11-29 164: .execute(&mut conn).await
39ee25f5c3 2020-11-29 165: .with_context(|| format!("π Enable source:\n{:?}", &self.pool))?
39ee25f5c3 2020-11-29 166: .rows_affected() {
39ee25f5c3 2020-11-29 167: 1 => { Ok("Source disabled\\.") },
39ee25f5c3 2020-11-29 168: 0 => { Ok("Source not found\\.") },
39ee25f5c3 2020-11-29 169: _ => { Err(anyhow!("Database error.")) },
39ee25f5c3 2020-11-29 170: }
39ee25f5c3 2020-11-29 171: }
39ee25f5c3 2020-11-29 172:
39ee25f5c3 2020-11-29 173: async fn disable<S>(&self, source_id: &i32, id: S) -> Result<&str>
39ee25f5c3 2020-11-29 174: where S: Into<i64> {
39ee25f5c3 2020-11-29 175: let id: i64 = id.into();
39ee25f5c3 2020-11-29 176: let mut conn = self.pool.acquire().await
39ee25f5c3 2020-11-29 177: .with_context(|| format!("π Disable fetch conn:\n{:?}", &self.pool))?;
39ee25f5c3 2020-11-29 178: match sqlx::query("update rsstg_source set enabled = false where source_id = $1 and owner = $2")
39ee25f5c3 2020-11-29 179: .bind(source_id)
39ee25f5c3 2020-11-29 180: .bind(id)
181: .execute(&mut conn).await
39ee25f5c3 2020-11-29 182: .with_context(|| format!("π Disable source:\n{:?}", &self.pool))?
183: .rows_affected() {
184: 1 => { Ok("Source disabled\\.") },
185: 0 => { Ok("Source not found\\.") },
186: _ => { Err(anyhow!("Database error.")) },
187: }
188: }
189:
190: async fn autofetch(&self) -> Result<()> {
191: let mut delay = chrono::Duration::minutes(5);
192: let mut now;
193: loop {
194: let mut conn = self.pool.acquire().await
39ee25f5c3 2020-11-29 195: .with_context(|| format!("π Autofetch fetch conn:\n{:?}", &self.pool))?;
196: now = chrono::Local::now();
39ee25f5c3 2020-11-29 197: let mut queue = sqlx::query("select source_id, next_fetch, owner from rsstg_order natural left join rsstg_source natural left join rsstg_channel where next_fetch < now();")
198: .fetch_all(&mut conn).await?;
199: for row in queue.iter() {
200: let source_id: i32 = row.try_get("source_id")?;
201: let owner: i64 = row.try_get("owner")?;
202: let next_fetch: DateTime<chrono::Local> = row.try_get("next_fetch")?;
203: if next_fetch < now {
204: sqlx::query("update rsstg_source set last_scrape = now() + interval '1 hour' where source_id = $1;")
205: .bind(source_id)
206: .execute(&mut conn).await
39ee25f5c3 2020-11-29 207: .with_context(|| format!("π Lock source:\n\n{:?}", &self.pool))?;
208: let clone = self.clone();
209: tokio::spawn(async move {
210: if let Err(err) = clone.check(&source_id, owner, true).await {
39ee25f5c3 2020-11-29 211: if let Err(err) = clone.debug(&format!("{:?}", err)) {
212: eprintln!("Check error: {}", err);
213: };
214: };
215: });
216: } else {
217: if next_fetch - now < delay {
218: delay = next_fetch - now;
219: }
220: }
221: };
222: queue.clear();
223: tokio::time::delay_for(delay.to_std()?).await;
224: delay = chrono::Duration::minutes(5);
225: }
226: }
227:
39ee25f5c3 2020-11-29 228: async fn list(&self, id: telegram_bot::UserId) -> Result<Vec<String>> {
39ee25f5c3 2020-11-29 229: let id = i64::from(id);
230: let mut reply = vec![];
231: let mut conn = self.pool.acquire().await
39ee25f5c3 2020-11-29 232: .with_context(|| format!("π List fetch conn:\n{:?}", &self.pool))?;
233: reply.push("Channels:".to_string());
39ee25f5c3 2020-11-29 234: let rows = sqlx::query("select source_id, username, enabled, url, iv_hash from rsstg_source left join rsstg_channel using (channel_id) where owner = $1 order by source_id")
39ee25f5c3 2020-11-29 235: .bind(id)
236: .fetch_all(&mut conn).await?;
237: for row in rows.iter() {
238: let source_id: i32 = row.try_get("source_id")?;
39ee25f5c3 2020-11-29 239: let username: &str = row.try_get("username")?;
240: let enabled: bool = row.try_get("enabled")?;
241: let url: &str = row.try_get("url")?;
242: let iv_hash: Option<&str> = row.try_get("iv_hash")?;
243: reply.push(format!("\n\\#οΈβ£ {} \\*οΈβ£ `{}` {}\nπ `{}`", source_id, username,
244: match enabled {
245: true => "π enabled",
246: false => "β disabled",
247: }, url));
248: if let Some(hash) = iv_hash {
249: reply.push(format!("IV `{}`", hash));
250: }
251: };
252: Ok(reply)
253: }
254: }
255:
256: #[tokio::main]
257: async fn main() -> Result<()> {
258: let mut settings = config::Config::default();
259: settings.merge(config::File::with_name("rsstg"))?;
260:
261: let core = Core::new(settings).await?;
262:
263: let mut stream = core.stream();
264:
265: while let Some(update) = stream.next().await {
266: if let Err(err) = handle(update?, &core).await {
39ee25f5c3 2020-11-29 267: core.debug(&format!("{:?}", err))?;
268: };
269: }
270:
271: Ok(())
272: }
273:
274: async fn handle(update: telegram_bot::Update, core: &Core) -> Result<()> {
275: lazy_static! {
276: static ref RE_USERNAME: Regex = Regex::new(r"^@[a-zA-Z][a-zA-Z0-9_]+$").unwrap();
277: static ref RE_LINK: Regex = Regex::new(r"^https?://[a-zA-Z.0-9-]+/[-_a-zA-Z.0-9/?=]+$").unwrap();
278: static ref RE_IV_HASH: Regex = Regex::new(r"^[a-f0-9]{14}$").unwrap();
279: }
280:
281: match update.kind {
282: UpdateKind::Message(message) => {
283: let mut reply: Vec<String> = vec![];
284: match message.kind {
285: MessageKind::Text { ref data, .. } => {
286: let mut words = data.split_whitespace();
287: let cmd = words.next().unwrap();
288: match cmd {
289:
290: // start
291:
292: "/start" => {
293: reply.push("We are open\\. Probably\\. Visit [channel](https://t.me/rsstg_bot_help/3) for details\\.".to_string());
294: },
295:
296: // list
297:
298: "/list" => {
299: reply.append(&mut core.list(message.from.id).await?);
300: },
301:
302: // add
303:
304: "/add" | "/update" => {
39ee25f5c3 2020-11-29 305: let mut source_id: i32 = 0;
39ee25f5c3 2020-11-29 306: if cmd == "/update" {
39ee25f5c3 2020-11-29 307: source_id = words.next().unwrap().parse::<i32>()?;
39ee25f5c3 2020-11-29 308: }
39ee25f5c3 2020-11-29 309: let (channel, url, iv_hash) = (words.next().unwrap(), words.next().unwrap(), words.next());
39ee25f5c3 2020-11-29 310: let ok_link = RE_LINK.is_match(&url);
39ee25f5c3 2020-11-29 311: let ok_hash = match iv_hash {
39ee25f5c3 2020-11-29 312: Some(hash) => RE_IV_HASH.is_match(&hash),
39ee25f5c3 2020-11-29 313: None => true,
39ee25f5c3 2020-11-29 314: };
39ee25f5c3 2020-11-29 315: if ! ok_link {
39ee25f5c3 2020-11-29 316: reply.push("Link should be link to atom/rss feed, something like \"https://domain/path\"\\.".to_string());
39ee25f5c3 2020-11-29 317: core.debug(&format!("Url: {:?}", &url))?;
39ee25f5c3 2020-11-29 318: }
39ee25f5c3 2020-11-29 319: if ! ok_hash {
39ee25f5c3 2020-11-29 320: reply.push("IV hash should be 14 hex digits.".to_string());
39ee25f5c3 2020-11-29 321: core.debug(&format!("IV: {:?}", &iv_hash))?;
39ee25f5c3 2020-11-29 322: }
39ee25f5c3 2020-11-29 323: if ok_link && ok_hash {
39ee25f5c3 2020-11-29 324: let chan: Option<i64> = match sqlx::query("select channel_id from rsstg_channel where username = $1")
39ee25f5c3 2020-11-29 325: .bind(channel)
39ee25f5c3 2020-11-29 326: .fetch_one(&core.pool).await {
39ee25f5c3 2020-11-29 327: Ok(chan) => Some(chan.try_get("channel_id")?),
39ee25f5c3 2020-11-29 328: Err(sqlx::Error::RowNotFound) => {
39ee25f5c3 2020-11-29 329: let chan_id = i64::from(core.tg.send(telegram_bot::GetChat::new(telegram_bot::types::ChatRef::ChannelUsername(channel.to_string()))).await?.id());
39ee25f5c3 2020-11-29 330: sqlx::query("insert into rsstg_channel (channel_id, username) values ($1, $2);")
39ee25f5c3 2020-11-29 331: .bind(chan_id)
39ee25f5c3 2020-11-29 332: .bind(channel)
39ee25f5c3 2020-11-29 333: .execute(&core.pool).await?;
39ee25f5c3 2020-11-29 334: Some(chan_id)
39ee25f5c3 2020-11-29 335: },
39ee25f5c3 2020-11-29 336: Err(err) => {
39ee25f5c3 2020-11-29 337: reply.push("Sorry, unknown error\\.".to_string());
39ee25f5c3 2020-11-29 338: core.debug(&format!("Sorry, unknown error:\n{:#?}\n", err))?;
39ee25f5c3 2020-11-29 339: None
39ee25f5c3 2020-11-29 340: },
39ee25f5c3 2020-11-29 341: };
39ee25f5c3 2020-11-29 342: if let Some(chan) = chan {
39ee25f5c3 2020-11-29 343: match if cmd == "/update" {
39ee25f5c3 2020-11-29 344: sqlx::query("update rsstg_source set channel_id = $2, url = $3, iv_hash = $4, owner = $4 where source_id = $1").bind(source_id)
39ee25f5c3 2020-11-29 345: } else {
39ee25f5c3 2020-11-29 346: sqlx::query("insert into rsstg_source (channel_id, url, iv_hash, owner) values ($1, $2, $3, $4)")
39ee25f5c3 2020-11-29 347: }
39ee25f5c3 2020-11-29 348: .bind(chan)
39ee25f5c3 2020-11-29 349: .bind(url)
39ee25f5c3 2020-11-29 350: .bind(iv_hash)
39ee25f5c3 2020-11-29 351: .bind(i64::from(message.from.id))
39ee25f5c3 2020-11-29 352: .execute(&core.pool).await {
39ee25f5c3 2020-11-29 353: Ok(_) => reply.push("Channel added\\.".to_string()),
39ee25f5c3 2020-11-29 354: Err(sqlx::Error::Database(err)) => {
39ee25f5c3 2020-11-29 355: match err.downcast::<sqlx::postgres::PgDatabaseError>().routine() {
39ee25f5c3 2020-11-29 356: Some("_bt_check_unique", ) => {
39ee25f5c3 2020-11-29 357: reply.push("Duplicate key\\.".to_string());
39ee25f5c3 2020-11-29 358: },
39ee25f5c3 2020-11-29 359: Some(_) => {
39ee25f5c3 2020-11-29 360: reply.push("Database error\\.".to_string());
39ee25f5c3 2020-11-29 361: },
39ee25f5c3 2020-11-29 362: None => {
39ee25f5c3 2020-11-29 363: reply.push("No database error extracted\\.".to_string());
39ee25f5c3 2020-11-29 364: },
39ee25f5c3 2020-11-29 365: };
39ee25f5c3 2020-11-29 366: },
39ee25f5c3 2020-11-29 367: Err(err) => {
39ee25f5c3 2020-11-29 368: reply.push("Sorry, unknown error\\.".to_string());
39ee25f5c3 2020-11-29 369: core.debug(&format!("Sorry, unknown error:\n{:#?}\n", err))?;
39ee25f5c3 2020-11-29 370: },
39ee25f5c3 2020-11-29 371: };
39ee25f5c3 2020-11-29 372: };
39ee25f5c3 2020-11-29 373: };
39ee25f5c3 2020-11-29 374: },
39ee25f5c3 2020-11-29 375:
39ee25f5c3 2020-11-29 376: // addchan
39ee25f5c3 2020-11-29 377:
39ee25f5c3 2020-11-29 378: "/addchan" => {
39ee25f5c3 2020-11-29 379: let channel = words.next().unwrap();
39ee25f5c3 2020-11-29 380: if ! RE_USERNAME.is_match(&channel) {
39ee25f5c3 2020-11-29 381: reply.push("Usernames should be something like \"@\\[a\\-zA\\-Z]\\[a\\-zA\\-Z0\\-9\\_]+\", aren't they?".to_string());
39ee25f5c3 2020-11-29 382: } else {
39ee25f5c3 2020-11-29 383: let chan: Option<i64> = match sqlx::query("select channel_id from rsstg_channel where username = $1")
39ee25f5c3 2020-11-29 384: .bind(channel)
39ee25f5c3 2020-11-29 385: .fetch_one(&core.pool).await {
39ee25f5c3 2020-11-29 386: Ok(chan) => Some(chan.try_get("channel_id")?),
39ee25f5c3 2020-11-29 387: Err(sqlx::Error::RowNotFound) => None,
39ee25f5c3 2020-11-29 388: Err(err) => {
39ee25f5c3 2020-11-29 389: reply.push("Sorry, unknown error\\.".to_string());
39ee25f5c3 2020-11-29 390: core.debug(&format!("Sorry, unknown error:\n{:#?}", err))?;
39ee25f5c3 2020-11-29 391: None
39ee25f5c3 2020-11-29 392: },
39ee25f5c3 2020-11-29 393: };
39ee25f5c3 2020-11-29 394: match chan {
39ee25f5c3 2020-11-29 395: Some(chan) => {
39ee25f5c3 2020-11-29 396: let new_chan = core.tg.send(telegram_bot::GetChat::new(telegram_bot::types::ChatId::new(chan))).await?;
39ee25f5c3 2020-11-29 397: if i64::from(new_chan.id()) == chan {
39ee25f5c3 2020-11-29 398: reply.push("I already know that channel\\.".to_string());
39ee25f5c3 2020-11-29 399: } else {
39ee25f5c3 2020-11-29 400: reply.push("Hmm, channel has changed⦠I'll fix it later\\.".to_string());
39ee25f5c3 2020-11-29 401: };
39ee25f5c3 2020-11-29 402: },
39ee25f5c3 2020-11-29 403: None => {
39ee25f5c3 2020-11-29 404: match core.tg.send(telegram_bot::GetChatAdministrators::new(telegram_bot::types::ChatRef::ChannelUsername(channel.to_string()))).await {
39ee25f5c3 2020-11-29 405: Ok(chan_adm) => {
39ee25f5c3 2020-11-29 406: let (mut me, mut user) = (false, false);
39ee25f5c3 2020-11-29 407: for admin in &chan_adm {
39ee25f5c3 2020-11-29 408: if admin.user.id == core.my.id {
39ee25f5c3 2020-11-29 409: me = true;
39ee25f5c3 2020-11-29 410: };
39ee25f5c3 2020-11-29 411: if admin.user.id == message.from.id {
39ee25f5c3 2020-11-29 412: user = true;
39ee25f5c3 2020-11-29 413: };
39ee25f5c3 2020-11-29 414: };
39ee25f5c3 2020-11-29 415: if ! me { reply.push("I need to be admin on that channel\\.".to_string()); };
39ee25f5c3 2020-11-29 416: if ! user { reply.push("You should be admin on that channel\\.".to_string()); };
39ee25f5c3 2020-11-29 417: if me && user {
39ee25f5c3 2020-11-29 418: let chan_id = core.tg.send(telegram_bot::GetChat::new(telegram_bot::types::ChatRef::ChannelUsername(channel.to_string()))).await?;
39ee25f5c3 2020-11-29 419: sqlx::query("insert into rsstg_channel (channel_id, username) values ($1, $2);")
39ee25f5c3 2020-11-29 420: .bind(i64::from(chan_id.id()))
39ee25f5c3 2020-11-29 421: .bind(channel)
39ee25f5c3 2020-11-29 422: .execute(&core.pool).await?;
39ee25f5c3 2020-11-29 423: reply.push("Good, I know that channel now\\.\n".to_string());
39ee25f5c3 2020-11-29 424: };
39ee25f5c3 2020-11-29 425: },
39ee25f5c3 2020-11-29 426: Err(_) => {
39ee25f5c3 2020-11-29 427: reply.push("Sorry, I have no access to that chat\\.".to_string());
39ee25f5c3 2020-11-29 428: },
39ee25f5c3 2020-11-29 429: };
39ee25f5c3 2020-11-29 430: },
39ee25f5c3 2020-11-29 431: };
39ee25f5c3 2020-11-29 432: };
433: },
434:
435: // check
436:
437: "/check" => {
438: match &words.next().unwrap().parse::<i32>() {
439: Err(err) => {
440: reply.push(format!("I need a number\\.\n{}", &err));
441: },
442: Ok(number) => {
443: core.check(&number, message.from.id, false).await
39ee25f5c3 2020-11-29 444: .context("π Channel check failed.")?;
445: },
446: };
447: },
448:
449: // clean
450:
451: "/clean" => {
452: match &words.next().unwrap().parse::<i32>() {
453: Err(err) => {
454: reply.push(format!("I need a number\\.\n{}", &err));
455: },
456: Ok(number) => {
457: let result = core.clean(&number, message.from.id).await?;
458: reply.push(result.to_string());
459: },
460: };
461: },
462:
463: // enable
464:
465: "/enable" => {
466: match &words.next().unwrap().parse::<i32>() {
467: Err(err) => {
468: reply.push(format!("I need a number\\.\n{}", &err));
469: },
470: Ok(number) => {
471: let result = core.enable(&number, message.from.id).await?;
472: reply.push(result.to_string());
473: },
474: };
475: },
476:
477: // disable
478:
479: "/disable" => {
480: match &words.next().unwrap().parse::<i32>() {
481: Err(err) => {
482: reply.push(format!("I need a number\\.\n{}", &err));
483: },
484: Ok(number) => {
485: let result = core.disable(&number, message.from.id).await?;
486: reply.push(result.to_string());
487: },
488: };
489: },
490:
491: _ => {
492: },
493: };
494: },
495: _ => {
496: },
497: };
498:
499: if reply.len() > 0 {
500: if let Err(err) = core.tg.send(message.text_reply(reply.join("\n")).parse_mode(types::ParseMode::MarkdownV2)).await {
501: dbg!(reply.join("\n"));
502: println!("{}", err);
503: };
504: };
505: },
506: _ => {},
507: };
508:
509: Ok(())
510: }