Lines of
src/command.rs
from check-in fae13a0e55
that are changed by the sequence of edits moving toward
check-in 44575a91d3:
1: use crate::core::Core;
2:
fae13a0e55 2025-06-28 3: use anyhow::{
fae13a0e55 2025-06-28 4: anyhow,
fae13a0e55 2025-06-28 5: bail,
fae13a0e55 2025-06-28 6: Context,
fae13a0e55 2025-06-28 7: Result,
fae13a0e55 2025-06-28 8: };
9: use lazy_static::lazy_static;
10: use regex::Regex;
11: use sedregex::ReplaceCommand;
12: use tgbot::types::{
13: ChatMember,
14: ChatUsername,
15: GetChat,
16: GetChatAdministrators,
17: Message,
18: ParseMode::MarkdownV2,
19: };
20:
21: lazy_static! {
22: static ref RE_USERNAME: Regex = Regex::new(r"^@([a-zA-Z][a-zA-Z0-9_]+)$").unwrap();
23: static ref RE_LINK: Regex = Regex::new(r"^https?://[a-zA-Z.0-9-]+/[-_a-zA-Z.:0-9/?=]+$").unwrap();
24: static ref RE_IV_HASH: Regex = Regex::new(r"^[a-f0-9]{14}$").unwrap();
25: }
26:
27: pub async fn start (core: &Core, msg: &Message) -> Result<()> {
28: core.send("We are open\\. Probably\\. Visit [channel](https://t.me/rsstg_bot_help/3) for details\\.",
fae13a0e55 2025-06-28 29: Some(msg.chat.get_id()), Some(MarkdownV2)).await?;
30: Ok(())
31: }
32:
33: pub async fn list (core: &Core, msg: &Message) -> Result<()> {
34: let sender = msg.sender.get_user_id()
fae13a0e55 2025-06-28 35: .ok_or(anyhow!("Ignoring unreal users."))?;
fae13a0e55 2025-06-28 36: let reply = core.list(sender).await?;
fae13a0e55 2025-06-28 37: core.send(reply, Some(msg.chat.get_id()), Some(MarkdownV2)).await?;
38: Ok(())
39: }
40:
41: pub async fn command (core: &Core, command: &str, msg: &Message, words: &[String]) -> Result<()> {
fae13a0e55 2025-06-28 42: let mut conn = core.db.begin().await?;
43: let sender = msg.sender.get_user_id()
fae13a0e55 2025-06-28 44: .ok_or(anyhow!("Ignoring unreal users."))?;
45: let reply = if words.len() == 1 {
46: match words[0].parse::<i32>() {
47: Err(err) => format!("I need a number.\n{}", &err).into(),
48: Ok(number) => match command {
49: "/check" => core.check(number, false).await
50: .context("Channel check failed.")?.into(),
fae13a0e55 2025-06-28 51: "/clean" => conn.clean(number, sender).await?,
fae13a0e55 2025-06-28 52: "/enable" => conn.enable(number, sender).await?.into(),
fae13a0e55 2025-06-28 53: "/delete" => conn.delete(number, sender).await?,
fae13a0e55 2025-06-28 54: "/disable" => conn.disable(number, sender).await?.into(),
55: _ => bail!("Command {command} {words:?} not handled."),
56: },
57: }
58: } else {
59: "This command needs exacly one number.".into()
60: };
fae13a0e55 2025-06-28 61: core.send(reply, Some(msg.chat.get_id()), None).await?;
62: Ok(())
63: }
64:
65: pub async fn update (core: &Core, command: &str, msg: &Message, words: &[String]) -> Result<()> {
66: let sender = msg.sender.get_user_id()
fae13a0e55 2025-06-28 67: .ok_or(anyhow!("Ignoring unreal users."))?;
68: let mut source_id: Option<i32> = None;
69: let at_least = "Requires at least 3 parameters.";
70: let mut i_words = words.iter();
71: match command {
72: "/update" => {
73: let next_word = i_words.next().context(at_least)?;
74: source_id = Some(next_word.parse::<i32>()
75: .context(format!("I need a number, but got {next_word}."))?);
76: },
77: "/add" => {},
78: _ => bail!("Passing {command} is not possible here."),
79: };
80: let (channel, url, iv_hash, url_re) = (
81: i_words.next().context(at_least)?,
82: i_words.next().context(at_least)?,
83: i_words.next(),
84: i_words.next());
85: /*
86: let channel = match RE_USERNAME.captures(channel) {
87: Some(caps) => match caps.get(1) {
88: Some(data) => data.as_str(),
89: None => bail!("No string found in channel name"),
90: },
91: None => {
92: bail!("Usernames should be something like \"@\\[a\\-zA\\-Z]\\[a\\-zA\\-Z0\\-9\\_]+\", aren't they?\nNot {channel:?}");
93: },
94: };
95: */
96: if ! RE_USERNAME.is_match(channel) {
97: bail!("Usernames should be something like \"@\\[a\\-zA\\-Z]\\[a\\-zA\\-Z0\\-9\\_]+\", aren't they?\nNot {channel:?}");
98: };
99: if ! RE_LINK.is_match(url) {
100: bail!("Link should be a link to atom/rss feed, something like \"https://domain/path\".\nNot {url:?}");
101: }
102: let iv_hash = match iv_hash {
103: Some(hash) => {
104: match hash.as_ref() {
105: "-" => None,
106: thing => {
107: if ! RE_IV_HASH.is_match(thing) {
108: bail!("IV hash should be 14 hex digits.\nNot {thing:?}");
109: };
110: Some(thing)
111: },
112: }
113: },
114: None => None,
115: };
116: let url_re = match url_re {
117: Some(re) => {
118: match re.as_ref() {
119: "-" => None,
120: thing => {
121: let _url_rex = ReplaceCommand::new(thing).context("Regexp parsing error:")?;
122: Some(thing)
123: }
124: }
125: },
126: None => None,
127: };
128: let chat_id = ChatUsername::from(channel.as_ref());
fae13a0e55 2025-06-28 129: let channel_id = core.tg.execute(GetChat::new(chat_id.clone())).await?.id;
130: let chan_adm = core.tg.execute(GetChatAdministrators::new(chat_id)).await
131: .context("Sorry, I have no access to that chat.")?;
132: let (mut me, mut user) = (false, false);
133: for admin in chan_adm {
134: let member_id = match admin {
135: ChatMember::Creator(member) => member.user.id,
136: ChatMember::Administrator(member) => member.user.id,
137: ChatMember::Left(_)
138: | ChatMember::Kicked(_)
139: | ChatMember::Member{..}
140: | ChatMember::Restricted(_) => continue,
141: };
142: if member_id == core.me.id {
143: me = true;
144: }
145: if member_id == sender {
146: user = true;
147: }
148: };
149: if ! me { bail!("I need to be admin on that channel."); };
150: if ! user { bail!("You should be admin on that channel."); };
fae13a0e55 2025-06-28 151: let mut conn = core.db.begin().await?;
fae13a0e55 2025-06-28 152: core.send(conn.update(source_id, channel, channel_id, url, iv_hash, url_re, sender).await?, Some(msg.chat.get_id()), None).await?;
153: Ok(())
154: }