Overview
Comment: | move all sql stuff to separate module, get rid of macros |
---|---|
Downloads: | Tarball | ZIP archive | SQL archive |
Timelines: | family | ancestors | descendants | both | trunk | v0.3.1 |
Files: | files | file ages | folders |
SHA3-256: |
0340541002ec78f0d4d5581b4389e477 |
User & Date: | arcade on 2025-04-24 12:12:43.296 |
Other Links: | manifest | tags |
Context
2025-04-26
| ||
06:10 | fix int size, small bump check-in: e3f7eeb26a user: arcade tags: trunk, v0.3.2 | |
2025-04-24
| ||
12:12 | move all sql stuff to separate module, get rid of macros check-in: 0340541002 user: arcade tags: trunk, v0.3.1 | |
2025-04-20
| ||
09:51 | drop old telegram_bot library, switch to frankenstein check-in: e624ef9d66 user: arcade tags: trunk, v0.3.0 | |
Changes
Modified Cargo.lock
from [85f5ac9f8e]
to [351e452347].
︙ | ︙ | |||
99 100 101 102 103 104 105 | "event-listener-strategy", "futures-core", "pin-project-lite", ] [[package]] name = "async-compression" | | | | 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | "event-listener-strategy", "futures-core", "pin-project-lite", ] [[package]] name = "async-compression" version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b37fc50485c4f3f736a4fb14199f6d5f5ba008d7f28fe710306c92780f004c07" dependencies = [ "brotli", "flate2", "futures-core", "memchr", "pin-project-lite", "tokio", |
︙ | ︙ | |||
344 345 346 347 348 349 350 | "futures-io", "futures-lite 2.6.0", "piper", ] [[package]] name = "bon" | | | | | | | | | | 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 | "futures-io", "futures-lite 2.6.0", "piper", ] [[package]] name = "bon" version = "3.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced38439e7a86a4761f7f7d5ded5ff009135939ecb464a24452eaa4c1696af7d" dependencies = [ "bon-macros", "rustversion", ] [[package]] name = "bon-macros" version = "3.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce61d2d3844c6b8d31b2353d9f66cf5e632b3e9549583fe3cac2f4f6136725e" dependencies = [ "darling", "ident_case", "prettyplease", "proc-macro2", "quote", "rustversion", "syn 2.0.100", ] [[package]] name = "brotli" version = "8.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf19e729cdbd51af9a397fb9ef8ac8378007b797f8273cfbfdf45dcaa316167b" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", "brotli-decompressor", ] [[package]] name = "brotli-decompressor" version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", ] [[package]] name = "bumpalo" |
︙ | ︙ | |||
960 961 962 963 964 965 966 | dependencies = [ "typenum", "version_check", ] [[package]] name = "getrandom" | | | | 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 | dependencies = [ "typenum", "version_check", ] [[package]] name = "getrandom" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", "wasm-bindgen", ] |
︙ | ︙ | |||
1454 1455 1456 1457 1458 1459 1460 | name = "libc" version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libm" | | | | 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 | name = "libc" version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libm" version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9627da5196e5d8ed0b0495e61e518847578da83483c37288316d9b2e03a7f72" [[package]] name = "libsqlite3-sys" version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" dependencies = [ |
︙ | ︙ | |||
1905 1906 1907 1908 1909 1910 1911 | "tokio", "tracing", "web-time", ] [[package]] name = "quinn-proto" | | | | 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 | "tokio", "tracing", "web-time", ] [[package]] name = "quinn-proto" version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcbafbbdbb0f638fe3f35f3c56739f77a8a1d070cb25603226c83339b391472b" dependencies = [ "bytes", "getrandom 0.3.2", "rand 0.9.1", "ring", "rustc-hash", "rustls", |
︙ | ︙ | |||
1999 2000 2001 2002 2003 2004 2005 | [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ | | | 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 | [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom 0.2.16", ] [[package]] name = "rand_core" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" |
︙ | ︙ | |||
2111 2112 2113 2114 2115 2116 2117 | name = "ring" version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", | | | 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 | name = "ring" version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", "getrandom 0.2.16", "libc", "untrusted", "windows-sys 0.52.0", ] [[package]] name = "rsa" |
︙ | ︙ | |||
2151 2152 2153 2154 2155 2156 2157 | "derive_builder", "never", "quick-xml", ] [[package]] name = "rsstg" | | | 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 | "derive_builder", "never", "quick-xml", ] [[package]] name = "rsstg" version = "0.3.1" dependencies = [ "anyhow", "async-std", "atom_syndication", "chrono", "config", "frankenstein", |
︙ | ︙ | |||
2909 2910 2911 2912 2913 2914 2915 | "futures-util", "thiserror 1.0.69", "tokio", ] [[package]] name = "tokio-util" | | | | 2909 2910 2911 2912 2913 2914 2915 2916 2917 2918 2919 2920 2921 2922 2923 2924 2925 | "futures-util", "thiserror 1.0.69", "tokio", ] [[package]] name = "tokio-util" version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", ] |
︙ | ︙ |
Modified Cargo.toml
from [0f0e00c98d]
to [d8cdea31b9].
1 2 | [package] name = "rsstg" | | | 1 2 3 4 5 6 7 8 9 10 | [package] name = "rsstg" version = "0.3.1" authors = ["arcade"] edition = "2021" [dependencies] anyhow = "1.0.86" async-std = { version = "1.12.0", features = [ "attributes", "tokio1" ] } atom_syndication = { version = "0.12.4", features = [ "with-serde" ] } |
︙ | ︙ |
Modified rsstg.sql
from [b4b5fc5ff8]
to [660469de7c].
1 2 3 4 5 6 7 8 9 10 11 12 | create table rsstg_updates (owner integer, update jsonb); create unique index rsstg_updates__id on rsstg_updates(update->>'update_id'); create table rsstg_source ( source_id serial, channel text not null, channel_id integer not null, url text not null, last_scrape not null timestamptz default now(), enabled boolean not null default true, iv_hash text, | | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | create table rsstg_updates (owner integer, update jsonb); create unique index rsstg_updates__id on rsstg_updates(update->>'update_id'); create table rsstg_source ( source_id serial, channel text not null, channel_id integer not null, url text not null, last_scrape not null timestamptz default now(), enabled boolean not null default true, iv_hash text, owner bigint not null, url_re text); create unique index rsstg_source__source_id on rsstg_source(source_id); create unique index rsstg_source__channel_id__owner on rsstg_source(channel_id, owner); create index rsstg_source__owner on rsstg_source(owner); create table rsstg_post ( source_id integer not null, posted timestamptz not null, |
︙ | ︙ |
Modified src/command.rs
from [11c299d7be]
to [bdc185bc57].
︙ | ︙ | |||
31 32 33 34 35 36 37 | pub async fn start(core: &Core, chat_id: i64) -> Result<()> { core.send("We are open\\. Probably\\. Visit [channel](https://t.me/rsstg_bot_help/3) for details\\.", Some(chat_id), Some(ParseMode::MarkdownV2)).await?; Ok(()) } | | > | | > | | | | | | | 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 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 | pub async fn start(core: &Core, chat_id: i64) -> Result<()> { core.send("We are open\\. Probably\\. Visit [channel](https://t.me/rsstg_bot_help/3) for details\\.", Some(chat_id), Some(ParseMode::MarkdownV2)).await?; Ok(()) } pub async fn list(core: &mut Core, sender: i64) -> Result<()> { let msg = core.list(sender).await?; core.send(msg, Some(sender), Some(ParseMode::MarkdownV2)).await?; Ok(()) } pub async fn command(core: &mut Core, sender: i64, command: Vec<&str>) -> Result<()> { let mut conn = core.db.begin().await?; if command.len() >= 2 { let msg: Cow<str> = match &command[1].parse::<i32>() { Err(err) => format!("I need a number.\n{}", &err).into(), Ok(number) => match command[0] { "/check" => core.check(number, sender, false).await .context("Channel check failed.")?.into(), "/clean" => conn.clean(*number, sender).await?, "/enable" => conn.enable(*number, sender).await?.into(), "/delete" => conn.delete(*number, sender).await?, "/disable" => conn.disable(*number, sender).await?.into(), _ => bail!("Command {} not handled.", &command[0]), }, }; core.send(msg, Some(sender), None).await?; } else { core.send("This command needs a number.", Some(sender), None).await?; } Ok(()) } pub async fn update(core: &mut Core, sender: i64, command: Vec<&str>) -> Result<()> { let mut source_id: Option<i32> = None; let at_least = "Requires at least 3 parameters."; let mut i_command = command.iter(); let first_word = i_command.next().context(at_least)?; match *first_word { "/update" => { let next_word = i_command.next().context(at_least)?; |
︙ | ︙ | |||
131 132 133 134 135 136 137 | }; if member_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."); }; | > | | 133 134 135 136 137 138 139 140 141 142 143 | }; if member_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."); }; let mut conn = core.db.begin().await?; core.send(conn.update(source_id, channel, channel_id, url, iv_hash, url_re, sender).await?, Some(sender), None).await?; Ok(()) } |
Modified src/core.rs
from [d44f60dcb6]
to [73ac2fd559].
|
| | > > > < < | | | < < < < | | | | | 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 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 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | use crate::{ command, sql::Db, }; use std::{ borrow::Cow, collections::{ BTreeMap, HashSet, }, num::TryFromIntError, sync::{ Arc, Mutex }, }; use anyhow::{ bail, Result, }; use async_std::task; use chrono::DateTime; use frankenstein::{ client_reqwest::Bot, methods::{ GetUpdatesParams, SendMessageParams }, types::{ AllowedUpdate, MessageEntityType, User, }, updates::UpdateContent, AsyncTelegramApi, ParseMode, }; use thiserror::Error; #[derive(Error, Debug)] pub enum RssError { // #[error(transparent)] // Tg(#[from] TgError), #[error(transparent)] Int(#[from] TryFromIntError), } #[derive(Clone)] pub struct Core { owner_chat: i64, pub tg: Bot, pub me: User, pub db: Db, sources: Arc<Mutex<HashSet<Arc<i32>>>>, http_client: reqwest::Client, } impl Core { pub async fn new(settings: config::Config) -> Result<Core> { let owner_chat = settings.get_int("owner")?; let api_key = settings.get_string("api_key")?; let tg = Bot::new(&api_key); let mut client = reqwest::Client::builder(); if let Ok(proxy) = settings.get_string("proxy") { let proxy = reqwest::Proxy::all(proxy)?; client = client.proxy(proxy); } let http_client = client.build()?; let me = tg.get_me().await?; let me = me.result; let core = Core { tg, me, owner_chat, db: Db::new(&settings.get_string("pg")?)?, sources: Arc::new(Mutex::new(HashSet::new())), http_client, }; let mut clone = core.clone(); task::spawn(async move { loop { let delay = match &clone.autofetch().await { Err(err) => { if let Err(err) = clone.send(format!("š {err:?}"), None, None).await { eprintln!("Autofetch error: {err:?}"); }; std::time::Duration::from_secs(60) }, Ok(time) => *time, }; task::sleep(delay).await; } }); Ok(core) } pub async fn stream(&mut self) -> Result<()> { let mut offset: i64 = 0; let mut params = GetUpdatesParams { offset: None, limit: Some(100), timeout: Some(300), allowed_updates: Some(vec![AllowedUpdate::Message]), }; |
︙ | ︙ | |||
161 162 163 164 165 166 167 | .text(msg) .parse_mode(mode) .build(); self.tg.send_message(&send).await?; Ok(()) } | | | < | | 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 | .text(msg) .parse_mode(mode) .build(); self.tg.send_message(&send).await?; Ok(()) } pub async fn check (&mut self, id: &i32, owner: i64, real: bool) -> Result<String> { let mut posted: i32 = 0; let mut conn = self.db.begin().await?; let id = { let mut set = self.sources.lock().unwrap(); match set.get(id) { Some(id) => id.clone(), None => { let id = Arc::new(*id); set.insert(id.clone()); id.clone() }, } }; let count = Arc::strong_count(&id); if count == 2 { let source = conn.get_source(*id, owner).await?; let destination = match real { true => source.channel_id, false => source.owner, }; let mut this_fetch: Option<DateTime<chrono::FixedOffset>> = None; let mut posts: BTreeMap<DateTime<chrono::FixedOffset>, String> = BTreeMap::new(); |
︙ | ︙ | |||
227 228 229 230 231 232 233 | } }; for (date, url) in posts.iter() { let post_url: Cow<str> = match source.url_re { Some(ref x) => sedregex::ReplaceCommand::new(x)?.execute(url), None => url.into(), }; | < | < | < | | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | < | | | < | < | | | 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 | } }; for (date, url) in posts.iter() { let post_url: Cow<str> = match source.url_re { Some(ref x) => sedregex::ReplaceCommand::new(x)?.execute(url), None => url.into(), }; if let Some(exists) = conn.exists(&post_url, *id).await? { if ! exists { if this_fetch.is_none() || *date > this_fetch.unwrap() { this_fetch = Some(*date); }; self.send( match &source.iv_hash { Some(hash) => format!("<a href=\"https://t.me/iv?url={post_url}&rhash={hash}\"> </a>{post_url}"), None => format!("{post_url}"), }, Some(destination), Some(ParseMode::Html)).await?; conn.add_post(*id, date, &post_url).await?; }; }; posted += 1; }; posts.clear(); }; conn.set_scrape(*id).await?; Ok(format!("Posted: {posted}")) } async fn autofetch(&mut self) -> Result<std::time::Duration> { let mut delay = chrono::Duration::minutes(1); let now = chrono::Local::now(); let mut conn = self.db.begin().await?; for row in conn.get_queue().await? { if let Some(next_fetch) = row.next_fetch { if next_fetch < now { if let (Some(owner), Some(source_id)) = (row.owner, row.source_id) { let mut clone = Core { owner_chat: owner, ..self.clone() }; task::spawn(async move { if let Err(err) = clone.check(&source_id, owner, true).await { if let Err(err) = clone.send(&format!("š {err:?}"), None, None).await { eprintln!("Check error: {err:?}"); // clone.disable(&source_id, owner).await.unwrap(); }; }; }); } } else if next_fetch - now < delay { delay = next_fetch - now; } } }; Ok(delay.to_std()?) } pub async fn list (&mut self, owner: i64) -> Result<String> { let mut reply: Vec<Cow<str>> = vec![]; reply.push("Channels:".into()); let mut conn = self.db.begin().await?; for row in conn.get_list(owner).await? { reply.push(format!("\n\\#ļøā£ {} \\*ļøā£ `{}` {}\nš `{}`", row.source_id, row.channel, match row.enabled { true => "š enabled", false => "ā disabled", }, row.url).into()); if let Some(hash) = &row.iv_hash { reply.push(format!("IV: `{hash}`").into()); |
︙ | ︙ |
Modified src/main.rs
from [086f4498d9]
to [a04d5da65d].
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | //! This is telegram bot to fetch RSS/ATOM feeds and post results on public //! channels #![warn(missing_docs)] mod command; mod core; use anyhow::Result; #[async_std::main] async fn main() -> Result<()> { let settings = config::Config::builder() .add_source(config::File::with_name("rsstg")) .build()?; | > | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | //! This is telegram bot to fetch RSS/ATOM feeds and post results on public //! channels #![warn(missing_docs)] mod command; mod core; mod sql; use anyhow::Result; #[async_std::main] async fn main() -> Result<()> { let settings = config::Config::builder() .add_source(config::File::with_name("rsstg")) .build()?; let mut core = core::Core::new(settings).await?; core.stream().await?; Ok(()) } |
Added src/sql.rs version [9ff239b4bf].