Index: .github/workflows/rust-clippy.yml ================================================================== --- .github/workflows/rust-clippy.yml +++ .github/workflows/rust-clippy.yml @@ -1,7 +1,10 @@ name: rust-ci -on: push +on: + push: + branches: [ master, release ] + pull_request: # sccache enable for rust/C builds env: SCCACHE_GHA_ENABLED: "true" RUSTC_WRAPPER: "sccache" @@ -8,21 +11,22 @@ jobs: rust-ci-run: name: Run rust-clippy analyzing and tests runs-on: ubuntu-latest + if: github.event_name == 'push' || (github.head_ref != 'master' && github.head_ref != 'release') permissions: contents: read steps: # SETUP - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - - uses: mozilla-actions/sccache-action@v0.0.9 + - uses: mozilla-actions/sccache-action@v0.0.10 # TESTS - name: Run tests run: cargo test --all-targets --all-features # CLIPPY - name: Run rust-clippy run: cargo clippy --all-targets --all-features -- -D warnings Index: Cargo.lock ================================================================== --- Cargo.lock +++ Cargo.lock @@ -2091,13 +2091,13 @@ "windows-sys 0.61.2", ] [[package]] name = "rustls" -version = "0.23.38" +version = "0.23.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21" +checksum = "7c2c118cb077cca2822033836dfb1b975355dfb784b5e8da48f7b6c5db74e60e" dependencies = [ "aws-lc-rs", "once_cell", "ring", "rustls-pki-types", @@ -2155,13 +2155,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.12" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8279bb85272c9f10811ae6a6c547ff594d6a7f3c6c6b02ee9726d1d0dcfcdd06" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -3022,13 +3022,13 @@ "linked-hash-map", ] [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" [[package]] name = "unicase" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3629,13 +3629,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" +checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" dependencies = [ "memchr", ] [[package]] Index: src/command.rs ================================================================== --- src/command.rs +++ src/command.rs @@ -42,11 +42,11 @@ } /// Send the sender's subscription list to the chat. /// /// Retrieves the message sender's user ID, obtains their subscription list from `core`, -/// and sends the resulting reply into the message chat using MarkdownV2. +/// and sends the resulting reply into the message chat using HTML pub async fn list (core: &Core, msg: &Message) -> Result<()> { let sender = msg.sender.get_user_id() .stack_err("Ignoring unreal users.")?; let reply = core.list(sender).await.stack()?; core.tg.send(MyMessage::html_to(reply, msg.chat.get_id())).await.stack()?; Index: src/core.rs ================================================================== --- src/core.rs +++ src/core.rs @@ -5,10 +5,11 @@ sql::Db, tg_bot::{ Callback, MyMessage, Tg, + validate, }, }; use std::{ borrow::Cow, @@ -112,10 +113,12 @@ pub feeds: Arc>, running: Arc>>, http_client: reqwest::Client, } +// XXX Right now that part is unfinished and I guess I need to finish menu first +#[allow(unused)] pub struct Post { uri: String, _title: String, _authors: String, _summary: String, @@ -421,12 +424,11 @@ impl UpdateHandler for Core { /// Dispatches an incoming Telegram update to a matching command handler and reports handler errors to the originating chat. /// /// This method inspects the update; if it contains a message that can be parsed as a bot command, /// it executes the corresponding command handler. If the handler returns an error, the error text - /// is sent back to the message's chat using MarkdownV2 formatting. Unknown commands produce an error - /// which is also reported to the chat. + /// is sent back to the message's chat. Unknown commands produce an error which is also reported to the chat. async fn handle (&self, update: Update) -> () { match update.update_type { UpdateType::Message(msg) => { if let Ok(cmd) = Command::try_from(*msg) { let msg = cmd.get_message(); @@ -438,17 +440,24 @@ "/list" => command::list(self, msg).await, "/test" => command::test(self, msg).await, "/add" | "/update" => command::update(self, command, msg, words).await, any => Err(anyhow!("Unknown command: {any}")), }; - if let Err(err) = res - && let Err(err2) = self.tg.send(MyMessage::html_to( - format!("#error
{err}
"), - msg.chat.get_id(), - )).await - { - dbg!(err2); + if let Err(err) = res { + match validate(&err.to_string()) { + Ok(text) => { + if let Err(err2) = self.tg.send(MyMessage::html_to( + format!("#error
{}
", text), + msg.chat.get_id(), + )).await { + dbg!(err2); + } + }, + Err(err2) => { + dbg!(err2); + }, + } } } else { // not a command } }, Index: src/sql.rs ================================================================== --- src/sql.rs +++ src/sql.rs @@ -234,11 +234,13 @@ .bind(id.into()) .execute(&mut *self.0).await.stack()?; Ok(()) } - #[allow(clippy::too_many_arguments)] // XXX at least for now… + #[allow(clippy::too_many_arguments)] // XXX do I need to make it variadic? I guess it work fine + // this way for now, unless there would be a good struct + // with all source fields to use that as an argument pub async fn update (&mut self, update: Option, channel: &str, channel_id: i64, url: &str, iv_hash: Option<&str>, url_re: Option<&str>, owner: I) -> Result<&str> where I: Into { match match update { Some(id) => { sqlx::query("update rsstg_source set channel_id = $2, url = $3, iv_hash = $4, owner = $5, channel = $6, url_re = $7 where source_id = $1") Index: src/tg_bot.rs ================================================================== --- src/tg_bot.rs +++ src/tg_bot.rs @@ -8,14 +8,16 @@ borrow::Cow, fmt, time::Duration, }; +use lazy_static::lazy_static; use serde::{ Deserialize, Serialize, }; +use regex::Regex; use smol::Timer; use stacked_errors::{ bail, Result, StackableErr, @@ -39,10 +41,25 @@ SendMessage, }, }; const CB_VERSION: u8 = 0; + +lazy_static! { + pub static ref RE_CLOSING: Regex = Regex::new(r"").unwrap(); +} + +// validate input as being postable in preformatted block, all html tags are fine, except tags that +// break the block - and , we don't need to escape anything else, as telegram manages +// that +pub fn validate (text: &str) -> Result<&str> { + if RE_CLOSING.is_match(text) { + bail!("Telegram closing tag found."); + } else { + Ok(text) + } +} #[derive(Serialize, Deserialize, Debug)] pub enum Callback { // Edit one feed (version, name) Edit(u8, String), @@ -148,11 +165,11 @@ if long { kb.push(vec![ InlineKeyboardButton::for_callback_data("<<", Callback::list("", if *page == 0 { *page } else { page - 1 } ).to_string()), InlineKeyboardButton::for_callback_data(">>", - Callback::list("", page + 1).to_string()), + Callback::list("", page.saturating_add(1)).to_string()), ]); } InlineKeyboardMarkup::from(kb) }, Callback::Menu(_) => { @@ -197,12 +214,12 @@ where S: Into> { let text = text.into(); MyMessage::HtmlToKb { text, to, kb } } - fn req (&self, tg: &Tg) -> Result { - Ok(match self { + fn req (&self, tg: &Tg) -> SendMessage { + match self { MyMessage::Html { text } => SendMessage::new(tg.owner, text.as_ref()) .with_parse_mode(ParseMode::Html), MyMessage::HtmlTo { text, to } => SendMessage::new(*to, text.as_ref()) @@ -209,11 +226,11 @@ .with_parse_mode(ParseMode::Html), MyMessage::HtmlToKb { text, to, kb } => SendMessage::new(*to, text.as_ref()) .with_parse_mode(ParseMode::Html) .with_reply_markup(kb.clone()), - }) + } } } #[derive(Clone)] pub struct Tg { @@ -253,11 +270,11 @@ /// Send a text message to a chat, using an optional target and parse mode. /// /// # Returns /// The sent `Message` on success. pub async fn send (&self, msg: MyMessage<'_>) -> Result { - self.client.execute(msg.req(self)?).await.stack() + self.client.execute(msg.req(self)).await.stack() } pub async fn answer_cb (&self, id: String, text: String) -> Result { self.client.execute( AnswerCallbackQuery::new(id) @@ -278,10 +295,11 @@ owner: ChatPeerId::from(owner.into()), ..self.clone() } } + // XXX Can loop indefinitely if API calls results retry_after, add max retries? pub async fn update_message (&self, chat_id: i64, message_id: i64, text: String, feeds: &Arc>, cb: Callback) -> Result { loop { let req = EditMessageText::for_chat_message(chat_id, message_id, &text) .with_reply_markup(get_kb(&cb, feeds).await.stack()?); let res = self.client.execute(req).await;