Check-in [a7f91033c0]
Logged in as anonymous
Overview
Comment:make /list work again, more fixes
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: a7f91033c0168c03ac5daca37b2aa3884a6a7d59113aa25d5ebf192990145778
User & Date: arcade on 2021-11-13 10:32:19.032
Other Links: manifest | tags
Context
2021-11-13
10:50
0.2.1: even more errors now can reach users check-in: 4632d20d39 user: arcade tags: trunk
10:32
make /list work again, more fixes check-in: a7f91033c0 user: arcade tags: trunk
06:58
0.2.0: huge inner revamp, I guess now replies should be correctly sent to users. check-in: 9171c791eb user: arcade tags: trunk
Changes
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
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













-
+




-
+

















-
+







use anyhow::{bail, Context, Result};
use crate::core::Core;
use regex::Regex;
use sedregex::ReplaceCommand;
use telegram_bot;

lazy_static! {
	static ref RE_USERNAME: Regex = Regex::new(r"^@[a-zA-Z][a-zA-Z0-9_]+$").unwrap();
	static ref RE_LINK: Regex = Regex::new(r"^https?://[a-zA-Z.0-9-]+/[-_a-zA-Z.0-9/?=]+$").unwrap();
	static ref RE_IV_HASH: Regex = Regex::new(r"^[a-f0-9]{14}$").unwrap();
}

pub async fn start(core: &Core, sender: telegram_bot::UserId) -> Result<()> {
	core.send("We are open\\. Probably\\. Visit [channel](https://t.me/rsstg_bot_help/3) for details\\.", Some(sender))?;
	core.send("We are open\\. Probably\\. Visit [channel](https://t.me/rsstg_bot_help/3) for details\\.", Some(sender), None)?;
	Ok(())
}

pub async fn list(core: &Core, sender: telegram_bot::UserId) -> Result<()> {
	core.send(core.list(sender).await?.join("\n"), Some(sender))?;
	core.send(core.list(sender).await?, Some(sender), Some(telegram_bot::types::ParseMode::MarkdownV2))?;
	Ok(())
}

pub async fn command(core: &Core, sender: telegram_bot::UserId, command: Vec<&str>) -> Result<()> {
	core.send( match &command[1].parse::<i32>() {
		Err(err) => format!("I need a number\\.\n{}", &err),
		Ok(number) => match command[0] {
			"/check" => core.check(&number, sender, false).await
				.context("Channel check failed.")?,
			"/clean" => core.clean(&number, sender).await?,
			"/enable" => core.enable(&number, sender).await?
				.to_string(),
			"/delete" => core.delete(&number, sender).await?,
			"/disable" => core.disable(&number, sender).await?
				.to_string(),
			_ => bail!("Command {} not handled.", &command[0]),
		},
	}, Some(sender))?;
	}, Some(sender), None)?;
	Ok(())
}

pub async fn update(core: &Core, sender: telegram_bot::UserId, command: Vec<&str>) -> Result<()> {
	let mut source_id: Option<i32> = None;
	let at_least = "Requires at least 3 parameters.";
	let first_word = command[0];
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
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







-
+



-
+





-
+







	let mut i_command = command.into_iter();
	let (channel, url, iv_hash, url_re) = (
		i_command.next().context(at_least)?,
		i_command.next().context(at_least)?,
		i_command.next(),
		i_command.next());
	if ! RE_USERNAME.is_match(&channel) {
		core.send(format!("Usernames should be something like \"@\\[a\\-zA\\-Z]\\[a\\-zA\\-Z0\\-9\\_]+\", aren't they?\nNot {:?}", &channel), Some(sender))?;
		core.send(format!("Usernames should be something like \"@\\[a\\-zA\\-Z]\\[a\\-zA\\-Z0\\-9\\_]+\", aren't they?\nNot {:?}", &channel), Some(sender), None)?;
		return Ok(())
	}
	if ! RE_LINK.is_match(&url) {
		core.send(format!("Link should be a link to atom/rss feed, something like \"https://domain/path\".\nNot {:?}", &url), Some(sender))?;
		core.send(format!("Link should be a link to atom/rss feed, something like \"https://domain/path\".\nNot {:?}", &url), Some(sender), None)?;
		return Ok(())
	}
	let iv_hash = match iv_hash {
		Some(hash) => {
			if ! RE_IV_HASH.is_match(hash) {
				core.send(format!("IV hash should be 14 hex digits.\nNot {:?}", hash), Some(sender))?;
				core.send(format!("IV hash should be 14 hex digits.\nNot {:?}", hash), Some(sender), None)?;
				return Ok(())
			};
			Some(*hash)
		}
		None => None,
	};
	if let Some(rex) = url_re {
88
89
90
91
92
93
94
95


96
88
89
90
91
92
93
94

95
96
97







-
+
+

		};
		if admin.user.id == sender {
			user = true;
		};
	};
	if ! me   { bail!("I need to be admin on that channel\\."); };
	if ! user { bail!("You should be admin on that channel\\."); };
	core.send(core.update(source_id, channel, channel_id, url, iv_hash, None, sender).await?, Some(sender))
	core.send(core.update(source_id, channel, channel_id, url, iv_hash, None, sender).await?, Some(sender), None)?;
	Ok(())
}
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
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







-
+











-
+


+
+
+
+



-
+







				.idle_timeout(std::time::Duration::new(60, 0))
				.connect_lazy(&settings.get_str("pg")?)?,
			sources: Arc::new(Mutex::new(HashSet::new())),
		};
		let clone = core.clone();
		tokio::spawn(async move {
			if let Err(err) = &clone.autofetch().await {
				if let Err(err) = clone.send(&format!("🛑 {:?}", err), None) {
				if let Err(err) = clone.send(&format!("🛑 {:?}", err), None, None) {
					eprintln!("Autofetch error: {}", err);
				};
			}
		});
		Ok(core)
	}

	pub fn stream(&self) -> telegram_bot::UpdatesStream {
		self.tg.stream()
	}

	pub fn send<S>(&self, msg: S, target: Option<telegram_bot::UserId>) -> Result<()>
	pub fn send<S>(&self, msg: S, target: Option<telegram_bot::UserId>, parse_mode: Option<telegram_bot::types::ParseMode>) -> Result<()>
	where S: Into<String> {
		let msg: String = msg.into();
		let parse_mode = match parse_mode {
			Some(mode) => mode,
			None => telegram_bot::types::ParseMode::Html,
		};
		self.tg.spawn(telegram_bot::SendMessage::new(match target {
			Some(user) => user,
			None => self.owner_chat,
		}, msg.to_owned()));
		}, msg.to_owned()).parse_mode(parse_mode));
		Ok(())
	}

	pub async fn check<S>(&self, id: &i32, owner: S, real: bool) -> Result<String>
	where S: Into<i64> {
		let mut posted: i32 = 0;
		let owner: i64 = owner.into();
318
319
320
321
322
323
324
325

326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342

343
344
345
346
347
348
349
322
323
324
325
326
327
328

329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345

346
347
348
349
350
351
352
353







-
+
















-
+







					//clone.owner_chat(UserId::new(owner));
					let clone = Core {
						owner_chat: telegram_bot::UserId::new(owner),
						..self.clone()
					};
					tokio::spawn(async move {
						if let Err(err) = clone.check(&source_id, owner, true).await {
							if let Err(err) = clone.send(&format!("🛑 {:?}", err), None) {
							if let Err(err) = clone.send(&format!("🛑 {:?}", err), None, None) {
								eprintln!("Check error: {}", err);
							};
						};
					});
				} else {
					if next_fetch - now < delay {
						delay = next_fetch - now;
					}
				}
			};
			queue.clear();
			tokio::time::sleep(delay.to_std()?).await;
			delay = chrono::Duration::minutes(1);
		}
	}

	pub async fn list<S>(&self, owner: S) -> Result<Vec<String>>
	pub async fn list<S>(&self, owner: S) -> Result<String>
	where S: Into<i64> {
		let owner = owner.into();
		let mut reply = vec![];
		let mut conn = self.pool.acquire().await
			.with_context(|| format!("List fetch conn:\n{:?}", &self.pool))?;
		reply.push("Channels:".to_string());
		let rows = sqlx::query("select source_id, channel, enabled, url, iv_hash from rsstg_source where owner = $1 order by source_id")
360
361
362
363
364
365
366
367

368
369
364
365
366
367
368
369
370

371
372
373







-
+


					true  => "🔄 enabled",
					false => "â›” disabled",
				}, url));
			if let Some(hash) = iv_hash {
				reply.push(format!("IV `{}`", hash));
			}
		};
		Ok(reply)
		Ok(reply.join("\n"))
	}
}
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
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






-
-
+














-
-
+
+






-
+



-
+







-
+

-
+

-
+







mod command;
mod core;

use config;
use futures::StreamExt;
use tokio;

use telegram_bot::*;
use telegram_bot;

#[macro_use]
extern crate lazy_static;

use anyhow::Result;

#[tokio::main]
async fn main() -> Result<()> {
	let mut settings = config::Config::default();
	settings.merge(config::File::with_name("rsstg"))?;

	let core = core::Core::new(settings).await?;

	let mut stream = core.stream();
	stream.allowed_updates(&[AllowedUpdate::Message]);
	let mut reply_to: Option<UserId>;
	stream.allowed_updates(&[telegram_bot::AllowedUpdate::Message]);
	let mut reply_to: Option<telegram_bot::UserId>;

	loop {
		reply_to = None;
		match stream.next().await {
			Some(update) => {
				if let Err(err) = handle(update?, &core, &mut reply_to).await {
					core.send(&format!("🛑 {:?}", err), reply_to)?;
					core.send(&format!("🛑 {:?}", err), reply_to, None)?;
				};
			},
			None => {
				core.send(&format!("🛑 None error."), None)?;
				core.send(&format!("🛑 None error."), None, None)?;
			}
		};
	}

	//Ok(())
}

async fn handle(update: telegram_bot::Update, core: &core::Core, mut _reply_to: &Option<UserId>) -> Result<()> {
async fn handle(update: telegram_bot::Update, core: &core::Core, mut _reply_to: &Option<telegram_bot::UserId>) -> Result<()> {
	match update.kind {
		UpdateKind::Message(message) => {
		telegram_bot::UpdateKind::Message(message) => {
			match message.kind {
				MessageKind::Text { ref data, .. } => {
				telegram_bot::MessageKind::Text { ref data, .. } => {
					let sender = message.from.id;
					let words: Vec<&str> = data.split_whitespace().collect();
					match words[0] {
						"/check" | "/clean" | "/enable" | "/delete" | "/disable" => command::command(core, sender, words).await?,
						"/start" => command::start(core, sender).await?,
						"/list" => command::list(core, sender).await?,
						"/add" | "/update" => command::update(core, sender, words).await?,