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
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))?;
	Ok(())
}

pub async fn list(core: &Core, sender: telegram_bot::UserId) -> Result<()> {
	core.send(core.list(sender).await?.join("\n"), Some(sender))?;
	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))?;
	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];













|




|

















|







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), None)?;
	Ok(())
}

pub async fn list(core: &Core, sender: telegram_bot::UserId) -> Result<()> {
	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), 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
	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))?;
		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))?;
		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))?;
				return Ok(())
			};
			Some(*hash)
		}
		None => None,
	};
	if let Some(rex) = url_re {







|



|





|







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), 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), 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), None)?;
				return Ok(())
			};
			Some(*hash)
		}
		None => None,
	};
	if let Some(rex) = url_re {
88
89
90
91
92
93
94
95

96
		};
		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))

}







|
>

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), 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
				.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) {
					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<()>
	where S: Into<String> {
		let msg: String = msg.into();




		self.tg.spawn(telegram_bot::SendMessage::new(match target {
			Some(user) => user,
			None => self.owner_chat,
		}, msg.to_owned()));
		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();







|











|


>
>
>
>



|







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, 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>, 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()).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
					//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) {
								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>>
	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")







|
















|







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







|


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.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
mod command;
mod core;

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

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>;

	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)?;
				};
			},
			None => {
				core.send(&format!("🛑 None error."), None)?;
			}
		};
	}

	//Ok(())
}

async fn handle(update: telegram_bot::Update, core: &core::Core, mut _reply_to: &Option<UserId>) -> Result<()> {
	match update.kind {
		UpdateKind::Message(message) => {
			match message.kind {
				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?,






<
|














|
|






|



|







|

|

|







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;

#[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(&[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, None)?;
				};
			},
			None => {
				core.send(&format!("🛑 None error."), None, None)?;
			}
		};
	}

	//Ok(())
}

async fn handle(update: telegram_bot::Update, core: &core::Core, mut _reply_to: &Option<telegram_bot::UserId>) -> Result<()> {
	match update.kind {
		telegram_bot::UpdateKind::Message(message) => {
			match message.kind {
				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?,