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