Index: Cargo.lock ================================================================== --- Cargo.lock +++ Cargo.lock @@ -66,24 +66,10 @@ name = "anyhow" version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" -[[package]] -name = "aquamarine" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f50776554130342de4836ba542aa85a4ddb361690d7e8df13774d7284c3d5c2" -dependencies = [ - "include_dir", - "itertools", - "proc-macro-error2", - "proc-macro2", - "quote", - "syn 2.0.100", -] - [[package]] name = "async-attributes" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" @@ -115,13 +101,13 @@ "pin-project-lite", ] [[package]] name = "async-compression" -version = "0.4.22" +version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59a194f9d963d8099596278594b3107448656ba73831c9d8c783e613ce86da64" +checksum = "b37fc50485c4f3f736a4fb14199f6d5f5ba008d7f28fe710306c92780f004c07" dependencies = [ "brotli", "flate2", "futures-core", "memchr", @@ -129,18 +115,19 @@ "tokio", ] [[package]] name = "async-executor" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" +checksum = "bb812ffb58524bdd10860d7d974e2f01cc0950c2438a74ee5ec2e2280c6c4ffa" dependencies = [ "async-task", "concurrent-queue", "fastrand 2.3.0", "futures-lite 2.6.0", + "pin-project-lite", "slab", ] [[package]] name = "async-global-executor" @@ -247,10 +234,21 @@ [[package]] name = "async-task" version = "4.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] [[package]] name = "atoi" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -285,13 +283,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" -version = "0.3.74" +version = "0.3.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", "cfg-if", "libc", "miniz_oxide", @@ -318,13 +316,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" dependencies = [ "serde", ] [[package]] @@ -346,27 +344,52 @@ "async-task", "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.101", +] [[package]] name = "brotli" -version = "7.0.0" +version = "8.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd" +checksum = "9991eea70ea4f293524138648e41ee89b0b2b12ddef3b255effa43c8056e0e0d" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", "brotli-decompressor", ] [[package]] name = "brotli-decompressor" -version = "4.0.3" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a334ef7c9e23abf0ce748e8cd309037da93e606ad52eb372e4ce327a0dcfbdfd" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", ] @@ -374,16 +397,10 @@ name = "bumpalo" version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" -[[package]] -name = "bytemuck" -version = "1.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540" - [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" @@ -394,13 +411,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.19" +version = "1.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362" +checksum = "5f4ac86a9e5bc1e2b3449ab9d7d3a6a405e3d1bb28d7b9be8614f55846ae3766" dependencies = [ "shlex", ] [[package]] @@ -415,13 +432,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.40" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", @@ -482,13 +499,13 @@ "libc", ] [[package]] name = "crc" -version = "3.2.1" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" dependencies = [ "crc-catalog", ] [[package]] @@ -550,11 +567,11 @@ "fnv", "ident_case", "proc-macro2", "quote", "strsim", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] name = "darling_macro" version = "0.20.11" @@ -561,11 +578,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] name = "der" version = "0.7.10" @@ -575,20 +592,10 @@ "const-oid", "pem-rfc7468", "zeroize", ] -[[package]] -name = "deranged" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" -dependencies = [ - "powerfmt", - "serde", -] - [[package]] name = "derive_builder" version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" @@ -603,42 +610,21 @@ checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] name = "derive_builder_macro" version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.100", -] - -[[package]] -name = "derive_more" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" -dependencies = [ - "derive_more-impl", -] - -[[package]] -name = "derive_more-impl" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", - "unicode-xid", + "syn 2.0.101", ] [[package]] name = "digest" version = "0.10.7" @@ -666,28 +652,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] name = "dotenvy" version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" -[[package]] -name = "dptree" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d81175dab5ec79c30e0576df2ed2c244e1721720c302000bb321b107e82e265c" -dependencies = [ - "futures", -] - [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" @@ -708,25 +685,15 @@ name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" -[[package]] -name = "erasable" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "437cfb75878119ed8265685c41a115724eae43fb7cc5a0bf0e4ecc3b803af1c4" -dependencies = [ - "autocfg", - "scopeguard", -] - [[package]] name = "errno" -version = "0.3.11" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" +checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" dependencies = [ "libc", "windows-sys 0.59.0", ] @@ -837,10 +804,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] + +[[package]] +name = "frankenstein" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bb8f97b9b2fefaa34071c0ccb9df53d7d15d5c4a375c07cd701e133dac048af" +dependencies = [ + "async-trait", + "bon", + "macro_rules_attribute", + "paste", + "reqwest", + "serde", + "serde_json", + "serde_with", + "thiserror 2.0.12", + "tokio", +] [[package]] name = "futures" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -933,11 +918,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] name = "futures-sink" version = "0.3.31" @@ -978,13 +963,13 @@ "version_check", ] [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", @@ -991,13 +976,13 @@ "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "js-sys", "libc", "r-efi", @@ -1023,38 +1008,32 @@ "wasm-bindgen", ] [[package]] name = "h2" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75249d144030531f8dee69fe9cea04d3edf809a017ae445e2abdff6629e86633" +checksum = "a9421a676d1b147b16b82c9225157dc629087ef8ec4d5e2960f9437a90dac0a5" dependencies = [ "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", "http", - "indexmap 2.9.0", + "indexmap", "slab", "tokio", "tokio-util", "tracing", ] [[package]] name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - -[[package]] -name = "hashbrown" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" dependencies = [ "allocator-api2", "equivalent", "foldhash", ] @@ -1063,11 +1042,11 @@ name = "hashlink" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" dependencies = [ - "hashbrown 0.15.2", + "hashbrown", ] [[package]] name = "heck" version = "0.5.0" @@ -1192,11 +1171,11 @@ "rustls", "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", - "webpki-roots", + "webpki-roots 0.26.11", ] [[package]] name = "hyper-tls" version = "0.6.0" @@ -1213,13 +1192,13 @@ "tower-service", ] [[package]] name = "hyper-util" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" +checksum = "cf9f1e950e0d9d1d3c47184416723cf29c0d1f93bd8cccf37e4beb6b44f31710" dependencies = [ "bytes", "futures-channel", "futures-util", "http", @@ -1257,126 +1236,94 @@ "cc", ] [[package]] name = "icu_collections" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" dependencies = [ "displaydoc", + "potential_utf", "yoke", "zerofrom", "zerovec", ] [[package]] -name = "icu_locid" -version = "1.5.0" +name = "icu_locale_core" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" dependencies = [ "displaydoc", "litemap", "tinystr", "writeable", "zerovec", ] -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" - [[package]] name = "icu_normalizer" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" dependencies = [ "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", "icu_provider", "smallvec", - "utf16_iter", - "utf8_iter", - "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" -version = "1.5.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" [[package]] name = "icu_properties" -version = "1.5.1" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" dependencies = [ "displaydoc", "icu_collections", - "icu_locid_transform", + "icu_locale_core", "icu_properties_data", "icu_provider", - "tinystr", + "potential_utf", + "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "1.5.1" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" [[package]] name = "icu_provider" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" dependencies = [ "displaydoc", - "icu_locid", - "icu_provider_macros", + "icu_locale_core", "stable_deref_trait", "tinystr", "writeable", "yoke", "zerofrom", + "zerotrie", "zerovec", ] -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", -] - [[package]] name = "ident_case" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" @@ -1392,57 +1339,26 @@ "utf8_iter", ] [[package]] name = "idna_adapter" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ "icu_normalizer", "icu_properties", ] -[[package]] -name = "include_dir" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd" -dependencies = [ - "include_dir_macros", -] - -[[package]] -name = "include_dir_macros" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" -dependencies = [ - "proc-macro2", - "quote", -] - -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", - "serde", -] - [[package]] name = "indexmap" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", - "hashbrown 0.15.2", - "serde", + "hashbrown", ] [[package]] name = "instant" version = "0.1.13" @@ -1467,19 +1383,10 @@ name = "ipnet" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" @@ -1518,13 +1425,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libm" -version = "0.2.11" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libsqlite3-sys" version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1552,13 +1459,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "litemap" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "lock_api" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1575,10 +1482,32 @@ checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" dependencies = [ "value-bag", ] +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "macro_rules_attribute" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a82271f7bc033d84bbca59a3ce3e4159938cb08a9c3aebbe54d215131518a13" +dependencies = [ + "macro_rules_attribute-proc_macro", + "paste", +] + +[[package]] +name = "macro_rules_attribute-proc_macro" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dd856d451cc0da70e2ef2ce95a18e39a93b7558bedf10201ad28503f918568" + [[package]] name = "md-5" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" @@ -1667,16 +1596,10 @@ "rand 0.8.5", "smallvec", "zeroize", ] -[[package]] -name = "num-conv" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - [[package]] name = "num-integer" version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" @@ -1724,11 +1647,11 @@ name = "openssl" version = "0.10.72" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "cfg-if", "foreign-types", "libc", "once_cell", "openssl-macros", @@ -1741,11 +1664,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] name = "openssl-probe" version = "0.1.6" @@ -1752,13 +1675,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" -version = "0.9.107" +version = "0.9.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8288979acd84749c744a9014b4382d42b8f7b2592847b5afb2ed29e5d16ede07" +checksum = "e145e1651e858e820e4860f7b9c5e169bc1d8ce1c86043be79fa7b7634821847" dependencies = [ "cc", "libc", "pkg-config", "vcpkg", @@ -1791,10 +1714,16 @@ "redox_syscall", "smallvec", "windows-targets 0.52.6", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "pathdiff" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" @@ -1812,30 +1741,10 @@ name = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" -[[package]] -name = "pin-project" -version = "1.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", -] - [[package]] name = "pin-project-lite" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" @@ -1914,14 +1823,17 @@ "tracing", "windows-sys 0.59.0", ] [[package]] -name = "powerfmt" -version = "0.2.0" +name = "potential_utf" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] [[package]] name = "ppv-lite86" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1929,28 +1841,17 @@ dependencies = [ "zerocopy", ] [[package]] -name = "proc-macro-error-attr2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" -dependencies = [ - "proc-macro2", - "quote", -] - -[[package]] -name = "proc-macro-error2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" -dependencies = [ - "proc-macro-error-attr2", - "proc-macro2", - "quote", +name = "prettyplease" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6" +dependencies = [ + "proc-macro2", + "syn 2.0.101", ] [[package]] name = "proc-macro2" version = "1.0.95" @@ -1958,34 +1859,25 @@ checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] -[[package]] -name = "psm" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f58e5423e24c18cc840e1c98370b3993c6649cd1678b4d24318bcf0a083cbe88" -dependencies = [ - "cc", -] - [[package]] name = "quick-xml" -version = "0.37.4" +version = "0.37.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4ce8c88de324ff838700f36fb6ab86c96df0e3c4ab6ef3a9b2044465cce1369" +checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" dependencies = [ "encoding_rs", "memchr", ] [[package]] name = "quinn" -version = "0.11.7" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3bd15a6f2967aef83887dcb9fec0014580467e33720d073560cf015a5683012" +checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" dependencies = [ "bytes", "cfg_aliases", "pin-project-lite", "quinn-proto", @@ -1999,16 +1891,17 @@ "web-time", ] [[package]] name = "quinn-proto" -version = "0.11.10" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b820744eb4dc9b57a3398183639c511b5a26d2ed702cedd3febaa1393caa22cc" +checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" dependencies = [ "bytes", - "getrandom 0.3.2", + "getrandom 0.3.3", + "lru-slab", "rand 0.9.1", "ring", "rustc-hash", "rustls", "rustls-pki-types", @@ -2019,13 +1912,13 @@ "web-time", ] [[package]] name = "quinn-udp" -version = "0.5.11" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "541d0f57c6ec747a90738a52741d3221f7960e8ac2f0ff4b1a63680e033b4ab5" +checksum = "ee4e529991f949c5e25755532370b8af5d114acae52326361d68d47af64aa842" dependencies = [ "cfg_aliases", "libc", "once_cell", "socket2 0.5.9", @@ -2093,38 +1986,29 @@ name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", ] [[package]] name = "rand_core" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.2", -] - -[[package]] -name = "rc-box" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897fecc9fac6febd4408f9e935e86df739b0023b625e610e0357535b9c8adad0" -dependencies = [ - "erasable", + "getrandom 0.3.3", ] [[package]] name = "redox_syscall" -version = "0.5.11" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" +checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", ] [[package]] name = "regex" version = "1.11.1" @@ -2202,32 +2086,23 @@ "url", "wasm-bindgen", "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots", + "webpki-roots 0.26.11", "windows-registry", ] -[[package]] -name = "rgb" -version = "0.8.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" -dependencies = [ - "bytemuck", -] - [[package]] 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.15", + "getrandom 0.2.16", "libc", "untrusted", "windows-sys 0.52.0", ] @@ -2263,26 +2138,26 @@ "quick-xml", ] [[package]] name = "rsstg" -version = "0.3.0" +version = "0.3.3" dependencies = [ "anyhow", "async-std", "atom_syndication", "chrono", "config", + "frankenstein", "futures", "futures-util", "lazy_static", "regex", "reqwest", "rss", "sedregex", "sqlx", - "teloxide", "thiserror 2.0.12", ] [[package]] name = "rustc-demangle" @@ -2314,35 +2189,35 @@ name = "rustix" version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "errno", "libc", "linux-raw-sys 0.4.15", "windows-sys 0.59.0", ] [[package]] name = "rustix" -version = "1.0.5" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "errno", "libc", "linux-raw-sys 0.9.4", "windows-sys 0.59.0", ] [[package]] name = "rustls" -version = "0.23.26" +version = "0.23.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0" +checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321" dependencies = [ "once_cell", "ring", "rustls-pki-types", "rustls-webpki", @@ -2359,22 +2234,23 @@ "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" dependencies = [ "web-time", + "zeroize", ] [[package]] name = "rustls-webpki" -version = "0.103.1" +version = "0.103.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" +checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" dependencies = [ "ring", "rustls-pki-types", "untrusted", ] @@ -2410,11 +2286,11 @@ name = "security-framework" version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "core-foundation", "core-foundation-sys", "libc", "security-framework-sys", ] @@ -2453,11 +2329,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] name = "serde_json" version = "1.0.140" @@ -2495,20 +2371,13 @@ name = "serde_with" version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" dependencies = [ - "base64", - "chrono", - "hex", - "indexmap 1.9.3", - "indexmap 2.9.0", "serde", "serde_derive", - "serde_json", "serde_with_macros", - "time", ] [[package]] name = "serde_with_macros" version = "3.12.0" @@ -2516,11 +2385,11 @@ checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] name = "sha1" version = "0.10.6" @@ -2532,13 +2401,13 @@ "digest", ] [[package]] name = "sha2" -version = "0.10.8" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", "digest", ] @@ -2547,19 +2416,10 @@ name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" -[[package]] -name = "signal-hook-registry" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" -dependencies = [ - "libc", -] - [[package]] name = "signature" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" @@ -2625,13 +2485,13 @@ "der", ] [[package]] name = "sqlx" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c3a85280daca669cfd3bcb68a337882a8bc57ec882f72c5d13a430613a738e" +checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" dependencies = [ "sqlx-core", "sqlx-macros", "sqlx-mysql", "sqlx-postgres", @@ -2638,13 +2498,13 @@ "sqlx-sqlite", ] [[package]] name = "sqlx-core" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f743f2a3cea30a58cd479013f75550e879009e3a02f616f18ca699335aa248c3" +checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" dependencies = [ "async-io 1.13.0", "async-std", "base64", "bytes", @@ -2655,13 +2515,13 @@ "event-listener 5.4.0", "futures-core", "futures-intrusive", "futures-io", "futures-util", - "hashbrown 0.15.2", + "hashbrown", "hashlink", - "indexmap 2.9.0", + "indexmap", "log", "memchr", "once_cell", "percent-encoding", "rustls", @@ -2670,31 +2530,31 @@ "sha2", "smallvec", "thiserror 2.0.12", "tracing", "url", - "webpki-roots", + "webpki-roots 0.26.11", ] [[package]] name = "sqlx-macros" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4200e0fde19834956d4252347c12a083bdcb237d7a1a1446bffd8768417dce" +checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" dependencies = [ "proc-macro2", "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] name = "sqlx-macros-core" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "882ceaa29cade31beca7129b6beeb05737f44f82dbe2a9806ecea5a7093d00b7" +checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" dependencies = [ "async-std", "dotenvy", "either", "heck", @@ -2707,24 +2567,23 @@ "sha2", "sqlx-core", "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.100", - "tempfile", + "syn 2.0.101", "url", ] [[package]] name = "sqlx-mysql" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0afdd3aa7a629683c2d750c2df343025545087081ab5942593a5288855b1b7a7" +checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", "base64", - "bitflags 2.9.0", + "bitflags 2.9.1", "byteorder", "bytes", "chrono", "crc", "digest", @@ -2757,17 +2616,17 @@ "whoami", ] [[package]] name = "sqlx-postgres" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0bedbe1bbb5e2615ef347a5e9d8cd7680fb63e77d9dafc0f29be15e53f1ebe6" +checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", "base64", - "bitflags 2.9.0", + "bitflags 2.9.1", "byteorder", "chrono", "crc", "dotenvy", "etcetera", @@ -2795,13 +2654,13 @@ "whoami", ] [[package]] name = "sqlx-sqlite" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c26083e9a520e8eb87a06b12347679b142dc2ea29e6e409f805644a7a979a5bc" +checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" dependencies = [ "atoi", "chrono", "flume", "futures-channel", @@ -2824,23 +2683,10 @@ name = "stable_deref_trait" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" -[[package]] -name = "stacker" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601f9201feb9b09c00266478bf459952b9ef9a6b94edb2f21eba14ab681a60a9" -dependencies = [ - "cc", - "cfg-if", - "libc", - "psm", - "windows-sys 0.59.0", -] - [[package]] name = "stringprep" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" @@ -2873,13 +2719,13 @@ "unicode-ident", ] [[package]] name = "syn" -version = "2.0.100" +version = "2.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] @@ -2893,26 +2739,26 @@ "futures-core", ] [[package]] name = "synstructure" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] name = "system-configuration" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "core-foundation", "system-configuration-sys", ] [[package]] @@ -2923,102 +2769,20 @@ dependencies = [ "core-foundation-sys", "libc", ] -[[package]] -name = "take_mut" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" - -[[package]] -name = "takecell" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20f34339676cdcab560c9a82300c4c2581f68b9369aedf0fae86f2ff9565ff3e" - -[[package]] -name = "teloxide" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17cb7c03a9217286fe11021dc72d5a674acbb0d3b24ba38d04f7efe7920e4948" -dependencies = [ - "aquamarine", - "bytes", - "derive_more", - "dptree", - "either", - "futures", - "log", - "mime", - "pin-project", - "serde", - "serde_json", - "teloxide-core", - "teloxide-macros", - "thiserror 2.0.12", - "tokio", - "tokio-stream", - "tokio-util", - "url", -] - -[[package]] -name = "teloxide-core" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2f70a3cd58c2b31ca899691b99573a40c6da713ab230bb78bbb4fb0b5c751a" -dependencies = [ - "bitflags 2.9.0", - "bytes", - "chrono", - "derive_more", - "either", - "futures", - "log", - "mime", - "once_cell", - "pin-project", - "rc-box", - "reqwest", - "rgb", - "serde", - "serde_json", - "serde_with", - "stacker", - "take_mut", - "takecell", - "thiserror 2.0.12", - "tokio", - "tokio-util", - "url", - "uuid", -] - -[[package]] -name = "teloxide-macros" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3118a980ed2ec11f73d9495a6606905bd74726e3ffe95a42fbeb187ded8fdbf4" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.100", -] - [[package]] name = "tempfile" -version = "3.19.1" +version = "3.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" dependencies = [ "fastrand 2.3.0", - "getrandom 0.3.2", + "getrandom 0.3.3", "once_cell", - "rustix 1.0.5", + "rustix 1.0.7", "windows-sys 0.59.0", ] [[package]] name = "thiserror" @@ -3044,11 +2808,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] name = "thiserror-impl" version = "2.0.12" @@ -3055,49 +2819,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", -] - -[[package]] -name = "time" -version = "0.3.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" -dependencies = [ - "deranged", - "itoa", - "num-conv", - "powerfmt", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" - -[[package]] -name = "time-macros" -version = "0.2.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" -dependencies = [ - "num-conv", - "time-core", + "syn 2.0.101", ] [[package]] name = "tinystr" -version = "0.7.6" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" dependencies = [ "displaydoc", "zerovec", ] @@ -3116,20 +2849,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.44.2" +version = "1.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" +checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165" dependencies = [ "backtrace", "bytes", "libc", "mio", "pin-project-lite", - "signal-hook-registry", "socket2 0.5.9", "windows-sys 0.52.0", ] [[package]] @@ -3162,26 +2894,15 @@ "futures-util", "thiserror 1.0.69", "tokio", ] -[[package]] -name = "tokio-stream" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - [[package]] name = "tokio-util" -version = "0.7.14" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", @@ -3188,36 +2909,36 @@ "tokio", ] [[package]] name = "toml" -version = "0.8.20" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" +checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" dependencies = [ "serde", "serde_spanned", "toml_datetime", "toml_edit", ] [[package]] name = "toml_datetime" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.24" +version = "0.22.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" +checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" dependencies = [ - "indexmap 2.9.0", + "indexmap", "serde", "serde_spanned", "toml_datetime", "winnow", ] @@ -3267,11 +2988,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] name = "tracing-core" version = "0.1.33" @@ -3324,16 +3045,10 @@ name = "unicode-properties" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - [[package]] name = "untrusted" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" @@ -3345,34 +3060,18 @@ checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", "percent-encoding", - "serde", -] - -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" +] [[package]] name = "utf8_iter" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" -[[package]] -name = "uuid" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" -dependencies = [ - "getrandom 0.3.2", -] - [[package]] name = "value-bag" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "943ce29a8a743eb10d6082545d861b24f9d1b160b7d741e0f2cdf726bec909c5" @@ -3445,11 +3144,11 @@ dependencies = [ "bumpalo", "log", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" @@ -3480,11 +3179,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] @@ -3529,13 +3228,22 @@ "wasm-bindgen", ] [[package]] name = "webpki-roots" -version = "0.26.8" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.0", +] + +[[package]] +name = "webpki-roots" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9" +checksum = "2853738d1cc4f2da3a225c18ec6c3721abb31961096e9dbf5ab35fa88b19cfdb" dependencies = [ "rustls-pki-types", ] [[package]] @@ -3570,19 +3278,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-core" -version = "0.61.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ "windows-implement", "windows-interface", "windows-link", "windows-result", - "windows-strings 0.4.0", + "windows-strings 0.4.2", ] [[package]] name = "windows-implement" version = "0.60.0" @@ -3589,11 +3297,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] name = "windows-interface" version = "0.59.1" @@ -3600,11 +3308,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] name = "windows-link" version = "0.1.1" @@ -3622,13 +3330,13 @@ "windows-targets 0.53.0", ] [[package]] name = "windows-result" -version = "0.3.2" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ "windows-link", ] [[package]] @@ -3640,13 +3348,13 @@ "windows-link", ] [[package]] name = "windows-strings" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ "windows-link", ] [[package]] @@ -3861,13 +3569,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.7.6" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10" +checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" dependencies = [ "memchr", ] [[package]] @@ -3874,67 +3582,61 @@ name = "wit-bindgen-rt" version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.9.0", -] - -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + "bitflags 2.9.1", +] [[package]] name = "writeable" -version = "0.5.5" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" [[package]] name = "yoke" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" dependencies = [ "serde", "stable_deref_trait", "yoke-derive", "zerofrom", ] [[package]] name = "yoke-derive" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.24" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" +checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.24" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" +checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] name = "zerofrom" version = "0.1.6" @@ -3950,36 +3652,47 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", "synstructure", ] [[package]] name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] [[package]] name = "zerovec" -version = "0.10.4" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" dependencies = [ "yoke", "zerofrom", "zerovec-derive", ] [[package]] name = "zerovec-derive" -version = "0.10.3" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] Index: Cargo.toml ================================================================== --- Cargo.toml +++ Cargo.toml @@ -1,27 +1,26 @@ [package] name = "rsstg" -version = "0.3.0" +version = "0.3.3" 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" ] } chrono = "0.4.38" config = { version = "0.15", default-features = false, features = [ "toml" ] } +frankenstein = { version = "0.40.0", features = [ "client-reqwest" ] } futures = "0.3.30" futures-util = "0.3.30" lazy_static = "1.5.0" regex = "1.10.6" reqwest = { version = "0.12.7", features = [ "brotli", "socks", "deflate" ]} rss = "2.0.9" sedregex = "0.2.5" sqlx = { version = "0.8", features = [ "postgres", "runtime-async-std-rustls", "chrono", "macros" ], default-features = false } -#telegram-bot = { git = "https://github.com/kworr/telegram-bot", features = [ "rustls" ], default-features = false } -teloxide = { version = "0.15.0", default-features = false, features = [ "ctrlc_handler", "macros", "rustls" ] } thiserror = "2.0.0" [profile.release] lto = true codegen-units = 1 Index: rsstg.sql ================================================================== --- rsstg.sql +++ rsstg.sql @@ -8,11 +8,12 @@ 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); + 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 ( Index: src/command.rs ================================================================== --- src/command.rs +++ src/command.rs @@ -1,74 +1,60 @@ use crate::core::Core; +use std::borrow::Cow; + use anyhow::{ bail, Context, Result +}; +use frankenstein::{ + methods::{ + GetChatAdministratorsParams, + GetChatParams, + }, + types::{ + ChatId, + ChatMember, + }, + AsyncTelegramApi, + ParseMode, }; use lazy_static::lazy_static; use regex::Regex; use sedregex::ReplaceCommand; -use std::borrow::Cow; -use teloxide::{ - Bot, - dispatching::dialogue::GetChatId, - payloads::GetChatAdministrators, - requests::{ - Requester, - ResponseResult - }, - types::{ - Message, - UserId, - }, - utils::command::BotCommands, -}; 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(); } -#[derive(BotCommands, Clone)] -#[command(rename_rule = "lowercase", description = "Supported commands:")] -enum Command { - #[command(description = "display this help.")] - Help, - #[command(description = "Does nothing.")] - Start, - #[commant(description = "List active subscriptions.")] - List, -} - -pub async fn cmd_handler(bot: Bot, msg: Message, cmd: Command) -> ResponseResult<()> { - match cmd { - Command::Help => bot.send_message(msg.chat.id, Command::descriptions().to_string()).await?, - Command::Start => bot.send_message(msg.chat.id, - "We are open. Probably. Visit [channel](https://t.me/rsstg_bot_help/3) for details.").await?, - Command::List => bot.send_message(msg.chat.id, core.list(msg.from).await?).await?, - }; - Ok(()) -} - -pub async fn list(core: &Core, sender: UserId) -> Result<()> { - core.send(core.list(sender).await?, Some(sender), Some(telegram_bot::types::ParseMode::MarkdownV2)).await?; - Ok(()) -} - -pub async fn command(core: &Core, sender: telegram_bot::UserId, command: Vec<&str>) -> Result<()> { +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 = match &command[1].parse::() { 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.")?, - "/clean" => core.clean(number, sender).await?, - "/enable" => core.enable(number, sender).await?.into(), - "/delete" => core.delete(number, sender).await?, - "/disable" => core.disable(number, sender).await?.into(), + .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 { @@ -75,42 +61,42 @@ core.send("This command needs a number.", Some(sender), None).await?; } Ok(()) } -pub async fn update(core: &Core, sender: telegram_bot::UserId, command: Vec<&str>) -> Result<()> { +pub async fn update(core: &mut Core, sender: i64, command: Vec<&str>) -> Result<()> { let mut source_id: Option = 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)?; source_id = Some(next_word.parse::() - .context(format!("I need a number, but got {}.", next_word))?); + .context(format!("I need a number, but got {next_word}."))?); }, "/add" => {}, - _ => bail!("Passing {} is not possible here.", first_word), + _ => bail!("Passing {first_word} is not possible here."), }; 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) { - bail!("Usernames should be something like \"@\\[a\\-zA\\-Z]\\[a\\-zA\\-Z0\\-9\\_]+\", aren't they?\nNot {:?}", &channel); + bail!("Usernames should be something like \"@\\[a\\-zA\\-Z]\\[a\\-zA\\-Z0\\-9\\_]+\", aren't they?\nNot {channel:?}"); }; if ! RE_LINK.is_match(url) { - bail!("Link should be a link to atom/rss feed, something like \"https://domain/path\".\nNot {:?}", &url); + bail!("Link should be a link to atom/rss feed, something like \"https://domain/path\".\nNot {url:?}"); } let iv_hash = match iv_hash { Some(hash) => { match *hash { "-" => None, thing => { if ! RE_IV_HASH.is_match(thing) { - bail!("IV hash should be 14 hex digits.\nNot {:?}", thing); + bail!("IV hash should be 14 hex digits.\nNot {thing:?}"); }; Some(thing) }, } }, @@ -126,22 +112,32 @@ } } }, None => None, }; - let channel_id = i64::from(core.request(telegram_bot::GetChat::new(telegram_bot::ChatRef::ChannelUsername(channel.to_string()))).await?.id()); - let chan_adm = core.request(telegram_bot::GetChatAdministrators::new(telegram_bot::ChatRef::ChannelUsername(channel.to_string()))).await - .context("Sorry, I have no access to that chat.")?; + let chat_id = ChatId::String((*channel).into()); + let channel_id = core.tg.get_chat(&GetChatParams { chat_id: chat_id.clone() }).await?.result.id; + let chan_adm = core.tg.get_chat_administrators(&GetChatAdministratorsParams { chat_id }).await + .context("Sorry, I have no access to that chat.")?.result; let (mut me, mut user) = (false, false); for admin in chan_adm { - if admin.user.id == core.my.id { + let member_id = match admin { + ChatMember::Creator(member) => member.user.id, + ChatMember::Administrator(member) => member.user.id, + ChatMember::Left(_) + | ChatMember::Kicked(_) + | ChatMember::Member(_) + | ChatMember::Restricted(_) => continue, + } as i64; + if member_id == core.me.id as i64 { me = true; }; - if admin.user.id == sender { + 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."); }; - core.send(core.update(source_id, channel, channel_id, url, iv_hash, url_re, sender).await?, Some(sender), None).await?; + 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(()) } Index: src/core.rs ================================================================== --- src/core.rs +++ src/core.rs @@ -1,19 +1,9 @@ -use anyhow::{anyhow, bail, Context, Result}; -use async_std::task; -use chrono::DateTime; -use sqlx::postgres::PgPoolOptions; -use teloxide::{ - Bot, - payloads::SendMessage, - requests::Requester, - types::{ - Me, - UserId, - }, +use crate::{ + command, + sql::Db, }; -use thiserror::Error; use std::{ borrow::Cow, collections::{ BTreeMap, @@ -24,10 +14,35 @@ Arc, Mutex }, }; +use anyhow::{ + anyhow, + bail, + Result, +}; +use async_std::task; +use chrono::DateTime; +use frankenstein::{ + AsyncTelegramApi, + Error as FrankError, + ParseMode, + client_reqwest::Bot, + methods::{ + GetUpdatesParams, + SendMessageParams + }, + types::{ + AllowedUpdate, + MessageEntityType, + User, + }, + updates::UpdateContent, +}; +use thiserror::Error; + #[derive(Error, Debug)] pub enum RssError { // #[error(transparent)] // Tg(#[from] TgError), #[error(transparent)] @@ -34,107 +49,153 @@ Int(#[from] TryFromIntError), } #[derive(Clone)] pub struct Core { - owner_chat: UserId, + owner_chat: i64, + max_delay: u16, pub tg: Bot, - pub my: Me, - pool: sqlx::Pool, + pub me: User, + pub db: Db, sources: Arc>>>, http_client: reqwest::Client, } impl Core { - pub fn new(settings: config::Config) -> Result> { - let owner: u64 = settings.get_int("owner")?.try_into()?; + pub async fn new(settings: config::Config) -> Result { + let owner_chat = settings.get_int("owner")?; let api_key = settings.get_string("api_key")?; - let tg = Bot::new(api_key); - let tg_cloned = tg.clone(); + 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 core = Arc::new(Core { + let me = tg.get_me().await?; + let me = me.result; + let core = Core { tg, - my: task::block_on(async { - tg_cloned.get_me().await - })?, - owner_chat: UserId(owner), - pool: PgPoolOptions::new() - .max_connections(5) - .acquire_timeout(std::time::Duration::new(300, 0)) - .idle_timeout(std::time::Duration::new(60, 0)) - .connect_lazy(&settings.get_string("pg")?)?, + me, + owner_chat, + db: Db::new(&settings.get_string("pg")?)?, sources: Arc::new(Mutex::new(HashSet::new())), http_client, - }); - /* let clone = core.clone(); + max_delay: 60, + }; + 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); + 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 fn stream(&self) -> Result<()> { - let mut last_update: Option = None; - loop { - let updates = self.tg.get_updates(last_update, None, 300, Some(vec!["message"])); - } - Ok(()) - } - - /* - pub async fn send<'a, S>(&self, msg: S, target: Option, mode: Option) -> Result<()> - where S: Into> { - let mode = mode.unwrap_or(telegram_bot::types::ParseMode::Html); - let target = target.unwrap_or(self.owner_chat); - self.request(telegram_bot::SendMessage::new(target, msg).parse_mode(mode)).await?; - Ok(()) - } */ - - /* pub async fn request (&self, req: Req) -> Result<::Type, RssError> { - loop { - let res = self.tg.send(&req).await; - match res { - Ok(_) => return Ok(res?), - Err(err) => { - match &err { - TgError::Raw(TgrError::TelegramError { description: _, parameters: Some(params) }) => { - if let Some(delay) = params.retry_after { - println!("Throttled, waiting {} senconds.", delay); - task::sleep(std::time::Duration::from_secs(delay.try_into()?)).await; - } else { - return Err(err.into()); - } - }, - _ => return Err(err.into()), - } - }, - }; - } - } */ - - /* pub async fn check(&self, id: &i32, owner: S, real: bool) -> Result> - where S: Into { - let owner = owner.into(); - let mut posted: i32 = 0; - let mut conn = self.pool.acquire().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]), + }; + loop { + let updates = self.tg.get_updates(¶ms).await?.result; + if updates.is_empty() { + offset = 0; + params.offset = None; + continue; + } + for update in updates { + if i64::from(update.update_id) >= offset { + offset = i64::from(update.update_id) + 1; + params.offset = Some(offset); + } + if let UpdateContent::Message(msg) = update.content { + if let Some(text) = msg.text { + if let Some(entities) = msg.entities { + let chars: Vec = text.encode_utf16().collect(); + for entity in entities { + if entity.type_field == MessageEntityType::BotCommand && entity.offset != 0 { + bail!("commands should be at message start"); + }; + let cmd = String::from_utf16_lossy(&chars[entity.offset as usize..entity.length as usize]); + let words: Vec<&str> = text.split_whitespace().collect(); + let res = match cmd.as_ref() { + "/check" | "/clean" | "/enable" | "/delete" | "/disable" => command::command(self, msg.chat.id, words).await, + "/start" => command::start(self, msg.chat.id).await, + "/list" => command::list(self, msg.chat.id).await, + "/add" | "/update" => command::update(self, msg.chat.id, words).await, + any => Err(anyhow!("Unknown command: {any}")), + }; + if let Err(err) = res { + if let Err(err2) = self.send(format!("\\#error\n```\n{err:?}\n```"), + Some(msg.chat.id), + Some(ParseMode::MarkdownV2) + ).await{ + dbg!(err2); + }; + } + }; + }; + }; + }; + } + } + } + + pub async fn send (&self, msg: S, target: Option, mode: Option) -> Result<()> + where S: Into { + let msg = msg.into(); + + let mode = mode.unwrap_or(ParseMode::Html); + let target = target.unwrap_or(self.owner_chat); + let send = SendMessageParams::builder() + .chat_id(target) + .text(msg) + .parse_mode(mode) + .build(); + loop { + match self.tg.send_message(&send).await { + Ok(_) => break, + Err(err) => match err { + FrankError::Api(ref resp) => { + if resp.error_code == 429 { + let mut my_delay = self.max_delay; + if let Some(params) = resp.parameters { + if let Some(delay) = params.retry_after { + if delay < my_delay { + my_delay = delay; + } + } + } + task::sleep(std::time::Duration::from_secs(my_delay.into())).await; + } else { + return Err(err.into()); + } + }, + _ => return Err(err.into()), + }, + } + } + Ok(()) + } + + pub async fn check (&mut self, id: &i32, owner: i64, real: bool) -> Result { + 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(), @@ -145,15 +206,15 @@ }, } }; let count = Arc::strong_count(&id); if count == 2 { - let source = sqlx::query!("select source_id, channel_id, url, iv_hash, owner, url_re from rsstg_source where source_id = $1 and owner = $2", - *id, owner).fetch_one(&mut *conn).await?; + let source = conn.get_source(*id, owner).await?; + conn.set_scrape(*id).await?; let destination = match real { - true => telegram_bot::UserId::new(source.channel_id), - false => telegram_bot::UserId::new(source.owner), + true => source.channel_id, + false => source.owner, }; let mut this_fetch: Option> = None; let mut posts: BTreeMap, String> = BTreeMap::new(); let response = self.http_client.get(&source.url).send().await?; @@ -181,148 +242,58 @@ let url = item.links()[0].href(); posts.insert(*date, url.to_string()); }; }, Err(err) => { - bail!("Unsupported or mangled content:\n{:?}\n{:#?}\n{:#?}\n", &source.url, err, status) + bail!("Unsupported or mangled content:\n{:?}\n{err:#?}\n{status:#?}\n", &source.url) }, } }, rss::Error::Eof => (), - _ => bail!("Unsupported or mangled content:\n{:?}\n{:#?}\n{:#?}\n", &source.url, err, status) + _ => bail!("Unsupported or mangled content:\n{:?}\n{err:#?}\n{status:#?}\n", &source.url) } }; for (date, url) in posts.iter() { let post_url: Cow = match source.url_re { Some(ref x) => sedregex::ReplaceCommand::new(x)?.execute(url), None => url.into(), }; - if let Some(exists) = sqlx::query!("select exists(select true from rsstg_post where url = $1 and source_id = $2) as exists;", - &post_url, *id).fetch_one(&mut *conn).await?.exists { + 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.request( match &source.iv_hash { - Some(hash) => telegram_bot::SendMessage::new(destination, format!(" {0}", &post_url, hash)), - None => telegram_bot::SendMessage::new(destination, format!("{}", post_url)), - }.parse_mode(telegram_bot::types::ParseMode::Html)).await - .context("Can't post message:")?; - sqlx::query!("insert into rsstg_post (source_id, posted, url) values ($1, $2, $3);", - *id, date, &post_url).execute(&mut *conn).await?; + self.send( match &source.iv_hash { + Some(hash) => format!(" {post_url}"), + None => format!("{post_url}"), + }, Some(destination), Some(ParseMode::Html)).await?; + conn.add_post(*id, date, &post_url).await?; }; }; posted += 1; }; posts.clear(); }; - sqlx::query!("update rsstg_source set last_scrape = now() where source_id = $1;", - *id).execute(&mut *conn).await?; - Ok(format!("Posted: {}", &posted).into()) - } */ - - /* pub async fn delete(&self, source_id: &i32, owner: S) -> Result> - where S: Into { - let owner = owner.into(); - - match sqlx::query!("delete from rsstg_source where source_id = $1 and owner = $2;", - source_id, owner).execute(&mut *self.pool.acquire().await?).await?.rows_affected() { - 0 => { Ok("No data found found.".into()) }, - x => { Ok(format!("{} sources removed.", x).into()) }, - } - } */ - - /* pub async fn clean(&self, source_id: &i32, owner: S) -> Result> - where S: Into { - let owner = owner.into(); - - match sqlx::query!("delete from rsstg_post p using rsstg_source s where p.source_id = $1 and owner = $2 and p.source_id = s.source_id;", - source_id, owner).execute(&mut *self.pool.acquire().await?).await?.rows_affected() { - 0 => { Ok("No data found found.".into()) }, - x => { Ok(format!("{} posts purged.", x).into()) }, - } - } */ - - /* pub async fn enable(&self, source_id: &i32, owner: S) -> Result<&str> - where S: Into { - let owner = owner.into(); - - match sqlx::query!("update rsstg_source set enabled = true where source_id = $1 and owner = $2", - source_id, owner).execute(&mut *self.pool.acquire().await?).await?.rows_affected() { - 1 => { Ok("Source enabled.") }, - 0 => { Ok("Source not found.") }, - _ => { Err(anyhow!("Database error.")) }, - } - } */ - - /* pub async fn disable(&self, source_id: &i32, owner: S) -> Result<&str> - where S: Into { - let owner = owner.into(); - - match sqlx::query!("update rsstg_source set enabled = false where source_id = $1 and owner = $2", - source_id, owner).execute(&mut *self.pool.acquire().await?).await?.rows_affected() { - 1 => { Ok("Source disabled.") }, - 0 => { Ok("Source not found.") }, - _ => { Err(anyhow!("Database error.")) }, - } - } */ - - /* pub async fn update(&self, update: Option, channel: &str, channel_id: i64, url: &str, iv_hash: Option<&str>, url_re: Option<&str>, owner: S) -> Result<&str> - where S: Into { - let owner = owner.into(); - let mut conn = self.pool.acquire().await?; - - 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", - id, channel_id, url, iv_hash, owner, channel, url_re).execute(&mut *conn).await - }, - None => { - sqlx::query!("insert into rsstg_source (channel_id, url, iv_hash, owner, channel, url_re) values ($1, $2, $3, $4, $5, $6)", - channel_id, url, iv_hash, owner, channel, url_re).execute(&mut *conn).await - }, - } { - Ok(_) => Ok(match update { - Some(_) => "Channel updated.", - None => "Channel added.", - }), - Err(sqlx::Error::Database(err)) => { - match err.downcast::().routine() { - Some("_bt_check_unique", ) => { - Ok("Duplicate key.") - }, - Some(_) => { - Ok("Database error.") - }, - None => { - Ok("No database error extracted.") - }, - } - }, - Err(err) => { - bail!("Sorry, unknown error:\n{:#?}\n", err); - }, - } - } - - async fn autofetch(&self) -> Result { - let mut delay = chrono::Duration::minutes(1); - let now = chrono::Local::now(); - let mut queue = sqlx::query!(r#"select source_id, next_fetch as "next_fetch: DateTime", owner from rsstg_order natural left join rsstg_source where next_fetch < now() + interval '1 minute';"#) - .fetch_all(&mut *self.pool.acquire().await?).await?; - for row in queue.iter() { + Ok(format!("Posted: {posted}")) + } + + async fn autofetch(&mut self) -> Result { + 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 clone = Core { - owner_chat: telegram_bot::UserId::new(owner), + 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 { - dbg!("Check error: {}", err); + if let Err(err) = clone.send(&format!("šŸ›‘ {err:?}"), None, None).await { + eprintln!("Check error: {err:?}"); // clone.disable(&source_id, owner).await.unwrap(); }; }; }); } @@ -329,33 +300,28 @@ } else if next_fetch - now < delay { delay = next_fetch - now; } } }; - queue.clear(); Ok(delay.to_std()?) } - pub async fn list(&self, owner: S) -> Result - where S: Into { - let owner = owner.into(); - + pub async fn list (&mut self, owner: i64) -> Result { let mut reply: Vec> = vec![]; reply.push("Channels:".into()); - let rows = sqlx::query!("select source_id, channel, enabled, url, iv_hash, url_re from rsstg_source where owner = $1 order by source_id", - owner).fetch_all(&mut *self.pool.acquire().await?).await?; - for row in rows.iter() { + 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()); + reply.push(format!("IV: `{hash}`").into()); } if let Some(re) = &row.url_re { - reply.push(format!("RE: `{}`", re).into()); + reply.push(format!("RE: `{re}`").into()); } }; Ok(reply.join("\n")) - } */ + } } Index: src/main.rs ================================================================== --- src/main.rs +++ src/main.rs @@ -3,60 +3,21 @@ #![warn(missing_docs)] mod command; mod core; +mod sql; use anyhow::Result; -use async_std::task; -use async_std::stream::StreamExt; #[async_std::main] async fn main() -> Result<()> { let settings = config::Config::builder() .add_source(config::File::with_name("rsstg")) .build()?; - let core = core::Core::new(settings)?; - - let mut stream = core.stream(); - stream.allowed_updates(&[telegram_bot::AllowedUpdate::Message]); - - task::block_on(async { - let mut reply_to: Option; - loop { - reply_to = None; - match stream.next().await { - Some(update) => { - if let Err(err) = handle(update?, &core, &reply_to).await { - core.send(&format!("šŸ›‘ {err:?}"), reply_to, None).await?; - }; - }, - None => { - core.send("šŸ›‘ None error.", None, None).await?; - } - }; - } - }) -} - -async fn handle(update: telegram_bot::Update, core: &core::Core, mut _reply_to: &Option) -> Result<()> { - if let telegram_bot::UpdateKind::Message(message) = update.kind { - if let Some(from) = message.from { - if let telegram_bot::MessageKind::Text{ ref data, .. } = message.kind { - let sender = from.id; - let words: Vec<&str> = data.split_whitespace().collect(); - if let Err(err) = 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, - _ => Ok(()), - } { - core.send(format!("šŸ›‘ {:?}", err), Some(sender), None).await?; - }; - }; - }; - }; + let mut core = core::Core::new(settings).await?; + + core.stream().await?; Ok(()) } ADDED src/sql.rs Index: src/sql.rs ================================================================== --- /dev/null +++ src/sql.rs @@ -0,0 +1,207 @@ +use std::borrow::Cow; + +use anyhow::{ + Result, + bail, +}; +use chrono::{ + DateTime, + FixedOffset, + Local, +}; +use sqlx::{ + Pool, + Postgres, + Row, + postgres::PgPoolOptions, + pool::PoolConnection, +}; + +#[derive(sqlx::FromRow, Debug)] +pub struct List { + pub source_id: i32, + pub channel: String, + pub enabled: bool, + pub url: String, + pub iv_hash: Option, + pub url_re: Option, +} + +#[derive(sqlx::FromRow, Debug)] +pub struct Source { + pub channel_id: i64, + pub url: String, + pub iv_hash: Option, + pub owner: i64, + pub url_re: Option, +} + +#[derive(sqlx::FromRow)] +pub struct Queue { + pub source_id: Option, + pub next_fetch: Option>, + pub owner: Option, +} + +#[derive(Clone)] +pub struct Db { + pool: sqlx::Pool, +} + +pub struct Conn{ + conn: PoolConnection, +} + +impl Db { + pub fn new (pguri: &str) -> Result { + Ok(Db{ + pool: PgPoolOptions::new() + .max_connections(5) + .acquire_timeout(std::time::Duration::new(300, 0)) + .idle_timeout(std::time::Duration::new(60, 0)) + .connect_lazy(pguri)?, + }) + } + + pub async fn begin(&mut self) -> Result { + Conn::new(&mut self.pool).await + } +} + +impl Conn { + pub async fn new (pool: &mut Pool) -> Result { + let conn = pool.acquire().await?; + Ok(Conn{ + conn, + }) + } + + pub async fn add_post (&mut self, id: i32, date: &DateTime, post_url: &str) -> Result<()> { + sqlx::query("insert into rsstg_post (source_id, posted, url) values ($1, $2, $3);") + .bind(id) + .bind(date) + .bind(post_url) + .execute(&mut *self.conn).await?; + Ok(()) + } + + pub async fn clean (&mut self, source_id: i32, owner: i64) -> Result> { + match sqlx::query("delete from rsstg_post p using rsstg_source s where p.source_id = $1 and owner = $2 and p.source_id = s.source_id;") + .bind(source_id) + .bind(owner) + .execute(&mut *self.conn).await?.rows_affected() { + 0 => { Ok("No data found found.".into()) }, + x => { Ok(format!("{x} posts purged.").into()) }, + } + } + + pub async fn delete (&mut self, source_id: i32, owner: i64) -> Result> { + match sqlx::query("delete from rsstg_source where source_id = $1 and owner = $2;") + .bind(source_id) + .bind(owner) + .execute(&mut *self.conn).await?.rows_affected() { + 0 => { Ok("No data found found.".into()) }, + x => { Ok(format!("{} sources removed.", x).into()) }, + } + } + + pub async fn disable (&mut self, source_id: i32, owner: i64) -> Result<&str> { + match sqlx::query("update rsstg_source set enabled = false where source_id = $1 and owner = $2") + .bind(source_id) + .bind(owner) + .execute(&mut *self.conn).await?.rows_affected() { + 1 => { Ok("Source disabled.") }, + 0 => { Ok("Source not found.") }, + _ => { bail!("Database error.") }, + } + } + + pub async fn enable (&mut self, source_id: i32, owner: i64) -> Result<&str> { + match sqlx::query("update rsstg_source set enabled = true where source_id = $1 and owner = $2") + .bind(source_id) + .bind(owner) + .execute(&mut *self.conn).await?.rows_affected() { + 1 => { Ok("Source enabled.") }, + 0 => { Ok("Source not found.") }, + _ => { bail!("Database error.") }, + } + } + + pub async fn exists (&mut self, post_url: &str, id: i32) -> Result> { + let row = sqlx::query("select exists(select true from rsstg_post where url = $1 and source_id = $2) as exists;") + .bind(post_url) + .bind(id) + .fetch_one(&mut *self.conn).await?; + let exists: Option = row.try_get("exists")?; + Ok(exists) + } + + pub async fn get_queue (&mut self) -> Result> { + let block: Vec = sqlx::query_as("select source_id, next_fetch, owner from rsstg_order natural left join rsstg_source where next_fetch < now() + interval '1 minute';") + .fetch_all(&mut *self.conn).await?; + Ok(block) + } + + pub async fn get_list (&mut self, owner: i64) -> Result> { + let source: Vec = sqlx::query_as("select source_id, channel, enabled, url, iv_hash, url_re from rsstg_source where owner = $1 order by source_id") + .bind(owner) + .fetch_all(&mut *self.conn).await?; + Ok(source) + } + + pub async fn get_source (&mut self, id: i32, owner: i64) -> Result { + let source: Source = sqlx::query_as("select channel_id, url, iv_hash, owner, url_re from rsstg_source where source_id = $1 and owner = $2") + .bind(id) + .bind(owner) + .fetch_one(&mut *self.conn).await?; + Ok(source) + } + + pub async fn set_scrape (&mut self, id: i32) -> Result<()> { + sqlx::query("update rsstg_source set last_scrape = now() where source_id = $1;") + .bind(id) + .execute(&mut *self.conn).await?; + Ok(()) + } + + pub async fn update (&mut self, update: Option, channel: &str, channel_id: i64, url: &str, iv_hash: Option<&str>, url_re: Option<&str>, owner: i64) -> Result<&str> { + 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") + .bind(id) + }, + None => { + sqlx::query("insert into rsstg_source (channel_id, url, iv_hash, owner, channel, url_re) values ($1, $2, $3, $4, $5, $6)") + }, + } + .bind(channel_id) + .bind(url) + .bind(iv_hash) + .bind(owner) + .bind(channel) + .bind(url_re) + .execute(&mut *self.conn).await + { + Ok(_) => Ok(match update { + Some(_) => "Channel updated.", + None => "Channel added.", + }), + Err(sqlx::Error::Database(err)) => { + match err.downcast::().routine() { + Some("_bt_check_unique", ) => { + Ok("Duplicate key.") + }, + Some(_) => { + Ok("Database error.") + }, + None => { + Ok("No database error extracted.") + }, + } + }, + Err(err) => { + bail!("Sorry, unknown error:\n{err:#?}\n"); + }, + } + } +}