Index: .github/workflows/rust-clippy.yml ================================================================== --- .github/workflows/rust-clippy.yml +++ .github/workflows/rust-clippy.yml @@ -1,29 +1,28 @@ -name: rust-clippy analyze - +name: rust-ci on: push -# Make sure CI fails on all warnings, including Clippy lints +# sccache enable for rust/C builds env: - RUSTFLAGS: "-Dwarnings" + SCCACHE_GHA_ENABLED: "true" + RUSTC_WRAPPER: "sccache" jobs: - rust-clippy-test: - name: Run rust-clippy analyzing + rust-ci-run: + name: Run rust-clippy analyzing and tests runs-on: ubuntu-latest permissions: contents: read - security-events: write - steps: - - name: Checkout code - uses: actions/checkout@v6 - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@stable - - - uses: Swatinem/rust-cache@v2 - + steps: + # SETUP + - uses: actions/checkout@v6 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - uses: mozilla-actions/sccache-action@v0.0.9 + + # TESTS - name: Run tests run: cargo test --all-targets --all-features + # CLIPPY - name: Run rust-clippy - run: cargo clippy --all-targets --all-features + run: cargo clippy --all-targets --all-features -- -D warnings Index: Cargo.lock ================================================================== --- Cargo.lock +++ Cargo.lock @@ -72,26 +72,25 @@ "tokio", ] [[package]] name = "async-compression" -version = "0.4.36" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98ec5f6c2f8bc326c994cb9e241cc257ddaba9afa8555a43cffbb5dd86efaa37" +checksum = "d0f9ee0f6e02ffd7ad5816e9464499fba7b3effd01123b515c41d1697c43dad1" dependencies = [ "compression-codecs", "compression-core", - "futures-core", "pin-project-lite", "tokio", ] [[package]] name = "async-executor" -version = "1.13.3" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497c00e0fd83a72a79a39fcbd8e3e2f055d6f6c7e025f3b3d91f4f8e76527fb8" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" dependencies = [ "async-task", "concurrent-queue", "fastrand", "futures-lite", @@ -168,13 +167,13 @@ "rustix", ] [[package]] name = "async-signal" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c" +checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485" dependencies = [ "async-io", "async-lock", "atomic-waker", "cfg-if", @@ -249,23 +248,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.15.2" +version = "1.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a88aab2464f1f25453baa7a07c84c5b7684e274054ba06817f382357f77a288" +checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f" dependencies = [ "aws-lc-sys", "zeroize", ] [[package]] name = "aws-lc-sys" -version = "0.35.0" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45afffdee1e7c9126814751f88dddc747f41d91da16c9551a0f1e8a11e788a1" +checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7" dependencies = [ "cc", "cmake", "dunce", "fs_extra", @@ -277,19 +276,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.8.2" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d809780667f4410e7c41b07f52439b94d2bdf8528eeedc287fa38d3b7f95d82" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "bitflags" -version = "2.10.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" dependencies = [ "serde_core", ] [[package]] @@ -335,31 +334,31 @@ "alloc-stdlib", ] [[package]] name = "bumpalo" -version = "3.19.1" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "cc" -version = "1.2.51" +version = "1.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203" +checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" dependencies = [ "find-msvc-tools", "jobserver", "libc", "shlex", @@ -383,13 +382,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.42" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ "iana-time-zone", "js-sys", "num-traits", "serde", @@ -397,13 +396,13 @@ "windows-link", ] [[package]] name = "cmake" -version = "0.1.57" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" dependencies = [ "cc", ] [[package]] @@ -416,13 +415,13 @@ "memchr", ] [[package]] name = "compression-codecs" -version = "0.4.35" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0f7ac3e5b97fdce45e8922fb05cae2c37f7bbd63d30dd94821dacfd8f3f2bf2" +checksum = "eb7b51a7d9c967fc26773061ba86150f19c50c0d65c887cb1fbe295fd16619b7" dependencies = [ "brotli", "compression-core", "flate2", ] @@ -442,13 +441,13 @@ "crossbeam-utils", ] [[package]] name = "config" -version = "0.15.19" +version = "0.15.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b30fa8254caad766fc03cb0ccae691e14bf3bd72bfff27f72802ce729551b3d6" +checksum = "8e68cfe19cd7d23ffde002c24ffa5cda73931913ef394d5eaaa32037dc940c0c" dependencies = [ "pathdiff", "serde_core", "toml", "winnow", @@ -554,16 +553,16 @@ "darling_macro 0.20.11", ] [[package]] name = "darling" -version = "0.21.3" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" dependencies = [ - "darling_core 0.21.3", - "darling_macro 0.21.3", + "darling_core 0.23.0", + "darling_macro 0.23.0", ] [[package]] name = "darling_core" version = "0.20.11" @@ -578,15 +577,14 @@ "syn", ] [[package]] name = "darling_core" -version = "0.21.3" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" dependencies = [ - "fnv", "ident_case", "proc-macro2", "quote", "strsim", "syn", @@ -603,15 +601,15 @@ "syn", ] [[package]] name = "darling_macro" -version = "0.21.3" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ - "darling_core 0.21.3", + "darling_core 0.23.0", "quote", "syn", ] [[package]] @@ -787,25 +785,25 @@ "pin-project-lite", ] [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "find-msvc-tools" -version = "0.1.6" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "flate2" -version = "1.1.5" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", "miniz_oxide", ] @@ -847,13 +845,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" [[package]] name = "futures" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", "futures-executor", "futures-io", @@ -862,29 +860,29 @@ "futures-util", ] [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", ] [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", "futures-util", ] @@ -900,13 +898,13 @@ "parking_lot", ] [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-lite" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -919,46 +917,45 @@ "pin-project-lite", ] [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", "futures-io", "futures-macro", "futures-sink", "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] [[package]] name = "generic-array" @@ -970,13 +967,13 @@ "version_check", ] [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "js-sys", "libc", "wasi", @@ -1027,13 +1024,13 @@ "foldhash", ] [[package]] name = "hashbrown" -version = "0.16.1" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" [[package]] name = "hashlink" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1126,13 +1123,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "hyper" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" dependencies = [ "atomic-waker", "bytes", "futures-channel", "futures-core", @@ -1140,42 +1137,39 @@ "http", "http-body", "httparse", "itoa", "pin-project-lite", - "pin-utils", "smallvec", "tokio", "want", ] [[package]] name = "hyper-rustls" -version = "0.27.7" +version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ "http", "hyper", "hyper-util", "rustls", - "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", ] [[package]] name = "hyper-util" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ "base64", "bytes", "futures-channel", - "futures-core", "futures-util", "http", "http-body", "hyper", "ipnet", @@ -1190,13 +1184,13 @@ "windows-registry", ] [[package]] name = "iana-time-zone" -version = "0.1.64" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", @@ -1214,26 +1208,27 @@ "cc", ] [[package]] name = "icu_collections" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", "potential_utf", + "utf8_iter", "yoke", "zerofrom", "zerovec", ] [[package]] name = "icu_locale_core" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", "tinystr", "writeable", @@ -1240,13 +1235,13 @@ "zerovec", ] [[package]] name = "icu_normalizer" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ "icu_collections", "icu_normalizer_data", "icu_properties", "icu_provider", @@ -1254,19 +1249,19 @@ "zerovec", ] [[package]] name = "icu_normalizer_data" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] name = "icu_properties" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ "icu_collections", "icu_locale_core", "icu_properties_data", "icu_provider", @@ -1274,19 +1269,19 @@ "zerovec", ] [[package]] name = "icu_properties_data" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] name = "icu_provider" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", "icu_locale_core", "writeable", "yoke", @@ -1322,39 +1317,39 @@ "icu_properties", ] [[package]] name = "indexmap" -version = "2.12.1" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.16.1", + "hashbrown 0.17.0", ] [[package]] name = "ipnet" -version = "2.11.0" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" [[package]] name = "iri-string" -version = "0.7.10" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" dependencies = [ "memchr", "serde", ] [[package]] name = "itoa" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jni" version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1361,22 +1356,44 @@ checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" dependencies = [ "cesu8", "cfg-if", "combine", - "jni-sys", + "jni-sys 0.3.1", "log", "thiserror 1.0.69", "walkdir", "windows-sys 0.45.0", ] [[package]] name = "jni-sys" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn", +] [[package]] name = "jobserver" version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1386,14 +1403,16 @@ "libc", ] [[package]] name = "js-sys" -version = "0.3.83" +version = "0.3.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" dependencies = [ + "cfg-if", + "futures-util", "once_cell", "wasm-bindgen", ] [[package]] @@ -1405,29 +1424,30 @@ "spin", ] [[package]] name = "libc" -version = "0.2.179" +version = "0.2.185" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f" +checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" [[package]] name = "libm" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.12" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" dependencies = [ "bitflags", "libc", - "redox_syscall 0.7.0", + "plain", + "redox_syscall 0.7.4", ] [[package]] name = "libsqlite3-sys" version = "0.30.1" @@ -1436,21 +1456,27 @@ dependencies = [ "pkg-config", "vcpkg", ] +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "linux-raw-sys" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "lock_api" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1481,13 +1507,13 @@ "digest", ] [[package]] name = "memchr" -version = "2.7.6" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "mime" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1513,13 +1539,13 @@ "simd-adler32", ] [[package]] name = "mio" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", "wasi", "windows-sys 0.61.2", ] @@ -1539,11 +1565,11 @@ "lazy_static", "libm", "num-integer", "num-iter", "num-traits", - "rand 0.8.5", + "rand 0.8.6", "smallvec", "zeroize", ] [[package]] @@ -1576,25 +1602,25 @@ "libm", ] [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "openssl-probe" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f50d9b3dabb09ecd771ad0aa242ca6894994c130308ca3d7684634df8037391" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "owo-colors" -version = "4.2.3" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52" +checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d" [[package]] name = "parking" version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1644,25 +1670,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "piper" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" dependencies = [ "atomic-waker", "fastrand", "futures-io", ] @@ -1688,13 +1708,19 @@ "spki", ] [[package]] name = "pkg-config" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" [[package]] name = "polling" version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1708,13 +1734,13 @@ "windows-sys 0.61.2", ] [[package]] name = "potential_utf" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ "zerovec", ] [[package]] @@ -1726,13 +1752,13 @@ "zerocopy", ] [[package]] name = "proc-macro2" -version = "1.0.105" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] @@ -1757,33 +1783,33 @@ "quinn-proto", "quinn-udp", "rustc-hash", "rustls", "socket2", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tracing", "web-time", ] [[package]] name = "quinn-proto" -version = "0.11.13" +version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" dependencies = [ "aws-lc-rs", "bytes", "getrandom 0.3.4", "lru-slab", - "rand 0.9.2", + "rand 0.9.4", "ring", "rustc-hash", "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.17", + "thiserror 2.0.18", "tinyvec", "tracing", "web-time", ] @@ -1801,13 +1827,13 @@ "windows-sys 0.60.2", ] [[package]] name = "quote" -version = "1.0.43" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] [[package]] @@ -1816,27 +1842,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rand" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", "rand_chacha 0.3.1", "rand_core 0.6.4", ] [[package]] name = "rand" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] name = "rand_chacha" version = "0.3.1" @@ -1852,27 +1878,27 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[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", + "getrandom 0.2.17", ] [[package]] name = "rand_core" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ "getrandom 0.3.4", ] [[package]] @@ -1884,51 +1910,51 @@ "bitflags", ] [[package]] name = "redox_syscall" -version = "0.7.0" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27" +checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a" dependencies = [ "bitflags", ] [[package]] name = "regex" -version = "1.12.2" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" -version = "0.8.8" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "reqwest" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04e9018c9d814e5f30cc16a0f03271aeab3571e609612d9fe78c1aa8d11c2f62" +checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" dependencies = [ "base64", "bytes", "encoding_rs", "futures-core", @@ -1972,11 +1998,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.16", + "getrandom 0.2.17", "libc", "untrusted", "windows-sys 0.52.0", ] @@ -2012,11 +2038,11 @@ "quick-xml", ] [[package]] name = "rsstg" -version = "0.5.3" +version = "0.6.0" dependencies = [ "async-compat", "atom_syndication", "chrono", "config", @@ -2025,22 +2051,25 @@ "lazy_static", "regex", "reqwest", "rss", "sedregex", + "serde", "smol", "sqlx", "stacked_errors", "tgbot", + "toml", + "ttl_cache", "url", ] [[package]] name = "rustc-hash" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" [[package]] name = "rustc_version" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2049,13 +2078,13 @@ "semver", ] [[package]] name = "rustix" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", @@ -2062,13 +2091,13 @@ "windows-sys 0.61.2", ] [[package]] name = "rustls" -version = "0.23.36" +version = "0.23.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21" dependencies = [ "aws-lc-rs", "once_cell", "ring", "rustls-pki-types", @@ -2089,13 +2118,13 @@ "security-framework", ] [[package]] name = "rustls-pki-types" -version = "1.13.2" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ "web-time", "zeroize", ] @@ -2126,13 +2155,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.8" +version = "0.103.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +checksum = "8279bb85272c9f10811ae6a6c547ff594d6a7f3c6c6b02ee9726d1d0dcfcdd06" dependencies = [ "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -2144,13 +2173,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2159,13 +2188,13 @@ "winapi-util", ] [[package]] name = "schannel" -version = "0.1.28" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" dependencies = [ "windows-sys 0.61.2", ] [[package]] @@ -2174,13 +2203,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" -version = "3.5.1" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ "bitflags", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -2187,13 +2216,13 @@ "security-framework-sys", ] [[package]] name = "security-framework-sys" -version = "2.15.0" +version = "2.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" dependencies = [ "core-foundation-sys", "libc", ] @@ -2206,13 +2235,13 @@ "regex", ] [[package]] name = "semver" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "serde" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2255,13 +2284,13 @@ "zmij", ] [[package]] name = "serde_spanned" -version = "1.0.4" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" dependencies = [ "serde_core", ] [[package]] @@ -2276,25 +2305,25 @@ "serde", ] [[package]] name = "serde_with" -version = "3.16.1" +version = "3.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" +checksum = "dd5414fad8e6907dbdd5bc441a50ae8d6e26151a03b1de04d89a5576de61d01f" dependencies = [ "serde_core", "serde_with_macros", ] [[package]] name = "serde_with_macros" -version = "3.16.1" +version = "3.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" +checksum = "d3db8978e608f1fe7357e211969fd9abdcae80bac1ba7a3369bb7eb6b404eb65" dependencies = [ - "darling 0.21.3", + "darling 0.23.0", "proc-macro2", "quote", "syn", ] @@ -2356,19 +2385,19 @@ "rand_core 0.6.4", ] [[package]] name = "simd-adler32" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" [[package]] name = "slab" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallbox" version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2400,16 +2429,16 @@ "futures-lite", ] [[package]] name = "socket2" -version = "0.6.1" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "spin" version = "0.9.8" @@ -2469,11 +2498,11 @@ "rustls", "serde", "serde_json", "sha2", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tokio-stream", "tracing", "url", "webpki-roots 0.26.11", @@ -2545,19 +2574,19 @@ "log", "md-5", "memchr", "once_cell", "percent-encoding", - "rand 0.8.5", + "rand 0.8.6", "rsa", "serde", "sha1", "sha2", "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", "whoami", ] [[package]] @@ -2584,18 +2613,18 @@ "itoa", "log", "md-5", "memchr", "once_cell", - "rand 0.8.5", + "rand 0.8.6", "serde", "serde_json", "sha2", "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", "whoami", ] [[package]] @@ -2616,11 +2645,11 @@ "log", "percent-encoding", "serde", "serde_urlencoded", "sqlx-core", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", "url", ] [[package]] @@ -2636,11 +2665,11 @@ checksum = "45ef11d2fabcf9a75b82a9d80966bde3257410b1245b31f1fb6849103ceda0c3" dependencies = [ "owo-colors", "smallbox", "thin-vec", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] name = "stringprep" version = "0.1.5" @@ -2664,13 +2693,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.114" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] @@ -2695,13 +2724,13 @@ "syn", ] [[package]] name = "system-configuration" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ "bitflags", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -2716,13 +2745,13 @@ "libc", ] [[package]] name = "tgbot" -version = "0.41.0" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0212214ba5db8a369e2853900248792d2b19737dda88ecabbb2e31ef85a1d752" +checksum = "ef3557c6785bb002ebbd4c2f439cba36d946e5a96b287c2b5180b62459388cc7" dependencies = [ "async-stream", "bytes", "derive_more", "futures-util", @@ -2738,13 +2767,13 @@ "tokio-util", ] [[package]] name = "thin-vec" -version = "0.2.14" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "144f754d318415ac792f9d69fc87abbbfc043ce2ef041c60f16ad828f638717d" +checksum = "259cdf8ed4e4aca6f1e9d011e10bd53f524a2d0637d7b28450f6c64ac298c4c6" [[package]] name = "thiserror" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2753,15 +2782,15 @@ "thiserror-impl 1.0.69", ] [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.17", + "thiserror-impl 2.0.18", ] [[package]] name = "thiserror-impl" version = "1.0.69" @@ -2773,34 +2802,34 @@ "syn", ] [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tinystr" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", "zerovec", ] [[package]] name = "tinyvec" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" dependencies = [ "tinyvec_macros", ] [[package]] @@ -2809,13 +2838,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.49.0" +version = "1.52.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" dependencies = [ "bytes", "libc", "mio", "pin-project-lite", @@ -2857,44 +2886,52 @@ "tokio", ] [[package]] name = "toml" -version = "0.9.10+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48" +checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" dependencies = [ + "indexmap", "serde_core", "serde_spanned", "toml_datetime", "toml_parser", + "toml_writer", "winnow", ] [[package]] name = "toml_datetime" -version = "0.7.5+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" dependencies = [ "serde_core", ] [[package]] name = "toml_parser" -version = "1.0.6+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ "winnow", ] +[[package]] +name = "toml_writer" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" + [[package]] name = "tower" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", "pin-project-lite", "sync_wrapper", @@ -2973,10 +3010,19 @@ [[package]] name = "try-lock" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "ttl_cache" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4189890526f0168710b6ee65ceaedf1460c48a14318ceec933cb26baa492096a" +dependencies = [ + "linked-hash-map", +] [[package]] name = "typenum" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2994,13 +3040,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" -version = "1.0.22" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-normalization" version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3076,13 +3122,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.1+wasi-0.2.4" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ "wit-bindgen", ] [[package]] @@ -3091,13 +3137,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.106" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", @@ -3104,36 +3150,33 @@ "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.56" +version = "0.4.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" +checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8" dependencies = [ - "cfg-if", "js-sys", - "once_cell", "wasm-bindgen", - "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.106" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.106" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" dependencies = [ "bumpalo", "proc-macro2", "quote", "syn", @@ -3140,22 +3183,22 @@ "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.106" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" dependencies = [ "unicode-ident", ] [[package]] name = "wasm-streams" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" dependencies = [ "futures-util", "js-sys", "wasm-bindgen", "wasm-bindgen-futures", @@ -3162,13 +3205,13 @@ "web-sys", ] [[package]] name = "web-sys" -version = "0.3.83" +version = "0.3.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" +checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d" dependencies = [ "js-sys", "wasm-bindgen", ] @@ -3182,13 +3225,13 @@ "wasm-bindgen", ] [[package]] name = "webpki-root-certs" -version = "1.0.5" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36a29fc0408b113f68cf32637857ab740edfafdf460c326cd2afaa2d84cc05dc" +checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" dependencies = [ "rustls-pki-types", ] [[package]] @@ -3195,18 +3238,18 @@ name = "webpki-roots" version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ - "webpki-roots 1.0.5", + "webpki-roots 1.0.7", ] [[package]] name = "webpki-roots" -version = "1.0.5" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c" +checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" dependencies = [ "rustls-pki-types", ] [[package]] @@ -3586,86 +3629,86 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "0.7.14" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" dependencies = [ "memchr", ] [[package]] name = "wit-bindgen" -version = "0.46.0" +version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" [[package]] name = "writeable" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "yoke" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" dependencies = [ "stable_deref_trait", "yoke-derive", "zerofrom", ] [[package]] name = "yoke-derive" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", "syn", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.32" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fabae64378cb18147bb18bca364e63bdbe72a0ffe4adf0addfec8aa166b2c56" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.32" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9c2d862265a8bb4471d87e033e730f536e2a285cc7cb05dbce09a2a97075f90" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "zerofrom" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", "syn", "synstructure", @@ -3677,41 +3720,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" [[package]] name = "zerotrie" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" dependencies = [ "displaydoc", "yoke", "zerofrom", ] [[package]] name = "zerovec" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ "yoke", "zerofrom", "zerovec-derive", ] [[package]] name = "zerovec-derive" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "zmij" -version = "1.0.12" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fc5a66a20078bf1251bde995aa2fdcc4b800c70b5d92dd2c62abc5c60f679f8" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" Index: Cargo.toml ================================================================== --- Cargo.toml +++ Cargo.toml @@ -1,27 +1,32 @@ [package] name = "rsstg" -version = "0.5.3" -authors = ["arcade"] -edition = "2021" +version = "0.6.0" +authors = [ "arcade@b1t.name" ] +edition = "2024" +license = "0BSD" +repository = "http://fs.b1t.name/rsstg" [dependencies] async-compat = "0.2.5" atom_syndication = { version = "0.12.4", features = [ "with-serde" ] } chrono = "0.4.38" config = { version = "0.15", default-features = false, features = [ "toml" ] } -tgbot = "0.41" +tgbot = "0.44" futures = "0.3.30" futures-util = "0.3.30" lazy_static = "1.5.0" regex = "1.10.6" reqwest = { version = "0.13.1", features = [ "brotli", "socks", "deflate" ]} rss = "2.0.9" sedregex = "0.2.5" +serde = "1.0.228" smol = "2.0.2" stacked_errors = "0.7.1" sqlx = { version = "0.8", features = [ "postgres", "runtime-tokio-rustls", "chrono", "macros" ], default-features = false } +toml = "1.1.0" +ttl_cache = "0.5.1" url = "2.5.8" [profile.release] lto = true codegen-units = 1 Index: LICENSE.0BSD ================================================================== --- LICENSE.0BSD +++ LICENSE.0BSD @@ -1,6 +1,6 @@ -Copyright (C) 2020-2023 by Volodymyr Kostyrko +Copyright (C) 2020-2026 by Volodymyr Kostyrko Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH Index: src/command.rs ================================================================== --- src/command.rs +++ src/command.rs @@ -1,6 +1,13 @@ -use crate::core::Core; +use crate::{ + core::Core, + tg_bot::{ + Callback, + MyMessage, + get_kb, + }, +}; use lazy_static::lazy_static; use regex::Regex; use sedregex::ReplaceCommand; use stacked_errors::{ @@ -7,38 +14,68 @@ Result, StackableErr, bail, }; use tgbot::types::{ + CallbackQuery, + Chat, ChatMember, ChatUsername, GetChat, GetChatAdministrators, + MaybeInaccessibleMessage, Message, - ParseMode::MarkdownV2, }; use url::Url; lazy_static! { static ref RE_USERNAME: Regex = Regex::new(r"^@([a-zA-Z][a-zA-Z0-9_]+)$").unwrap(); static ref RE_IV_HASH: Regex = Regex::new(r"^[a-f0-9]{14}$").unwrap(); } +/// Sends an informational message to the message's chat linking to the bot help channel. pub async fn start (core: &Core, msg: &Message) -> Result<()> { - core.send("We are open\\. Probably\\. Visit [channel](https://t.me/rsstg_bot_help/3) for details\\.", - Some(msg.chat.get_id()), Some(MarkdownV2)).await.stack()?; + core.tg.send(MyMessage::html_to( + "We are open. Probably. Visit channel) for details.", + msg.chat.get_id() + )).await.stack()?; Ok(()) } +/// Send the sender's subscription list to the chat. +/// +/// Retrieves the message sender's user ID, obtains their subscription list from `core`, +/// and sends the resulting reply into the message chat using MarkdownV2. pub async fn list (core: &Core, msg: &Message) -> Result<()> { let sender = msg.sender.get_user_id() .stack_err("Ignoring unreal users.")?; let reply = core.list(sender).await.stack()?; - core.send(reply, Some(msg.chat.get_id()), Some(MarkdownV2)).await.stack()?; + core.tg.send(MyMessage::html_to(reply, msg.chat.get_id())).await.stack()?; + Ok(()) +} + +pub async fn test (core: &Core, msg: &Message) -> Result<()> { + let sender: i64 = msg.sender.get_user_id() + .stack_err("Ignoring unreal users.")?.into(); + let feeds = core.get_feeds(sender).await.stack()?; + let kb = get_kb(&Callback::menu(), &feeds).await.stack()?; + core.tg.send(MyMessage::html_to_kb("Main menu:", msg.chat.get_id(), kb)).await.stack()?; Ok(()) } +/// Handle channel-management commands that operate on a single numeric source ID. +/// +/// This validates that exactly one numeric argument is provided, performs the requested +/// operation (check, clean, enable, delete, disable) against the database or core, +/// and sends the resulting reply to the chat. +/// +/// # Parameters +/// +/// - `core`: application core containing database and Telegram clients. +/// - `command`: command string (e.g. "/check", "/clean", "/enable", "/delete", "/disable"). +/// - `msg`: incoming Telegram message that triggered the command; used to determine sender and chat. +/// - `words`: command arguments; expected to contain exactly one element that parses as a 32-bit integer. pub async fn command (core: &Core, command: &str, msg: &Message, words: &[String]) -> Result<()> { let mut conn = core.db.begin().await.stack()?; let sender = msg.sender.get_user_id() .stack_err("Ignoring unreal users.")?; let reply = if words.len() == 1 { @@ -47,22 +84,38 @@ Ok(number) => match command { "/check" => core.check(number, false, None).await .context("Channel check failed.")?.into(), "/clean" => conn.clean(number, sender).await.stack()?, "/enable" => conn.enable(number, sender).await.stack()?.into(), - "/delete" => conn.delete(number, sender).await.stack()?, + "/delete" => { + let res = conn.delete(number, sender).await.stack()?; + core.rm_feed(sender.into(), &number).await.stack()?; + res + } "/disable" => conn.disable(number, sender).await.stack()?.into(), _ => bail!("Command {command} {words:?} not handled."), }, } } else { - "This command needs exacly one number.".into() + "This command needs exactly one number.".into() }; - core.send(reply, Some(msg.chat.get_id()), None).await.stack()?; + core.tg.send(MyMessage::html_to(reply, msg.chat.get_id())).await.stack()?; Ok(()) } +/// Validate command arguments, check permissions and update or add a channel feed configuration in the database. +/// +/// This function parses and validates parameters supplied by a user command (either "/update ..." or "/add ..."), +/// verifies the channel username and feed URL, optionally validates an IV hash and a replacement regexp, +/// ensures both the bot and the command sender are administrators of the target channel, and performs the database update. +/// +/// # Parameters +/// +/// - `command` — the invoked command, expected to be either `"/update"` (followed by a numeric source id) or `"/add"`. +/// - `msg` — the incoming Telegram message; used to derive the command sender and target chat id for the reply. +/// - `words` — the command arguments: for `"/add"` expected `channel url [iv_hash|'-'] [url_re|'-']`; for `"/update"` +/// the first element must be a numeric `source_id` followed by the same parameters. pub async fn update (core: &Core, command: &str, msg: &Message, words: &[String]) -> Result<()> { let sender = msg.sender.get_user_id() .stack_err("Ignoring unreal users.")?; let mut source_id: Option = None; let at_least = "Requires at least 3 parameters."; @@ -79,21 +132,10 @@ let (channel, url, iv_hash, url_re) = ( i_words.next().context(at_least)?, i_words.next().context(at_least)?, i_words.next(), i_words.next()); - /* - let channel = match RE_USERNAME.captures(channel) { - Some(caps) => match caps.get(1) { - Some(data) => data.as_str(), - None => bail!("No string found in channel name"), - }, - None => { - bail!("Usernames should be something like \"@\\[a\\-zA\\-Z]\\[a\\-zA\\-Z0\\-9\\_]+\", aren't they?\nNot {channel:?}"); - }, - }; - */ if ! RE_USERNAME.is_match(channel) { bail!("Usernames should be something like \"@\\[a\\-zA\\-Z]\\[a\\-zA\\-Z0\\-9\\_]+\", aren't they?\nNot {channel:?}"); }; { let parsed_url = Url::parse(url) @@ -130,12 +172,12 @@ } }, None => None, }; let chat_id = ChatUsername::from(channel.as_ref()); - let channel_id = core.tg.execute(GetChat::new(chat_id.clone())).await.stack_err("gettting GetChat")?.id; - let chan_adm = core.tg.execute(GetChatAdministrators::new(chat_id)).await + let channel_id = core.tg.client.execute(GetChat::new(chat_id.clone())).await.stack_err("getting GetChat")?.id; + let chan_adm = core.tg.client.execute(GetChatAdministrators::new(chat_id)).await .context("Sorry, I have no access to that chat.")?; let (mut me, mut user) = (false, false); for admin in chan_adm { let member_id = match admin { ChatMember::Creator(member) => member.user.id, @@ -143,18 +185,59 @@ ChatMember::Left(_) | ChatMember::Kicked(_) | ChatMember::Member{..} | ChatMember::Restricted(_) => continue, }; - if member_id == core.me.id { + if member_id == core.tg.me.id { me = true; } 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.stack()?; - core.send(conn.update(source_id, channel, channel_id, url, iv_hash, url_re, sender).await.stack()?, Some(msg.chat.get_id()), None).await.stack()?; + let update = conn.update(source_id, channel, channel_id, url, iv_hash, url_re, sender).await.stack()?; + core.tg.send(MyMessage::html_to(update, msg.chat.get_id())).await.stack()?; + if command == "/add" { + if let Some(new_record) = conn.get_one_name(sender, channel).await.stack()? { + core.add_feed(sender.into(), new_record.source_id, new_record.channel).await.stack()?; + } else { + bail!("Failed to read data on freshly inserted source."); + } + }; + Ok(()) +} + +pub async fn answer_cb (core: &Core, query: &CallbackQuery, cb: &str) -> Result<()> { + let cb: Callback = toml::from_str(cb).stack()?; + let sender = &query.from; + //let mut conn = core.db.begin().await.stack()?; + let text = "Sample".to_owned(); + if let Some(msg) = &query.message { + match msg { + MaybeInaccessibleMessage::Message(message) => { + if let Some(owner) = message.sender.get_user() + && sender == owner + { + let feeds = core.get_feeds(owner.id.into()).await.stack()?; + core.tg.update_message(message.chat.get_id().into(), message.id, text, &feeds, cb).await?; + } else { + core.tg.send(MyMessage::html(format!("Can't identify request sender:
{:?}
", message))).await.stack()?; + } + }, + MaybeInaccessibleMessage::InaccessibleMessage(message) => { + let sender: i64 = sender.id.into(); + if let Chat::Private(priv_chat) = &message.chat + && priv_chat.id == sender + { + let feeds = core.get_feeds(priv_chat.id.into()).await.stack()?; + core.tg.update_message(message.chat.get_id().into(), message.message_id, text, &feeds, cb).await?; + } else { + core.tg.send(MyMessage::html(format!("Can't identify request sender:
{:?}
", message))).await.stack()?; + } + }, + }; + }; Ok(()) } Index: src/core.rs ================================================================== --- src/core.rs +++ src/core.rs @@ -1,70 +1,58 @@ use crate::{ + Arc, command, + Mutex, sql::Db, + tg_bot::{ + Callback, + MyMessage, + Tg, + }, }; use std::{ borrow::Cow, collections::{ BTreeMap, HashSet, }, - sync::Arc, + time::Duration, }; use async_compat::Compat; use chrono::{ DateTime, Local, }; use lazy_static::lazy_static; use regex::Regex; -use reqwest::header::{ - CACHE_CONTROL, - EXPIRES, - LAST_MODIFIED -}; -use smol::{ - Timer, - lock::Mutex, -}; -use tgbot::{ - api::Client, - handler::UpdateHandler, - types::{ - Bot, - ChatPeerId, - Command, - GetBot, - Message, - ParseMode, - SendMessage, - Update, - UpdateType, - UserPeerId, - }, -}; +use reqwest::header::LAST_MODIFIED; +use smol::Timer; use stacked_errors::{ Result, StackableErr, anyhow, bail, }; +use tgbot::{ + handler::UpdateHandler, + types::{ + CallbackQuery, + ChatPeerId, + Command, + Update, + UpdateType, + UserPeerId, + }, +}; +use ttl_cache::TtlCache; lazy_static!{ pub static ref RE_SPECIAL: Regex = Regex::new(r"([\-_*\[\]()~`>#+|{}\.!])").unwrap(); } -/// Escape characters that are special in Telegram MarkdownV2 by prefixing them with a backslash. -/// -/// This ensures the returned string can be used as MarkdownV2-formatted Telegram message content -/// without special characters being interpreted as MarkdownV2 markup. -pub fn encode (text: &str) -> Cow<'_, str> { - RE_SPECIAL.replace_all(text, "\\$1") -} - // This one does nothing except making sure only one token exists for each id pub struct Token { running: Arc>>, my_id: i32, } @@ -112,69 +100,63 @@ set.remove(&self.my_id); }) } } +pub type FeedList = BTreeMap; +type UserCache = TtlCache>>; + #[derive(Clone)] pub struct Core { - owner_chat: ChatPeerId, - // max_delay: u16, - pub tg: Client, - pub me: Bot, + pub tg: Tg, pub db: Db, + pub feeds: Arc>, running: Arc>>, http_client: reqwest::Client, } pub struct Post { uri: String, - title: String, - authors: String, - summary: String, + _title: String, + _authors: String, + _summary: String, } impl Core { /// Create a Core instance from configuration and start its background autofetch loop. /// /// The provided `settings` must include: - /// - `owner` (integer): chat id to use as the default destination, + /// - `owner` (integer): default chat id to use as the owner/destination, /// - `api_key` (string): Telegram bot API key, /// - `api_gateway` (string): Telegram API gateway host, /// - `pg` (string): PostgreSQL connection string, /// - optional `proxy` (string): proxy URL for the HTTP client. /// /// On success returns an initialized `Core` with Telegram and HTTP clients, database connection, /// an empty running set for per-id tokens, and a spawned background task that periodically runs /// `autofetch`. If any required setting is missing or initialization fails, an error is returned. pub async fn new(settings: config::Config) -> Result { - let owner_chat = ChatPeerId::from(settings.get_int("owner").stack()?); - let api_key = settings.get_string("api_key").stack()?; - let tg = Client::new(&api_key).stack()? - .with_host(settings.get_string("api_gateway").stack()?); - let mut client = reqwest::Client::builder(); if let Ok(proxy) = settings.get_string("proxy") { let proxy = reqwest::Proxy::all(proxy).stack()?; client = client.proxy(proxy); } - let http_client = client.build().stack()?; - let me = tg.execute(GetBot).await.stack()?; + let core = Core { - tg, - me, - owner_chat, + tg: Tg::new(&settings).await.stack()?, db: Db::new(&settings.get_string("pg").stack()?)?, + feeds: Arc::new(Mutex::new(TtlCache::new(10000))), running: Arc::new(Mutex::new(HashSet::new())), - http_client, - // max_delay: 60, + http_client: client.build().stack()?, }; + let clone = core.clone(); smol::spawn(Compat::new(async move { loop { let delay = match &clone.autofetch().await { Err(err) => { - if let Err(err) = clone.send(format!("🛑 {err}"), None, None).await { + if let Err(err) = clone.tg.send(MyMessage::html(format!("🛑 {err}"))).await { eprintln!("Autofetch error: {err:?}"); }; std::time::Duration::from_secs(60) }, Ok(time) => *time, @@ -183,22 +165,10 @@ } })).detach(); Ok(core) } - 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); - self.tg.execute( - SendMessage::new(target, msg) - .with_parse_mode(mode) - ).await.stack() - } - /// Fetches the feed for a source, sends any newly discovered posts to the appropriate chat, and records them in the database. /// /// This acquires a per-source guard to prevent concurrent checks for the same `id`. If a check is already running for /// the given `id`, the function returns an error. If `last_scrape` is provided, it is sent as the `If-Modified-Since` /// header to the feed request. The function parses RSS or Atom feeds, sends unseen post URLs to either the source's @@ -216,11 +186,11 @@ pub async fn check (&self, id: i32, real: bool, last_scrape: Option>) -> Result { let mut posted: i32 = 0; let mut conn = self.db.begin().await.stack()?; let _token = Token::new(&self.running, id).await.stack()?; - let source = conn.get_source(id, self.owner_chat).await.stack()?; + let source = conn.get_source(id, self.tg.owner).await.stack()?; conn.set_scrape(id).await.stack()?; let destination = ChatPeerId::from(match real { true => source.channel_id, false => source.owner, }); @@ -232,10 +202,14 @@ builder = builder.header(LAST_MODIFIED, last_scrape.to_rfc2822()); }; let response = builder.send().await.stack()?; #[cfg(debug_assertions)] { + use reqwest::header::{ + CACHE_CONTROL, + EXPIRES, + }; let headers = response.headers(); let expires = headers.get(EXPIRES); let cache = headers.get(CACHE_CONTROL); if expires.is_some() || cache.is_some() { println!("{} {} {:?} {:?} {:?}", Local::now().to_rfc2822(), &source.url, last_scrape, expires, cache); @@ -259,19 +233,15 @@ } }, None => bail!("Feed item misses posting date."), }), }.stack()?; - let uri = link.to_string(); - let title = item.title().unwrap_or("").to_string(); - let authors = item.author().unwrap_or("").to_string(); - let summary = item.content().unwrap_or("").to_string(); posts.insert(date, Post{ - uri, - title, - authors, - summary, + uri: link.to_string(), + _title: item.title().unwrap_or("").to_string(), + _authors: item.author().unwrap_or("").to_string(), + _summary: item.content().unwrap_or("").to_string(), }); } }; }, Err(err) => match err { @@ -287,18 +257,17 @@ bail!("Feed item missing post links."); } else { links[0].href().to_string() } }; - let title = item.title().to_string(); - let authors = item.authors().iter().map(|x| format!("{} <{:?}>", x.name(), x.email())).collect::>().join(", "); - let summary = if let Some(sum) = item.summary() { sum.value.clone() } else { String::new() }; + let _authors = item.authors().iter().map(|x| format!("{} <{:?}>", x.name(), x.email())).collect::>().join(", "); + let _summary = if let Some(sum) = item.summary() { sum.value.clone() } else { String::new() }; posts.insert(*date, Post{ uri, - title, - authors, - summary, + _title: item.title().to_string(), + _authors, + _summary, }); }; }, Err(err) => { bail!("Unsupported or mangled content:\n{:?}\n{err}\n{status:#?}\n", &source.url) @@ -316,22 +285,27 @@ }; if ! conn.exists(&post_url, id).await.stack()? { if this_fetch.is_none() || *date > this_fetch.unwrap() { this_fetch = Some(*date); }; - self.send( match &source.iv_hash { + self.tg.send(MyMessage::html_to(match &source.iv_hash { Some(hash) => format!(" {post_url}"), None => format!("{post_url}"), - }, Some(destination), Some(ParseMode::Html)).await.stack()?; + }, destination)).await.stack()?; conn.add_post(id, date, &post_url).await.stack()?; posted += 1; }; }; posts.clear(); Ok(format!("Posted: {posted}")) } + /// Determine the delay until the next scheduled fetch and spawn background checks for any overdue sources. + /// + /// This scans the database queue, spawns background tasks to run checks for sources whose `next_fetch` + /// is in the past (each task uses a Core clone with the appropriate owner), and computes the shortest + /// duration until the next `next_fetch`. async fn autofetch(&self) -> Result { let mut delay = chrono::Duration::minutes(1); let now = chrono::Local::now(); let queue = { let mut conn = self.db.begin().await.stack()?; @@ -340,11 +314,11 @@ for row in queue { if let Some(next_fetch) = row.next_fetch { if next_fetch < now { if let (Some(owner), Some(source_id), last_scrape) = (row.owner, row.source_id, row.last_scrape) { let clone = Core { - owner_chat: ChatPeerId::from(owner), + tg: self.tg.with_owner(owner), ..self.clone() }; let source = { let mut conn = self.db.begin().await.stack()?; match conn.get_one(owner, source_id).await { @@ -352,15 +326,14 @@ Ok(None) => "Source not found in database?".to_string(), Err(err) => format!("Failed to fetch source data:\n{err}"), } }; smol::spawn(Compat::new(async move { - if let Err(err) = clone.check(source_id, true, Some(last_scrape)).await { - if let Err(err) = clone.send(&format!("🛑 {source}\n{}", encode(&err.to_string())), None, Some(ParseMode::MarkdownV2)).await { - eprintln!("Check error: {err}"); - // clone.disable(&source_id, owner).await.unwrap(); - }; + if let Err(err) = clone.check(source_id, true, Some(last_scrape)).await + && let Err(err) = clone.tg.send(MyMessage::html(format!("🛑 {source}\n
{}
", &err.to_string()))).await + { + eprintln!("Check error: {err}"); }; })).detach(); } } else if next_fetch - now < delay { delay = next_fetch - now; @@ -368,42 +341,128 @@ } }; delay.to_std().stack() } + /// Displays full list of managed channels for specified user pub async fn list (&self, owner: UserPeerId) -> Result { let mut reply: Vec = vec![]; reply.push("Channels:".into()); let mut conn = self.db.begin().await.stack()?; for row in conn.get_list(owner).await.stack()? { reply.push(row.to_string()); }; Ok(reply.join("\n\n")) } + + /// Returns current cached list of feed for requested user, or loads data from database + pub async fn get_feeds (&self, owner: i64) -> Result>> { + let mut feeds = self.feeds.lock_arc().await; + Ok(match feeds.get(&owner) { + None => { + let mut conn = self.db.begin().await.stack()?; + let feed_list = conn.get_feeds(owner).await.stack()?; + let mut map = BTreeMap::new(); + for feed in feed_list { + map.insert(feed.source_id, feed.channel); + }; + let res = Arc::new(Mutex::new(map)); + feeds.insert(owner, res.clone(), Duration::from_secs(60 * 60 * 3)); + res + }, + Some(res) => res.clone(), + }) + } + + /// Adds feed to cached list + pub async fn add_feed (&self, owner: i64, source_id: i32, channel: String) -> Result<()> { + let mut inserted = true; + { + let mut feeds = self.feeds.lock_arc().await; + if let Some(feed) = feeds.get_mut(&owner) { + let mut feed = feed.lock_arc().await; + feed.insert(source_id, channel); + } else { + inserted = false; + } + } + // in case insert failed - we miss the entry we needed to expand, reload everything from + // database + if !inserted { + self.get_feeds(owner).await.stack()?; + } + Ok(()) + } + + /// Removes feed from cached list + pub async fn rm_feed (&self, owner: i64, source_id: &i32) -> Result<()> { + let mut dropped = false; + { + let mut feeds = self.feeds.lock_arc().await; + if let Some(feed) = feeds.get_mut(&owner) { + let mut feed = feed.lock_arc().await; + feed.remove(source_id); + dropped = true; + } + } + // in case we failed to found feed we need to remove - just reload everything from database + if !dropped { + self.get_feeds(owner).await.stack()?; + } + Ok(()) + } + + pub async fn cb (&self, query: &CallbackQuery, cb: &str) -> Result<()> { + let cb: Callback = toml::from_str(cb).stack()?; + todo!(); + Ok(()) + } } impl UpdateHandler for Core { - async fn handle (&self, update: Update) { - if let UpdateType::Message(msg) = update.update_type { - if let Ok(cmd) = Command::try_from(msg) { - let msg = cmd.get_message(); - let words = cmd.get_args(); - let command = cmd.get_name(); - let res = match command { - "/check" | "/clean" | "/enable" | "/delete" | "/disable" => command::command(self, command, msg, words).await, - "/start" => command::start(self, msg).await, - "/list" => command::list(self, msg).await, - "/add" | "/update" => command::update(self, command, msg, 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.get_id()), - Some(ParseMode::MarkdownV2) - ).await{ - dbg!(err2); - }; - } - }; - }; + /// Dispatches an incoming Telegram update to a matching command handler and reports handler errors to the originating chat. + /// + /// This method inspects the update; if it contains a message that can be parsed as a bot command, + /// it executes the corresponding command handler. If the handler returns an error, the error text + /// is sent back to the message's chat using MarkdownV2 formatting. Unknown commands produce an error + /// which is also reported to the chat. + async fn handle (&self, update: Update) -> () { + match update.update_type { + UpdateType::Message(msg) => { + if let Ok(cmd) = Command::try_from(*msg) { + let msg = cmd.get_message(); + let words = cmd.get_args(); + let command = cmd.get_name(); + let res = match command { + "/check" | "/clean" | "/enable" | "/delete" | "/disable" => command::command(self, command, msg, words).await, + "/start" => command::start(self, msg).await, + "/list" => command::list(self, msg).await, + "/test" => command::test(self, msg).await, + "/add" | "/update" => command::update(self, command, msg, words).await, + any => Err(anyhow!("Unknown command: {any}")), + }; + if let Err(err) = res + && let Err(err2) = self.tg.send(MyMessage::html_to( + format!("#error
{err}
"), + msg.chat.get_id(), + )).await + { + dbg!(err2); + } + } else { + // not a command + } + }, + UpdateType::CallbackQuery(query) => { + if let Some(ref cb) = query.data + && let Err(err) = self.cb(&query, cb).await + && let Err(err) = self.tg.answer_cb(query.id, err.to_string()).await + { + println!("{err:?}"); + } + }, + _ => { + println!("Unhandled UpdateKind:\n{update:?}") + }, + } } } Index: src/main.rs ================================================================== --- src/main.rs +++ src/main.rs @@ -4,12 +4,16 @@ #![warn(missing_docs)] mod command; mod core; mod sql; +mod tg_bot; + +use std::sync::Arc; use async_compat::Compat; +use smol::lock::Mutex; use stacked_errors::{ Result, StackableErr, }; use tgbot::handler::LongPoll; @@ -20,18 +24,22 @@ })); Ok(()) } +/// Initialises configuration and the bot core, then runs the Telegram long-poll loop. +/// +/// This function loads configuration (with a default API gateway), constructs the application +/// core, and starts the long-polling loop that handles incoming Telegram updates. async fn async_main () -> Result<()> { let settings = config::Config::builder() .set_default("api_gateway", "https://api.telegram.org").stack()? .add_source(config::File::with_name("rsstg")) .build() .stack()?; let core = core::Core::new(settings).await.stack()?; - LongPoll::new(core.tg.clone(), core).run().await; + LongPoll::new(core.tg.client.clone(), core).run().await; Ok(()) } Index: src/sql.rs ================================================================== --- src/sql.rs +++ src/sql.rs @@ -1,12 +1,15 @@ +use crate::{ + Arc, + Mutex, +}; + use std::{ borrow::Cow, fmt, - sync::Arc, }; -use smol::lock::Mutex; use chrono::{ DateTime, FixedOffset, Local, }; @@ -32,24 +35,31 @@ pub url_re: Option, } impl fmt::Display for List { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::result::Result<(), fmt::Error> { - write!(f, "\\#feed\\_{} \\*️⃣ `{}` {}\n🔗 `{}`", self.source_id, self.channel, + write!(f, "#feed_{} *️⃣ {} {}\n🔗 {}", self.source_id, self.channel, match self.enabled { true => "🔄 enabled", false => "⛔ disabled", }, self.url)?; if let Some(iv_hash) = &self.iv_hash { - write!(f, "\nIV: `{iv_hash}`")?; + write!(f, "\nIV: {iv_hash}")?; } if let Some(url_re) = &self.url_re { - write!(f, "\nRE: `{url_re}`")?; + write!(f, "\nRE: {url_re}")?; } Ok(()) } } + +/// One feed, used for caching and menu navigation +#[derive(sqlx::FromRow, Debug)] +pub struct Feed { + pub source_id: i32, + pub channel: String, +} #[derive(sqlx::FromRow, Debug)] pub struct Source { pub channel_id: i64, pub url: String, @@ -148,31 +158,44 @@ 0 => { Ok("Source not found.") }, _ => { bail!("Database error.") }, } } + /// Checks whether a post with the given URL exists for the specified source. + /// + /// # Parameters + /// - `post_url`: The URL of the post to check. + /// - `id`: The source identifier (converted to `i64`). + /// + /// # Returns + /// `true` if a post with the URL exists for the source, `false` otherwise. pub async fn exists (&mut self, post_url: &str, id: I) -> Result where I: Into { 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.into()) .fetch_one(&mut *self.0).await.stack()?; - if let Some(exists) = row.try_get("exists").stack()? { - Ok(exists) - } else { - bail!("Database error: can't check whether source exists."); - } + row.try_get("exists") + .stack_err("Database error: can't check whether post exists.") + } + + pub async fn get_feeds (&mut self, owner: I) -> Result> + where I: Into { + let block: Vec = sqlx::query_as("select source_id, channel from rsstg_source where owner = $1 order by source_id") + .bind(owner.into()) + .fetch_all(&mut *self.0).await.stack()?; + Ok(block) } /// Get all pending events for (now + 1 minute) pub async fn get_queue (&mut self) -> Result> { let block: Vec = sqlx::query_as("select source_id, next_fetch, owner, last_scrape from rsstg_order natural left join rsstg_source where next_fetch < now() + interval '1 minute';") .fetch_all(&mut *self.0).await.stack()?; Ok(block) } - pub async fn get_list (&mut self, owner: I) -> Result> + pub async fn get_list (&mut self, owner: I) -> Result> where I: Into { 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.into()) .fetch_all(&mut *self.0).await.stack()?; Ok(source) @@ -184,10 +207,19 @@ .bind(owner.into()) .bind(id) .fetch_optional(&mut *self.0).await.stack()?; Ok(source) } + + pub async fn get_one_name (&mut self, owner: I, name: &str) -> Result> + where I: Into { + let source: Option = sqlx::query_as("select source_id, channel, enabled, url, iv_hash, url_re from rsstg_source where owner = $1 and channel = $2") + .bind(owner.into()) + .bind(name) + .fetch_optional(&mut *self.0).await.stack()?; + Ok(source) + } pub async fn get_source (&mut self, id: i32, owner: I) -> Result where I: Into { 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) @@ -202,10 +234,11 @@ .bind(id.into()) .execute(&mut *self.0).await.stack()?; Ok(()) } + #[allow(clippy::too_many_arguments)] // XXX at least for now… pub async fn update (&mut self, update: Option, channel: &str, channel_id: i64, url: &str, iv_hash: Option<&str>, url_re: Option<&str>, owner: I) -> Result<&str> where I: Into { match match update { Some(id) => { sqlx::query("update rsstg_source set channel_id = $2, url = $3, iv_hash = $4, owner = $5, channel = $6, url_re = $7 where source_id = $1") @@ -220,11 +253,11 @@ .bind(iv_hash) .bind(owner.into()) .bind(channel) .bind(url_re) .execute(&mut *self.0).await - { + { Ok(_) => Ok(match update { Some(_) => "Channel updated.", None => "Channel added.", }), Err(sqlx::Error::Database(err)) => { ADDED src/tg_bot.rs Index: src/tg_bot.rs ================================================================== --- /dev/null +++ src/tg_bot.rs @@ -0,0 +1,305 @@ +use crate::{ + Arc, + Mutex, + core::FeedList, +}; + +use std::{ + borrow::Cow, + fmt, + time::Duration, +}; + +use serde::{ + Deserialize, + Serialize, +}; +use smol::Timer; +use stacked_errors::{ + bail, + Result, + StackableErr, +}; +use tgbot::{ + api::{ + Client, + ExecuteError + }, + types::{ + AnswerCallbackQuery, + Bot, + ChatPeerId, + EditMessageResult, + EditMessageText, + GetBot, + InlineKeyboardButton, + InlineKeyboardMarkup, + Message, + ParseMode, + SendMessage, + }, +}; + +const CB_VERSION: u8 = 0; + +#[derive(Serialize, Deserialize, Debug)] +pub enum Callback { + // Edit one feed (version, name) + Edit(u8, String), + // List all feeds (version, name to show, page number) + List(u8, String, usize), + // Show root menu (version) + Menu(u8), +} + +impl Callback { + pub fn edit (text: S) -> Callback + where S: Into { + Callback::Edit(CB_VERSION, text.into()) + } + + pub fn list (text: S, page: usize) -> Callback + where S: Into { + Callback::List(CB_VERSION, text.into(), page) + } + + pub fn menu () -> Callback { + Callback::Menu(CB_VERSION) + } + + fn version (&self) -> u8 { + match self { + Callback::Edit(version, .. ) => *version, + Callback::List(version, .. ) => *version, + Callback::Menu(version) => *version, + } + } +} + +impl fmt::Display for Callback { + fn fmt (&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(&toml::to_string(self).map_err(|_| fmt::Error)?) + } +} + +/// Produce new Keyboard Markup from current Callback +pub async fn get_kb (cb: &Callback, feeds: &Arc>) -> Result { + if cb.version() != CB_VERSION { + bail!("Wrong callback version."); + } + let mark = match cb { + Callback::Edit(_, _name) => { // XXX edit missing + let kb: Vec> = vec![]; + InlineKeyboardMarkup::from(kb) + }, + Callback::List(_, name, page) => { + let mut kb = vec![]; + let feeds = feeds.lock_arc().await; + let long = feeds.len() > 6; + let (start, end) = if long { + (page * 5, 5 + page * 5) + } else { + (0, 6) + }; + let mut i = 0; + if name.is_empty() { + let feed_iter = feeds.iter().skip(start); + for (id, name) in feed_iter { + kb.push(vec![ + InlineKeyboardButton::for_callback_data( + format!("{}. {}", id, name), + Callback::edit(name).to_string()), + ]); + i += 1; + if i == end { break } + } + } else { + let mut found = false; + let mut first_page = None; + for (id, feed_name) in feeds.iter() { + if name == feed_name { + found = true; + } + i += 1; + kb.push(vec![ + InlineKeyboardButton::for_callback_data( + format!("{}. {}", id, feed_name), + Callback::list("xxx", *page).to_string()), // XXX edit + ]); + if i == end { + // page complete, if found we got the right page, if not - reset and + // continue. + if found { + break + } else { + if first_page.is_none() { + first_page = Some(kb); + } + kb = vec![]; + i = 0 + } + } + } + if !found { + // name not found, showing first page + kb = first_page.unwrap_or_default(); + } + } + if long { + kb.push(vec![ + InlineKeyboardButton::for_callback_data("<<", + Callback::list("", if *page == 0 { *page } else { page - 1 } ).to_string()), + InlineKeyboardButton::for_callback_data(">>", + Callback::list("", page + 1).to_string()), + ]); + } + InlineKeyboardMarkup::from(kb) + }, + Callback::Menu(_) => { + let kb = vec![ + vec![ + InlineKeyboardButton::for_callback_data( + "Add new channel", + Callback::menu().to_string()), // new XXX + ], + vec![ + InlineKeyboardButton::for_callback_data( + "List channels", + Callback::list("", 0).to_string()), + ], + ]; + InlineKeyboardMarkup::from(kb) + }, + }; + Ok(mark) +} + +pub enum MyMessage <'a> { + Html { text: Cow<'a, str> }, + HtmlTo { text: Cow<'a, str>, to: ChatPeerId }, + HtmlToKb { text: Cow<'a, str>, to: ChatPeerId, kb: InlineKeyboardMarkup }, +} + +impl MyMessage <'_> { + pub fn html <'a, S> (text: S) -> MyMessage<'a> + where S: Into> { + let text = text.into(); + MyMessage::Html { text } + } + + pub fn html_to <'a, S> (text: S, to: ChatPeerId) -> MyMessage<'a> + where S: Into> { + let text = text.into(); + MyMessage::HtmlTo { text, to } + } + + pub fn html_to_kb <'a, S> (text: S, to: ChatPeerId, kb: InlineKeyboardMarkup) -> MyMessage<'a> + where S: Into> { + let text = text.into(); + MyMessage::HtmlToKb { text, to, kb } + } + + fn req (&self, tg: &Tg) -> Result { + Ok(match self { + MyMessage::Html { text } => + SendMessage::new(tg.owner, text.as_ref()) + .with_parse_mode(ParseMode::Html), + MyMessage::HtmlTo { text, to } => + SendMessage::new(*to, text.as_ref()) + .with_parse_mode(ParseMode::Html), + MyMessage::HtmlToKb { text, to, kb } => + SendMessage::new(*to, text.as_ref()) + .with_parse_mode(ParseMode::Html) + .with_reply_markup(kb.clone()), + }) + } +} + +#[derive(Clone)] +pub struct Tg { + pub me: Bot, + pub owner: ChatPeerId, + pub client: Client, +} + +impl Tg { + /// Construct a new `Tg` instance from configuration. + /// + /// The `settings` must provide the following keys: + /// - `"api_key"` (string), + /// - `"owner"` (integer chat id), + /// - `"api_gateway"` (string). + /// + /// The function initialises the client, configures the gateway and fetches the bot identity + /// before returning the constructed `Tg`. + pub async fn new (settings: &config::Config) -> Result { + let api_key = settings.get_string("api_key").stack()?; + + let owner = ChatPeerId::from(settings.get_int("owner").stack()?); + // We don't use retries, as in async environment this will just get us stuck for extra + // amount of time on simple requests. Just bail, show error and ack it in the code. In + // other case we might got stuck with multiple open transactions in database. + let client = Client::new(&api_key).stack()? + .with_host(settings.get_string("api_gateway").stack()?) + .with_max_retries(0); + let me = client.execute(GetBot).await.stack()?; + Ok(Tg { + me, + owner, + client, + }) + } + + /// Send a text message to a chat, using an optional target and parse mode. + /// + /// # Returns + /// The sent `Message` on success. + pub async fn send (&self, msg: MyMessage<'_>) -> Result { + self.client.execute(msg.req(self)?).await.stack() + } + + pub async fn answer_cb (&self, id: String, text: String) -> Result { + self.client.execute( + AnswerCallbackQuery::new(id) + .with_text(text) + ).await.stack() + } + + /// Create a copy of this `Tg` with the owner replaced by the given chat ID. + /// + /// # Parameters + /// - `owner`: The Telegram chat identifier to set as the new owner (expressed as an `i64`). + /// + /// # Returns + /// A new `Tg` instance identical to the original except its `owner` field is set to the provided chat ID. + pub fn with_owner (&self, owner: O) -> Tg + where O: Into { + Tg { + owner: ChatPeerId::from(owner.into()), + ..self.clone() + } + } + + pub async fn update_message (&self, chat_id: i64, message_id: i64, text: String, feeds: &Arc>, cb: Callback) -> Result { + loop { + let req = EditMessageText::for_chat_message(chat_id, message_id, &text) + .with_reply_markup(get_kb(&cb, feeds).await.stack()?); + let res = self.client.execute(req).await; + match res { + Ok(res) => return Ok(res), + Err(ref err) => { + if let ExecuteError::Response(resp) = err + && let Some(delay) = resp.retry_after() + { + if delay > 60 { + return res.context("Delay too big (>60), not waiting."); + } + Timer::after(Duration::from_secs(delay)).await; + } else { + return res.context("Can't update message"); + } + }, + }; + } + } +}