From d6f890f674e1ff23a1f8263428985dc718cc375d Mon Sep 17 00:00:00 2001 From: rafanate Date: Wed, 26 Aug 2026 23:51:13 -0300 Subject: [PATCH] fix(deps): bump grammers 0.8.1 -> 0.10.0 (MTProto layer 227) Telegram servers reject the old MTProto layer 216 shipped with grammers 0.8.x, causing every request to fail with rpc error 400 CONNECTION_LAYER_INVALID and breaking sync/daemon. Upgrade all grammers crates (client, session, mtsender, tl-types) to 0.10 and migrate to the new APIs: - grammers_session::defs -> grammers_session::types - PeerRef { id: PeerId, auth: PeerAuth } (no more Option auth); PeerId::user/chat/channel now return Option, bare_id() -> Option - Session::peer / SqliteSession::open / Client::new(SenderPoolFatHandle) and stream_updates / sync_update_state are now async - PeerRef::from(&Peer) removed; use Dialog::peer_ref() or Peer::to_ref() (new peer_to_ref / peer_to_ref_async helpers) - types module split: tl::types::update -> tl::types::updates, Message moved to tl::types root - Message::peer() -> Option<&Peer>, User::bare_id() -> User::id() - Document::name() -> Option<&str> - New required TL fields: SendMessage/EditMessage (rich_message, schedule_repeat_period), ForwardMessages (effect, schedule_repeat_period), InputReplyToMessage (poll_option), InputMediaPoll (attached_media, solution_media), Poll/PollAnswer, ChatBannedRights (edit_rank, send_reactions), ChatAdminRights (manage_ranks), contacts::Search (bots, broadcasts) - ImportChatInvite now returns ChatInviteJoinResult; InputMedia::Poll takes Box; EditAdmin rank is Option - from_raw() constructors now require &Client Verified: profile show, sync chats (236 chats) and full sync with message fetch all work against a real session. fix(tg): restore 0.8 peer-resolution semantics after grammers 0.10 port The 0.10 port turned several previously-infallible APIs into Option/Result and the mechanical conversion changed behavior in a few places. This keeps every command behaving as it did on 0.8. Peer auth resolution: - `PeerInfo::auth()` is now `Option`, where `None` means the peer is cached but has no usable (non-min) access hash. Mapping that to `PeerAuth::default()` and returning early produced a zeroed access_hash and skipped the user/chat/dialog-scan fallbacks that used to recover the peer. Use `Session::peer_ref()` instead, which yields `None` in that case so the fallback chain runs as before. Affects chats, folders, users and sync. - `peer_to_ref` mapped every `Peer::Group` to ambient auth, but a `Group` wraps `tl::enums::Chat`, which may be a megagroup/gigagroup `Chat::Channel` that needs an access hash. Handle the channel variants, and propagate session errors instead of swallowing them into a silently-wrong `PeerRef`. - Unify the dialog-scan sites on `peer_to_ref_async`; `Dialog::peer_ref()` unwraps internally and would panic on a peer without auth. Database keys: - `PeerId::bare_id()` is now `Option`. Writing `unwrap_or(-1)` into `upsert_chat`/`upsert_contact` would collide distinct peers on row -1, and the daemon did the same with 0 for its chat_id. Skip the record instead. Inside `if let Peer::User(..)` the enclosing `id` is already the user's bare id, so reuse it. Cleanups: - Carry the resolved `PeerRef` out of the sync filter loop instead of re-resolving inside each task; the lookup is a database query in 0.10, so it was being paid twice per chat. - `resolve_peer_from_session` now delegates to `resolve_peer_from_session_static` rather than duplicating ~55 lines. - Flatten the duplicated branches in the chats member resolver. CI: - `cargo fmt`. - `map(..).flatten()` -> `and_then(..)`, matching the idiom used elsewhere in the port. - `sort_by` -> `sort_by_key` in two spots that current stable clippy rejects under `-D warnings`. Co-Authored-By: Claude Opus 5 (1M context) --- Cargo.lock | 701 +++++++++++++++++++++++++-------------------- Cargo.toml | 8 +- src/app/mod.rs | 10 +- src/app/send.rs | 119 +++++--- src/app/sync.rs | 378 ++++++++++-------------- src/cmd/chats.rs | 97 +++---- src/cmd/daemon.rs | 65 +++-- src/cmd/export.rs | 10 +- src/cmd/folders.rs | 47 ++- src/cmd/profile.rs | 2 +- src/cmd/users.rs | 42 ++- src/tg/mod.rs | 68 ++++- 12 files changed, 818 insertions(+), 729 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 543ed47..dfd4e42 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,7 +14,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "generic-array", ] @@ -35,8 +35,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", - "cipher", - "cpufeatures", + "cipher 0.4.4", + "cpufeatures 0.2.17", +] + +[[package]] +name = "aes" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8eb277bec05f56a0e0591f155a484cbd0f4f07ff2905051a48c72f004f7ed58" +dependencies = [ + "cipher 0.5.2", + "cpubits", + "cpufeatures 0.3.0", ] [[package]] @@ -46,9 +57,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" dependencies = [ "aead", - "aes", - "cipher", - "ctr", + "aes 0.8.4", + "cipher 0.4.4", + "ctr 0.9.2", "ghash", "subtle", ] @@ -136,6 +147,17 @@ dependencies = [ "rustversion", ] +[[package]] +name = "async-trait" +version = "0.1.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + [[package]] name = "autocfg" version = "1.5.0" @@ -148,6 +170,29 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bindgen" +version = "0.66.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b84e06fc203107bfbad243f4aba2af864eb7db3b1cf46ea0a023b0b433d2a7" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash 1.1.0", + "shlex", + "syn 2.0.114", + "which", +] + [[package]] name = "bindgen" version = "0.69.5" @@ -167,7 +212,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn", + "syn 2.0.114", "which", ] @@ -179,11 +224,11 @@ checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "block-buffer" -version = "0.10.4" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" dependencies = [ - "generic-array", + "hybrid-array", ] [[package]] @@ -237,7 +282,7 @@ checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -248,9 +293,12 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.11.1" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" +dependencies = [ + "serde", +] [[package]] name = "cc" @@ -293,9 +341,9 @@ checksum = "18758054972164c3264f7c8386f5fc6da6114cb46b619fd365d4e3b2dc3ae487" [[package]] name = "chrono" -version = "0.4.43" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" dependencies = [ "iana-time-zone", "js-sys", @@ -311,8 +359,19 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ - "crypto-common", - "inout", + "crypto-common 0.1.7", + "inout 0.1.4", +] + +[[package]] +name = "cipher" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" +dependencies = [ + "block-buffer", + "crypto-common 0.2.2", + "inout 0.2.2", ] [[package]] @@ -366,7 +425,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -375,6 +434,21 @@ version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + +[[package]] +name = "cmov" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" + [[package]] name = "colorchoice" version = "1.0.4" @@ -390,6 +464,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -397,13 +477,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] -name = "core2" -version = "0.4.0" +name = "cpubits" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" -dependencies = [ - "memchr", -] +checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" [[package]] name = "cpufeatures" @@ -414,6 +491,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.5.0" @@ -468,13 +554,40 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + [[package]] name = "ctr" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" dependencies = [ - "cipher", + "cipher 0.4.4", +] + +[[package]] +name = "ctr" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baaca1c4b237092596f64d571e9db6ce4109c4ef9742e27590f1709594461f21" +dependencies = [ + "cipher 0.5.2", +] + +[[package]] +name = "ctutils" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +dependencies = [ + "cmov", ] [[package]] @@ -488,13 +601,14 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.7" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ "block-buffer", - "crypto-common", - "subtle", + "const-oid", + "crypto-common 0.2.2", + "ctutils", ] [[package]] @@ -557,7 +671,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -613,7 +727,7 @@ checksum = "4e7f34442dbe69c60fe8eaf58a8cafff81a1f278816d8ab4db255b3bef4ac3c4" dependencies = [ "getrandom 0.3.4", "libm", - "rand 0.9.2", + "rand", "siphasher", ] @@ -648,16 +762,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "futf" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" -dependencies = [ - "mac", - "new_debug_unreachable", -] - [[package]] name = "futures" version = "0.3.31" @@ -675,9 +779,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" dependencies = [ "futures-core", "futures-sink", @@ -685,9 +789,9 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" [[package]] name = "futures-executor" @@ -702,38 +806,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.4", ] [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" dependencies = [ "futures-channel", "futures-core", @@ -743,7 +847,6 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] @@ -791,10 +894,22 @@ checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 5.3.0", "wasip2", ] +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.1", +] + [[package]] name = "ghash" version = "0.5.1" @@ -820,16 +935,16 @@ dependencies = [ [[package]] name = "glass_pumpkin" -version = "1.9.0" +version = "2.0.0-rc0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41923edfeadce2964192d185f7f9f9e5d8737e6f5b8ee2580b0f02f717e81bd0" +checksum = "ec260059e5d59a4899d78dff8bf59abf81f8f5b3b44e9d3ba25c9dade1390a36" dependencies = [ - "core2", + "getrandom 0.4.3", "num-bigint", "num-integer", "num-traits", "once_cell", - "rand_core 0.6.4", + "rand_core 0.10.1", ] [[package]] @@ -840,9 +955,9 @@ checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "grammers-client" -version = "0.8.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38cac8001e87679df171268ac03cf3e488f5812788dc45f31402ec29f1d0b4c2" +checksum = "0f330139772e71b5e104f5a7bbf43bbda92fd8a734b4cf9c57839e04e949cf9b" dependencies = [ "chrono", "futures-util", @@ -861,13 +976,13 @@ dependencies = [ [[package]] name = "grammers-crypto" -version = "0.8.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f468a3f3449206198c1d607c54e36c90aa816da118e22a0f15fd1eb7835b342" +checksum = "23fe0fbd93bce6965d08248abdfec2d2544e11cb8d5c91dba3369c0a4295fd85" dependencies = [ - "aes", - "ctr", - "getrandom 0.3.4", + "aes 0.9.2", + "ctr 0.10.1", + "getrandom 0.4.3", "glass_pumpkin", "hmac", "num-bigint", @@ -879,14 +994,14 @@ dependencies = [ [[package]] name = "grammers-mtproto" -version = "0.8.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7ca3e1efb54ffe5c574ce95064ad0b79e6a313105123f8ed1fba36d790ce4c" +checksum = "2a8d68ffa402c5f0707e22c29d44229eb01e968017dd0fb27e1adab953d49fc3" dependencies = [ "bytes", "crc32fast", "flate2", - "getrandom 0.3.4", + "getrandom 0.4.3", "grammers-crypto", "grammers-tl-types", "log", @@ -896,9 +1011,9 @@ dependencies = [ [[package]] name = "grammers-mtsender" -version = "0.8.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c64ff5c7118f05f001416db0cca3ccac30c4163e2efc0243792fbd779468821" +checksum = "f39b8556ddc94f7ec935135dcfbf84ca8628538e103779cdc701423b759b078e" dependencies = [ "bytes", "grammers-crypto", @@ -913,40 +1028,39 @@ dependencies = [ [[package]] name = "grammers-session" -version = "0.8.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7535957942df8762c0c4e295c5f80b8d9b2deac4a8cdecf64e21abcdcac5060" +checksum = "3e4d5b9e434c6b9fc1e091fe82d1f201d8e19df5005d2c19e218b4977b4df506" dependencies = [ - "grammers-tl-gen", - "grammers-tl-parser", "grammers-tl-types", + "libsql", "log", - "sqlite", + "tokio", ] [[package]] name = "grammers-tl-gen" -version = "0.8.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f363661005caecc082e40df967cd21d2ed1b9d56c204f062aec5572a4a9c59b6" +checksum = "1a37dda68c9e775dfbffb85b99826b5ac8144e9da40be3327e77cbcd8618fc30" dependencies = [ "grammers-tl-parser", ] [[package]] name = "grammers-tl-parser" -version = "1.2.0" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3949756519386578835cf5298befef465009accb0531d884eba6e2fc5ad8766e" +checksum = "646fc5eeb27461ffcc94fcf9fda3ffd9b402a20250447ae4a65c39c1a905e381" dependencies = [ "crc32fast", ] [[package]] name = "grammers-tl-types" -version = "0.8.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e430e1834164323635d67f10bd8754eab2d97b16693a1b0532befd373d83d5bb" +checksum = "67d450e2f2588af535fe85a17b9a6df8338794ee8ad6bcb1b97d61a14d472fc5" dependencies = [ "grammers-tl-gen", "grammers-tl-parser", @@ -972,9 +1086,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hmac" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" dependencies = [ "digest", ] @@ -990,13 +1104,12 @@ dependencies = [ [[package]] name = "html5ever" -version = "0.35.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55d958c2f74b664487a2035fe1dadb032c48718a03b63f3ab0b8537db8549ed4" +checksum = "46a1761807faccc9a19e86944bbf40610014066306f96edcdedc2fb714bcb7b8" dependencies = [ "log", "markup5ever", - "match_token", ] [[package]] @@ -1009,6 +1122,15 @@ dependencies = [ "itoa", ] +[[package]] +name = "hybrid-array" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b" +dependencies = [ + "typenum", +] + [[package]] name = "iana-time-zone" version = "0.1.65" @@ -1144,6 +1266,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "inout" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" +dependencies = [ + "hybrid-array", +] + [[package]] name = "intrusive-collections" version = "0.9.7" @@ -1215,7 +1346,7 @@ checksum = "ff7a39c8862fc1369215ccf0a8f12dd4598c7f6484704359f0351bd617034dbf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -1252,9 +1383,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.180" +version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" [[package]] name = "libgit2-sys" @@ -1303,6 +1434,47 @@ dependencies = [ "libc", ] +[[package]] +name = "libsql" +version = "0.9.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30fe980ac5693ed1f3db490559fb578885e913a018df64af8a1a46e1959a78df" +dependencies = [ + "async-trait", + "bitflags", + "bytes", + "futures", + "libsql-sys", + "parking_lot", + "thiserror 1.0.69", + "tracing", +] + +[[package]] +name = "libsql-ffi" +version = "0.9.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0be1da6f123ceb2cd23f469883415cab9ee963286a85d61e22afb8b12e15e681" +dependencies = [ + "bindgen 0.66.1", + "cc", + "cmake", + "glob", +] + +[[package]] +name = "libsql-sys" +version = "0.9.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90725458cc4461bc82f8f7983e80b002ea4f64b5184e1462f252d0dd74b122f5" +dependencies = [ + "bytes", + "libsql-ffi", + "once_cell", + "tracing", + "zerocopy 0.7.35", +] + [[package]] name = "libz-sys" version = "1.1.23" @@ -1353,38 +1525,21 @@ dependencies = [ [[package]] name = "log" -version = "0.4.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" - -[[package]] -name = "mac" -version = "0.1.1" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" [[package]] name = "markup5ever" -version = "0.35.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "311fe69c934650f8f19652b3946075f0fc41ad8757dbb68f1ca14e7900ecc1c3" +checksum = "7122d987ec5f704ee56f6e5b41a7d93722e9aae27ae07cafa4036c4d3f9757de" dependencies = [ "log", "tendril", "web_atoms", ] -[[package]] -name = "match_token" -version = "0.35.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac84fd3f360fcc43dc5f5d186f02a94192761a080e8bc58621ad4d12296a58cf" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "matchers" version = "0.2.0" @@ -1434,7 +1589,7 @@ checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -1480,9 +1635,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.1.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" dependencies = [ "libc", "wasi", @@ -1497,9 +1652,9 @@ checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" [[package]] name = "nix" -version = "0.30.1" +version = "0.31.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" dependencies = [ "bitflags", "cfg-if", @@ -1534,7 +1689,6 @@ checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", - "rand 0.8.5", ] [[package]] @@ -1746,9 +1900,9 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "os_info" -version = "3.14.0" +version = "3.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4022a17595a00d6a369236fdae483f0de7f0a339960a53118b818238e132224" +checksum = "9cf20a545b305cf1da722b236b5155c9bb35f1d5ceb28c048bd96ca842f41b5b" dependencies = [ "android_system_properties", "log", @@ -1799,14 +1953,20 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pbkdf2" -version = "0.12.2" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +checksum = "112d82ceb8c5bf524d9af484d4e4970c9fd5a0cc15ba14ad93dccd28873b0629" dependencies = [ "digest", "hmac", ] +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + [[package]] name = "percent-encoding" version = "2.3.2" @@ -1815,18 +1975,19 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "phf" -version = "0.11.3" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" dependencies = [ "phf_shared", + "serde", ] [[package]] name = "phf_codegen" -version = "0.11.3" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" dependencies = [ "phf_generator", "phf_shared", @@ -1834,19 +1995,19 @@ dependencies = [ [[package]] name = "phf_generator" -version = "0.11.3" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" dependencies = [ + "fastrand", "phf_shared", - "rand 0.8.5", ] [[package]] name = "phf_shared" -version = "0.11.3" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" dependencies = [ "siphasher", ] @@ -1857,12 +2018,6 @@ 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" - [[package]] name = "pkg-config" version = "0.3.32" @@ -1890,7 +2045,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "opaque-debug", "universal-hash", ] @@ -1931,7 +2086,7 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "zerocopy", + "zerocopy 0.8.39", ] [[package]] @@ -1947,7 +2102,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.114", ] [[package]] @@ -1979,14 +2134,14 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] name = "pulldown-cmark" -version = "0.13.1" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83c41efbf8f90ac44de7f3a868f0867851d261b56291732d0cbf7cceaaeb55a6" +checksum = "e9f068eba8e7071c5f9511831b44f32c740d5adf574e990f946ddb53db2f314e" dependencies = [ "bitflags", "memchr", @@ -2009,13 +2164,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] -name = "rand" -version = "0.8.5" +name = "r-efi" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "rand_core 0.6.4", -] +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" @@ -2055,6 +2207,12 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + [[package]] name = "rapidhash" version = "4.3.0" @@ -2253,7 +2411,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -2271,12 +2429,12 @@ dependencies = [ [[package]] name = "sha1" -version = "0.10.6" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.3.0", "digest", ] @@ -2288,12 +2446,12 @@ checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" [[package]] name = "sha2" -version = "0.10.9" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.3.0", "digest", ] @@ -2366,12 +2524,12 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.6.2" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -2380,34 +2538,6 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fef461faaeb36c340b6c887167a9054a034f6acfc50a014ead26a02b4356b3de" -[[package]] -name = "sqlite" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f66e9c01a11936154f3910dbba732c01f8b591543bc4d6672bddee79fd9c4783" -dependencies = [ - "sqlite3-sys", -] - -[[package]] -name = "sqlite3-src" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5b6d3c860886b0a33e69e421796a5f4a27f23597a182c2450f6d7ace5103120" -dependencies = [ - "cc", - "pkg-config", -] - -[[package]] -name = "sqlite3-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7781d97adc13a1d5081127a9ee29afad8427f3757bd984daf814d8265267039" -dependencies = [ - "sqlite3-src", -] - [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -2416,22 +2546,21 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "string_cache" -version = "0.8.9" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901" dependencies = [ "new_debug_unreachable", "parking_lot", "phf_shared", "precomputed-hash", - "serde", ] [[package]] name = "string_cache_codegen" -version = "0.5.4" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" +checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69" dependencies = [ "phf_generator", "phf_shared", @@ -2464,7 +2593,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn", + "syn 2.0.114", ] [[package]] @@ -2484,6 +2613,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "synstructure" version = "0.13.2" @@ -2492,7 +2632,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -2510,13 +2650,11 @@ dependencies = [ [[package]] name = "tendril" -version = "0.4.3" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +checksum = "5fed54709c5b3a53d09bb1c113ea4f5ceafd1e772ddcb0030a82e1d56c087b08" dependencies = [ - "futf", - "mac", - "utf-8", + "new_debug_unreachable", ] [[package]] @@ -2536,7 +2674,7 @@ dependencies = [ "grammers-session", "grammers-tl-types", "log", - "rand 0.9.2", + "rand", "rpassword", "serde", "serde_json", @@ -2572,7 +2710,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -2583,7 +2721,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -2638,9 +2776,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.49.0" +version = "1.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" dependencies = [ "bytes", "libc", @@ -2655,13 +2793,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.6.0" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.4", ] [[package]] @@ -2708,7 +2846,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -2771,7 +2909,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a8b54994ee025964459322bcdb4f6f78c5dba82643863dabfac680f16c8afa8" dependencies = [ "aegis", - "aes", + "aes 0.8.4", "aes-gcm", "arc-swap", "bitflags", @@ -2796,7 +2934,7 @@ dependencies = [ "parking_lot", "paste", "polling", - "rand 0.9.2", + "rand", "rapidhash", "regex", "regex-syntax", @@ -2838,7 +2976,7 @@ checksum = "cc2f62bb271d4cf202bc2acbeb8e2c3f764ec754924f144e704cdcba2e5b0c84" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -2862,7 +3000,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00ff5b2cadd6c8b749511648d50c95f69bfa52efc5d88cc2e2deedd0beeb6c89" dependencies = [ - "bindgen", + "bindgen 0.69.5", "env_logger", "tracing", "tracing-appender", @@ -2879,7 +3017,7 @@ checksum = "289f7ea7499419e6670363ca18e954ed53397bb1e03ab7eabbb267d9b05ab836" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -2910,7 +3048,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b669b19a5f4bfa7cfdf5045af36ca4a2087431c0d2844ec539ddcf951b5c9d2" dependencies = [ - "bindgen", + "bindgen 0.69.5", "env_logger", "genawaiter", "parking_lot", @@ -2929,14 +3067,14 @@ version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" dependencies = [ - "rand 0.9.2", + "rand", ] [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "uncased" @@ -2971,7 +3109,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "subtle", ] @@ -2987,12 +3125,6 @@ dependencies = [ "serde", ] -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - [[package]] name = "utf8_iter" version = "1.0.4" @@ -3082,7 +3214,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.114", "wasm-bindgen-shared", ] @@ -3097,9 +3229,9 @@ dependencies = [ [[package]] name = "web_atoms" -version = "0.1.3" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ffde1dc01240bdf9992e3205668b235e59421fd085e8a317ed98da0178d414" +checksum = "ba8b815c1b593dc0baf78dd0f4fc8fdb2de53198fb1163738093e9a311c33fb3" dependencies = [ "phf", "phf_codegen", @@ -3162,7 +3294,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -3173,7 +3305,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -3227,15 +3359,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", -] - [[package]] name = "windows-sys" version = "0.61.2" @@ -3269,30 +3392,13 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", + "windows_i686_gnullvm", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", -] - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -3305,12 +3411,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -3323,12 +3423,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -3341,24 +3435,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -3371,12 +3453,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -3389,12 +3465,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -3407,12 +3477,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -3425,12 +3489,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - [[package]] name = "wit-bindgen" version = "0.51.0" @@ -3462,17 +3520,38 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", "synstructure", ] +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive 0.7.35", +] + [[package]] name = "zerocopy" version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" dependencies = [ - "zerocopy-derive", + "zerocopy-derive 0.8.39", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", ] [[package]] @@ -3483,7 +3562,7 @@ checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -3503,7 +3582,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", "synstructure", ] @@ -3537,7 +3616,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 271abdd..ce903ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,10 +16,10 @@ name = "tgcli" path = "src/main.rs" [dependencies] -grammers-client = { version = "0.8", features = ["fs", "markdown", "html"] } -grammers-session = "0.8" -grammers-tl-types = "0.8" -grammers-mtsender = "0.8" +grammers-client = { version = "0.10", features = ["fs", "markdown", "html"] } +grammers-session = "0.10" +grammers-tl-types = "0.10" +grammers-mtsender = "0.10" turso = "0.4" clap = { version = "4", features = ["derive"] } clap_complete = "4" diff --git a/src/app/mod.rs b/src/app/mod.rs index 1891f60..f7826e6 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -2,10 +2,10 @@ pub mod send; pub mod sync; use crate::store::Store; -use crate::tg::TgClient; +use crate::tg::{peer_to_ref_async, TgClient}; use crate::Cli; use anyhow::{Context, Result}; -use grammers_session::defs::PeerRef; +use grammers_session::types::PeerRef; use grammers_session::updates::UpdatesLike; use grammers_tl_types as tl; use tokio::sync::mpsc; @@ -34,6 +34,7 @@ impl App { // SqliteSession::open creates the file if it doesn't exist let (tg, updates_rx) = TgClient::connect_with_updates(&session_path) + .await .context("Failed to connect to Telegram")?; if !tg @@ -62,6 +63,7 @@ impl App { let session_path = format!("{}/session.db", store_dir); let (tg, updates_rx) = TgClient::connect_with_updates(&session_path) + .await .context("Failed to connect to Telegram")?; Ok(App { tg, @@ -137,8 +139,8 @@ impl App { format!("Failed to iterate dialogs while resolving chat {}", chat_id) })? { let peer = dialog.peer(); - if peer.id().bare_id() == chat_id { - return Ok(PeerRef::from(peer)); + if peer.id().bare_id() == Some(chat_id) { + return peer_to_ref_async(peer).await; } } anyhow::bail!( diff --git a/src/app/send.rs b/src/app/send.rs index dca6cbe..88b56ed 100644 --- a/src/app/send.rs +++ b/src/app/send.rs @@ -4,10 +4,10 @@ use crate::store::UpsertMessageParams; use anyhow::{Context, Result}; use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _}; use chrono::Utc; +use grammers_client::media::Attribute; +use grammers_client::message::InputMessage; use grammers_client::parsers::{parse_html_message, parse_markdown_message}; -use grammers_client::types::Attribute; -use grammers_client::InputMessage; -use grammers_session::defs::PeerRef; +use grammers_session::types::PeerRef; use grammers_tl_types as tl; use rand::Rng; use std::path::Path; @@ -140,11 +140,13 @@ impl App { reply_markup: None, entities, schedule_date: Some(schedule_date), + schedule_repeat_period: None, send_as: None, quick_reply_shortcut: None, effect: None, allow_paid_stars: None, suggested_post: None, + rich_message: None, }; let updates = self @@ -195,6 +197,7 @@ impl App { quote_offset: None, monoforum_peer_id: None, todo_item_id: None, + poll_option: None, } .into(), ), @@ -203,11 +206,13 @@ impl App { reply_markup: None, entities, schedule_date: None, + schedule_repeat_period: None, send_as: None, quick_reply_shortcut: None, effect: None, allow_paid_stars: None, suggested_post: None, + rich_message: None, }; let updates = self @@ -280,6 +285,7 @@ impl App { quote_offset: None, monoforum_peer_id: None, todo_item_id: None, + poll_option: None, } .into(), ), @@ -288,11 +294,13 @@ impl App { reply_markup: None, entities, schedule_date: None, + schedule_repeat_period: None, send_as: None, quick_reply_shortcut: None, effect: None, allow_paid_stars: None, suggested_post: None, + rich_message: None, }; let updates = self @@ -427,7 +435,9 @@ impl App { reply_markup: None, entities: None, schedule_date: None, + schedule_repeat_period: None, quick_reply_shortcut_id: None, + rich_message: None, }; self.tg.client.invoke(&request).await.context(format!( @@ -475,8 +485,10 @@ impl App { to_peer: to_input_peer, top_msg_id: to_topic_id, schedule_date: None, + schedule_repeat_period: None, send_as: None, quick_reply_shortcut: None, + effect: None, video_timestamp: None, allow_paid_stars: None, reply_to: None, @@ -859,8 +871,8 @@ impl App { let mut dialogs = self.tg.client.iter_dialogs(); while let Some(dialog) = dialogs.next().await? { let peer = dialog.peer(); - if peer.id().bare_id() == chat_id { - return Ok(PeerRef::from(peer)); + if peer.id().bare_id() == Some(chat_id) { + return crate::tg::peer_to_ref_async(peer).await; } } anyhow::bail!( @@ -932,7 +944,7 @@ impl App { continue; } - let sender_id = msg.sender().map(|s| s.id().bare_id()).unwrap_or(0); + let sender_id = msg.sender().and_then(|s| s.id().bare_id()).unwrap_or(0); let from_me = msg.outgoing(); let text = msg.text().to_string(); let reply_to_id = msg.reply_to_message_id().map(|id| id as i64); @@ -997,6 +1009,9 @@ impl App { entities: vec![], }), option: vec![i as u8], // Use index as option identifier + media: None, + added_by: None, + date: None, }) }) .collect(); @@ -1008,6 +1023,12 @@ impl App { public_voters, multiple_choice, quiz: false, + open_answers: false, + revoting_disabled: false, + shuffle_answers: false, + hide_results_until_close: false, + creator: false, + subscribers_only: false, question: tl::enums::TextWithEntities::Entities(tl::types::TextWithEntities { text: question.to_string(), entities: vec![], @@ -1015,15 +1036,19 @@ impl App { answers, close_period: None, close_date: None, + countries_iso2: None, + hash: 0, }); // Create InputMediaPoll - let input_media = tl::enums::InputMedia::Poll(tl::types::InputMediaPoll { + let input_media = tl::enums::InputMedia::Poll(Box::new(tl::types::InputMediaPoll { poll, correct_answers: None, + attached_media: None, solution: None, solution_entities: None, - }); + solution_media: None, + })); let random_id: i64 = rand::rng().random(); @@ -1043,6 +1068,7 @@ impl App { reply_markup: None, entities: None, schedule_date: None, + schedule_repeat_period: None, send_as: None, quick_reply_shortcut: None, effect: None, @@ -1200,6 +1226,8 @@ impl App { send_voices: true, send_docs: true, send_plain: true, + edit_rank: true, + send_reactions: true, until_date, }; @@ -1244,6 +1272,8 @@ impl App { send_voices: true, send_docs: true, send_plain: true, + edit_rank: true, + send_reactions: true, until_date: 0, // Permanent ban first }; @@ -1280,6 +1310,8 @@ impl App { send_voices: false, send_docs: false, send_plain: false, + edit_rank: false, + send_reactions: false, until_date: 0, }; @@ -1327,6 +1359,8 @@ impl App { send_voices: false, send_docs: false, send_plain: false, + edit_rank: false, + send_reactions: false, until_date: 0, }; @@ -1372,13 +1406,14 @@ impl App { edit_stories: false, delete_stories: false, manage_direct_messages: false, + manage_ranks: false, }; let request = tl::functions::channels::EditAdmin { channel: channel_peer, user_id: user_peer, admin_rights: tl::enums::ChatAdminRights::Rights(admin_rights), - rank: title.unwrap_or("Admin").to_string(), + rank: Some(title.unwrap_or("Admin").to_string()), }; self.tg.client.invoke(&request).await.context(format!( @@ -1412,13 +1447,14 @@ impl App { edit_stories: false, delete_stories: false, manage_direct_messages: false, + manage_ranks: false, }; let request = tl::functions::channels::EditAdmin { channel: channel_peer, user_id: user_peer, admin_rights: tl::enums::ChatAdminRights::Rights(admin_rights), - rank: String::new(), + rank: None, }; self.tg.client.invoke(&request).await.context(format!( @@ -1435,8 +1471,8 @@ impl App { let mut dialogs = self.tg.client.iter_dialogs(); while let Some(dialog) = dialogs.next().await? { let peer = dialog.peer(); - if peer.id().bare_id() == chat_id { - let peer_ref = PeerRef::from(peer); + if peer.id().bare_id() == Some(chat_id) { + let peer_ref = crate::tg::peer_to_ref_async(peer).await?; if let tl::enums::InputPeer::Channel(ch) = tl::enums::InputPeer::from(peer_ref) { return Ok(tl::enums::InputChannel::Channel(tl::types::InputChannel { channel_id: ch.channel_id, @@ -1458,8 +1494,8 @@ impl App { let mut dialogs = self.tg.client.iter_dialogs(); while let Some(dialog) = dialogs.next().await? { let peer = dialog.peer(); - if peer.id().bare_id() == user_id { - let peer_ref = PeerRef::from(peer); + if peer.id().bare_id() == Some(user_id) { + let peer_ref = crate::tg::peer_to_ref_async(peer).await?; if let tl::enums::InputPeer::User(u) = tl::enums::InputPeer::from(peer_ref) { return Ok(tl::enums::InputUser::User(tl::types::InputUser { user_id: u.user_id, @@ -1479,6 +1515,8 @@ impl App { /// Returns a list of matching chats (users, groups, channels). pub async fn search_chats(&self, query: &str, limit: usize) -> Result> { let request = tl::functions::contacts::Search { + broadcasts: false, + bots: false, q: query.to_string(), limit: limit as i32, }; @@ -1548,8 +1586,8 @@ impl App { let mut dialogs = self.tg.client.iter_dialogs(); while let Some(dialog) = dialogs.next().await? { let peer = dialog.peer(); - if peer.id().bare_id() == user_id { - let peer_ref = PeerRef::from(peer); + if peer.id().bare_id() == Some(user_id) { + let peer_ref = crate::tg::peer_to_ref_async(peer).await?; return Ok(tl::enums::InputPeer::from(peer_ref)); } } @@ -1582,7 +1620,7 @@ impl App { } count += 1; - let sender_id = msg.sender().map(|s| s.id().bare_id()).unwrap_or(0); + let sender_id = msg.sender().and_then(|s| s.id().bare_id()).unwrap_or(0); let from_me = msg.outgoing(); results.push(crate::store::Message { @@ -1611,9 +1649,9 @@ impl App { } count += 1; - let sender_id = msg.sender().map(|s| s.id().bare_id()).unwrap_or(0); + let sender_id = msg.sender().and_then(|s| s.id().bare_id()).unwrap_or(0); let from_me = msg.outgoing(); - let msg_chat_id = msg.peer_id().bare_id(); + let msg_chat_id = msg.peer_id().bare_id().unwrap_or(0); results.push(crate::store::Message { id: msg.id() as i64, @@ -1799,13 +1837,21 @@ impl App { let hash = extract_invite_hash(invite_link)?; let request = tl::functions::messages::ImportChatInvite { hash }; - let updates = self + let join_result = self .tg .client .invoke(&request) .await .context("Failed to join chat via invite link")?; + // ImportChatInvite now returns ChatInviteJoinResult; the Ok variant carries the updates. + let updates = match join_result { + tl::enums::messages::ChatInviteJoinResult::Ok(ok) => ok.updates, + tl::enums::messages::ChatInviteJoinResult::WebView(_) => { + anyhow::bail!("Invite link requires a web view to join") + } + }; + // Extract chat info from updates extract_chat_from_updates(&updates) } else if let Some(uname) = username { @@ -1824,7 +1870,7 @@ impl App { peer.ok_or_else(|| anyhow::anyhow!("Username '{}' not found", clean_username))?; // Join the chat - let peer_ref = PeerRef::from(&peer); + let peer_ref = crate::tg::peer_to_ref_async(&peer).await?; let input_peer: tl::enums::InputPeer = peer_ref.into(); // Determine if it's a channel/supergroup or a basic chat @@ -1851,17 +1897,17 @@ impl App { // Return info about the joined chat let (kind, name) = match &peer { - grammers_client::types::Peer::Channel(ch) => { + grammers_client::peer::Peer::Channel(ch) => { ("channel".to_string(), ch.title().to_string()) } - grammers_client::types::Peer::Group(g) => { + grammers_client::peer::Peer::Group(g) => { ("group".to_string(), g.title().unwrap_or("").to_string()) } - grammers_client::types::Peer::User(u) => ("user".to_string(), u.full_name()), + grammers_client::peer::Peer::User(u) => ("user".to_string(), u.full_name()), }; Ok(JoinChatResult { - id: peer.id().bare_id(), + id: peer.id().bare_id().unwrap_or(0), kind, name, }) @@ -2051,7 +2097,6 @@ impl App { msg_id: i64, output_path: Option<&str>, ) -> Result { - use grammers_client::types::Downloadable; use std::io::Write; let peer_ref = self.resolve_peer_ref(chat_id).await?; @@ -2244,6 +2289,7 @@ impl App { media: None, effect: None, suggested_post: None, + rich_message: None, }; self.tg @@ -2324,19 +2370,20 @@ fn extract_chat_from_updates(updates: &tl::enums::Updates) -> Result (String, String) { - use grammers_client::types::Media; +fn get_media_filename(media: &grammers_client::media::Media, msg_id: i64) -> (String, String) { + use grammers_client::media::Media; match media { Media::Photo(_) => (format!("photo_{}", msg_id), "jpg".to_string()), Media::Document(doc) => { - // Try to get original filename - if !doc.name().is_empty() { - let name = doc.name(); - if let Some(pos) = name.rfind('.') { - return (name[..pos].to_string(), name[pos + 1..].to_string()); + // Try to get original filename (name() is now Option<&str>) + if let Some(name) = doc.name() { + if !name.is_empty() { + if let Some(pos) = name.rfind('.') { + return (name[..pos].to_string(), name[pos + 1..].to_string()); + } + return (name.to_string(), "bin".to_string()); } - return (name.to_string(), "bin".to_string()); } // Determine extension from mime type @@ -2401,8 +2448,8 @@ fn media_mime_to_ext(mime: &str) -> String { } /// Get media type string from Media enum -fn get_media_type(media: &grammers_client::types::Media) -> String { - use grammers_client::types::Media; +fn get_media_type(media: &grammers_client::media::Media) -> String { + use grammers_client::media::Media; match media { Media::Photo(_) => "photo".to_string(), diff --git a/src/app/sync.rs b/src/app/sync.rs index 3193422..4dca748 100644 --- a/src/app/sync.rs +++ b/src/app/sync.rs @@ -1,13 +1,16 @@ use crate::app::App; use crate::shutdown; use crate::store::UpsertMessageParams; +use crate::tg::peer_to_ref_async; use anyhow::{Context, Result}; use chrono::{DateTime, Utc}; use futures::stream::{self, StreamExt}; -use grammers_client::types::{Media, Message as TgMessage, Peer}; +use grammers_client::media::Media; +use grammers_client::message::Message as TgMessage; +use grammers_client::peer::Peer; use grammers_client::Client; -use grammers_session::defs::{PeerAuth, PeerId, PeerRef}; use grammers_session::storages::SqliteSession; +use grammers_session::types::{PeerAuth, PeerId, PeerRef}; use grammers_session::Session; use grammers_tl_types as tl; use std::collections::HashSet; @@ -66,14 +69,11 @@ fn media_info(media: &Media) -> (String, String) { }; // Try to get extension from filename first - let ext = if let Some(name) = Some(doc.name()).filter(|n| !n.is_empty()) { - Path::new(name) - .extension() - .and_then(|e| e.to_str()) - .map(|s| s.to_lowercase()) - } else { - None - }; + let ext = doc + .name() + .filter(|n| !n.is_empty()) + .and_then(|name| Path::new(name).extension().and_then(|e| e.to_str())) + .map(|s| s.to_lowercase()); // Fall back to mime type let ext = ext.unwrap_or_else(|| { @@ -268,97 +268,13 @@ impl App { /// Try to resolve a chat ID to a PeerRef from the session cache (no API calls). /// If the peer is not in the session cache but we have a stored access_hash, use that. /// Returns None if the chat is not cached and we have no stored access_hash. - fn resolve_peer_from_session( + async fn resolve_peer_from_session( &self, chat_id: i64, kind: &str, stored_access_hash: Option, ) -> Option { - // Try based on the known type first - match kind { - "channel" | "group" => { - // Channels and megagroups use channel peer IDs - let channel_peer_id = PeerId::channel(chat_id); - if let Some(info) = self.tg.session.peer(channel_peer_id) { - return Some(PeerRef { - id: channel_peer_id, - auth: info.auth(), - }); - } - // Fallback to stored access_hash if available - if let Some(hash) = stored_access_hash { - return Some(PeerRef { - id: channel_peer_id, - auth: PeerAuth::from_hash(hash), - }); - } - } - "user" => { - let user_peer_id = PeerId::user(chat_id); - if let Some(info) = self.tg.session.peer(user_peer_id) { - return Some(PeerRef { - id: user_peer_id, - auth: info.auth(), - }); - } - // Fallback to stored access_hash if available - if let Some(hash) = stored_access_hash { - return Some(PeerRef { - id: user_peer_id, - auth: PeerAuth::from_hash(hash), - }); - } - } - _ => {} - } - - // Fallback: try all peer types from session cache - // Try as channel first (most common for groups) - let channel_peer_id = PeerId::channel(chat_id); - if let Some(info) = self.tg.session.peer(channel_peer_id) { - return Some(PeerRef { - id: channel_peer_id, - auth: info.auth(), - }); - } - - // Try as user - let user_peer_id = PeerId::user(chat_id); - if let Some(info) = self.tg.session.peer(user_peer_id) { - return Some(PeerRef { - id: user_peer_id, - auth: info.auth(), - }); - } - - // Try as small group chat (basic groups have different IDs) - if chat_id > 0 && chat_id <= 999999999999 { - let chat_peer_id = PeerId::chat(chat_id); - if let Some(info) = self.tg.session.peer(chat_peer_id) { - return Some(PeerRef { - id: chat_peer_id, - auth: info.auth(), - }); - } - // Small group chats don't need access_hash, so try with default auth - if stored_access_hash.is_some() { - return Some(PeerRef { - id: chat_peer_id, - auth: PeerAuth::default(), - }); - } - } - - // Last resort: try to construct from stored access_hash with best-guess peer type - if let Some(hash) = stored_access_hash { - // Most likely a channel/group if we have an access_hash - return Some(PeerRef { - id: channel_peer_id, - auth: PeerAuth::from_hash(hash), - }); - } - - None + resolve_peer_from_session_static(&self.tg.session, chat_id, kind, stored_access_hash).await } /// Download media from a message if present and return (media_type, media_path) @@ -473,7 +389,9 @@ impl App { } let peer = dialog.peer(); let (kind, name, username, is_forum, access_hash) = peer_info(peer); - let id = peer.id().bare_id(); + let Some(id) = peer.id().bare_id() else { + continue; + }; if should_ignore(id, &kind) { continue; @@ -499,7 +417,7 @@ impl App { self.get_store() .await? .upsert_contact( - user.bare_id(), + id, user.username(), user.first_name().unwrap_or(""), user.last_name().unwrap_or(""), @@ -546,7 +464,9 @@ impl App { break; } let (kind, name, username, is_forum, access_hash) = peer_info(&peer); - let id = peer.id().bare_id(); + let Some(id) = peer.id().bare_id() else { + continue; + }; if should_ignore(id, &kind) { continue; @@ -572,7 +492,7 @@ impl App { self.get_store() .await? .upsert_contact( - user.bare_id(), + id, user.username(), user.first_name().unwrap_or(""), user.last_name().unwrap_or(""), @@ -622,37 +542,40 @@ impl App { // Get all chats that have sync checkpoints let all_chats = self.get_store().await?.list_chats_with_checkpoint().await?; - // Filter chats to process + // Filter chats to process (async: peer resolution queries the session) let chat_filter = opts.chat_filter; let skip_archived = opts.skip_archived; let archived_only = opts.archived_only; - let chats_to_sync: Vec<_> = all_chats - .into_iter() - .filter(|chat| { - // If chat_filter is set, only include that specific chat - if let Some(filter_id) = chat_filter { - if chat.id != filter_id { - return false; - } - } - if ignore_set.contains(&chat.id) { - return false; - } - if ignore_channels && chat.kind == "channel" { - return false; - } - // Filter by archived status - if skip_archived && chat.archived { - return false; - } - if archived_only && !chat.archived { - return false; + let mut chats_to_sync: Vec<_> = Vec::new(); + for chat in all_chats { + // If chat_filter is set, only include that specific chat + if let Some(filter_id) = chat_filter { + if chat.id != filter_id { + continue; } - // Must have peer info to sync - self.resolve_peer_from_session(chat.id, &chat.kind, chat.access_hash) - .is_some() - }) - .collect(); + } + if ignore_set.contains(&chat.id) { + continue; + } + if ignore_channels && chat.kind == "channel" { + continue; + } + // Filter by archived status + if skip_archived && chat.archived { + continue; + } + if archived_only && !chat.archived { + continue; + } + // Must have peer info to sync. Keep the resolved ref: the session + // lookup is a database query, so re-resolving per task would double it. + if let Some(peer_ref) = self + .resolve_peer_from_session(chat.id, &chat.kind, chat.access_hash) + .await + { + chats_to_sync.push((chat, peer_ref)); + } + } let total_chats = chats_to_sync.len(); if total_chats == 0 { @@ -667,7 +590,7 @@ impl App { } // Fetch unread counts from dialogs for the chats we're about to sync - let chat_ids_to_sync: HashSet = chats_to_sync.iter().map(|c| c.id).collect(); + let chat_ids_to_sync: HashSet = chats_to_sync.iter().map(|(c, _)| c.id).collect(); let mut unread_counts: std::collections::HashMap = std::collections::HashMap::new(); @@ -678,7 +601,9 @@ impl App { let client = &self.tg.client; let mut dialogs = client.iter_dialogs(); while let Some(dialog) = dialogs.next().await.ok().flatten() { - let chat_id = dialog.peer().id().bare_id(); + let Some(chat_id) = dialog.peer().id().bare_id() else { + continue; + }; if chat_ids_to_sync.contains(&chat_id) { let unread = extract_unread_count(&dialog.raw); unread_counts.insert(chat_id, unread); @@ -705,9 +630,6 @@ impl App { // Clone client for use in tasks (grammers Client is Clone) let client = self.tg.client.clone(); - // Session for peer resolution - let session = self.tg.session.clone(); - // Store dir for media paths (if download enabled later) let store_dir = self.store_dir.clone(); let download_media = opts.download_media; @@ -752,10 +674,9 @@ impl App { // Create concurrent stream of chat sync tasks let results: Vec = stream::iter(chats_to_sync) - .map(|chat| { + .map(|(chat, peer_ref)| { let sem = semaphore.clone(); let client = client.clone(); - let session = session.clone(); let store_dir = store_dir.clone(); let chats_done = chats_done.clone(); let messages_fetched = messages_fetched.clone(); @@ -785,35 +706,6 @@ impl App { }; } - // Resolve peer - let peer_ref = resolve_peer_from_session_static( - &session, - chat.id, - &chat.kind, - chat.access_hash, - ); - - let peer_ref = match peer_ref { - Some(p) => p, - None => { - chats_done.fetch_add(1, Ordering::Relaxed); - return ChatSyncTaskResult { - chat_id: chat.id, - chat_name: chat.name.clone(), - chat_kind: chat.kind.clone(), - chat_username: chat.username.clone(), - is_forum: chat.is_forum, - access_hash: chat.access_hash, - archived: chat.archived, - messages: Vec::new(), - highest_msg_id: None, - latest_ts: None, - topic_counts: std::collections::HashMap::new(), - error: Some("No peer ref available".to_string()), - }; - } - }; - // Fetch messages // For incremental sync, use the stored checkpoint; for full sync, fetch all let last_sync_id = if incremental { @@ -867,7 +759,8 @@ impl App { latest_ts = Some(msg_ts); } - let sender_id = msg.sender().map(|s| s.id().bare_id()).unwrap_or(0); + let sender_id = + msg.sender().and_then(|s| s.id().bare_id()).unwrap_or(0); let from_me = msg.outgoing(); let text = msg.text().to_string(); let reply_to_id = msg.reply_to_message_id().map(|id| id as i64); @@ -1146,7 +1039,7 @@ impl App { .map(|mut summary| { summary .topics - .sort_by(|a, b| b.messages_synced.cmp(&a.messages_synced)); + .sort_by_key(|a| std::cmp::Reverse(a.messages_synced)); summary }) .collect(); @@ -1214,7 +1107,9 @@ impl App { } let peer = dialog.peer(); let (kind, name, username, is_forum, access_hash) = peer_info(peer); - let id = peer.id().bare_id(); + let Some(id) = peer.id().bare_id() else { + continue; + }; // Skip ignored chats. if should_ignore(id, &kind) { @@ -1241,7 +1136,7 @@ impl App { self.get_store() .await? .upsert_contact( - user.bare_id(), + id, user.username(), user.first_name().unwrap_or(""), user.last_name().unwrap_or(""), @@ -1254,7 +1149,7 @@ impl App { let unread_count = extract_unread_count(&dialog.raw); // Fetch messages for this chat - let peer_ref = PeerRef::from(peer); + let peer_ref = peer_to_ref_async(peer).await?; let mut message_iter = client.iter_messages(peer_ref); let mut count = 0; let mut latest_ts: Option> = None; @@ -1322,7 +1217,7 @@ impl App { latest_ts = Some(msg_ts); } - let sender_id = msg.sender().map(|s| s.id().bare_id()).unwrap_or(0); + let sender_id = msg.sender().and_then(|s| s.id().bare_id()).unwrap_or(0); let from_me = msg.outgoing(); let text = msg.text().to_string(); @@ -1542,7 +1437,9 @@ impl App { } let (kind, name, username, is_forum, access_hash) = peer_info(&peer); - let id = peer.id().bare_id(); + let Some(id) = peer.id().bare_id() else { + continue; + }; // Skip ignored chats. if should_ignore(id, &kind) { @@ -1569,7 +1466,7 @@ impl App { self.get_store() .await? .upsert_contact( - user.bare_id(), + id, user.username(), user.first_name().unwrap_or(""), user.last_name().unwrap_or(""), @@ -1579,7 +1476,7 @@ impl App { } // Fetch messages for this chat - let peer_ref = PeerRef::from(&peer); + let peer_ref = peer_to_ref_async(&peer).await?; let mut message_iter = client.iter_messages(peer_ref); let mut count = 0; let mut latest_ts: Option> = None; @@ -1648,7 +1545,7 @@ impl App { latest_ts = Some(msg_ts); } - let sender_id = msg.sender().map(|s| s.id().bare_id()).unwrap_or(0); + let sender_id = msg.sender().and_then(|s| s.id().bare_id()).unwrap_or(0); let from_me = msg.outgoing(); let text = msg.text().to_string(); @@ -1843,7 +1740,7 @@ impl App { .map(|mut summary| { summary .topics - .sort_by(|a, b| b.messages_synced.cmp(&a.messages_synced)); + .sort_by_key(|a| std::cmp::Reverse(a.messages_synced)); summary }) .collect(); @@ -1955,7 +1852,10 @@ impl App { last_peer_id = Some(u.user_id); last_peer_kind = Some("user"); if let Some(user) = users_map.get(&u.user_id) { - Peer::User(grammers_client::types::User::from_raw(user.clone())) + Peer::User(grammers_client::peer::User::from_raw( + &self.tg.client, + user.clone(), + )) } else { continue; } @@ -1964,7 +1864,10 @@ impl App { last_peer_id = Some(c.chat_id); last_peer_kind = Some("chat"); if let Some(chat) = chats_map.get(&c.chat_id) { - Peer::Group(grammers_client::types::Group::from_raw(chat.clone())) + Peer::Group(grammers_client::peer::Group::from_raw( + &self.tg.client, + chat.clone(), + )) } else { continue; } @@ -1974,12 +1877,16 @@ impl App { last_peer_kind = Some("channel"); if let Some(chat) = chats_map.get(&c.channel_id) { match chat { - tl::enums::Chat::Channel(ch) if ch.broadcast => Peer::Channel( - grammers_client::types::Channel::from_raw(chat.clone()), - ), + tl::enums::Chat::Channel(ch) if ch.broadcast => { + Peer::Channel(grammers_client::peer::Channel::from_raw( + &self.tg.client, + chat.clone(), + )) + } tl::enums::Chat::Channel(_) => { // Megagroup (supergroup) - treat as Group - Peer::Group(grammers_client::types::Group::from_raw( + Peer::Group(grammers_client::peer::Group::from_raw( + &self.tg.client, chat.clone(), )) } @@ -2025,8 +1932,23 @@ impl App { } } +/// Look up a peer in the session cache, returning a PeerRef if found. +/// +/// `Session::peer_ref` yields `None` both when the peer is absent and when it is +/// cached without usable auth, so callers keep walking their fallback chain +/// instead of building a `PeerRef` with a zeroed access_hash. +async fn lookup_peer_in_session(session: &SqliteSession, peer_id: PeerId) -> Option { + match session.peer_ref(peer_id).await { + Ok(peer_ref) => peer_ref, + Err(e) => { + log::warn!("Session lookup failed for peer {:?}: {}", peer_id, e); + None + } + } +} + /// Static version of resolve_peer_from_session for use in async tasks -fn resolve_peer_from_session_static( +async fn resolve_peer_from_session_static( session: &SqliteSession, chat_id: i64, kind: &str, @@ -2035,33 +1957,29 @@ fn resolve_peer_from_session_static( // Try based on the known type first match kind { "channel" | "group" => { - let channel_peer_id = PeerId::channel(chat_id); - if let Some(info) = session.peer(channel_peer_id) { - return Some(PeerRef { - id: channel_peer_id, - auth: info.auth(), - }); - } - if let Some(hash) = stored_access_hash { - return Some(PeerRef { - id: channel_peer_id, - auth: PeerAuth::from_hash(hash), - }); + if let Some(channel_peer_id) = PeerId::channel(chat_id) { + if let Some(peer_ref) = lookup_peer_in_session(session, channel_peer_id).await { + return Some(peer_ref); + } + if let Some(hash) = stored_access_hash { + return Some(PeerRef { + id: channel_peer_id, + auth: PeerAuth::from_hash(hash), + }); + } } } "user" => { - let user_peer_id = PeerId::user(chat_id); - if let Some(info) = session.peer(user_peer_id) { - return Some(PeerRef { - id: user_peer_id, - auth: info.auth(), - }); - } - if let Some(hash) = stored_access_hash { - return Some(PeerRef { - id: user_peer_id, - auth: PeerAuth::from_hash(hash), - }); + if let Some(user_peer_id) = PeerId::user(chat_id) { + if let Some(peer_ref) = lookup_peer_in_session(session, user_peer_id).await { + return Some(peer_ref); + } + if let Some(hash) = stored_access_hash { + return Some(PeerRef { + id: user_peer_id, + auth: PeerAuth::from_hash(hash), + }); + } } } _ => {} @@ -2069,42 +1987,40 @@ fn resolve_peer_from_session_static( // Fallback: try all peer types from session cache let channel_peer_id = PeerId::channel(chat_id); - if let Some(info) = session.peer(channel_peer_id) { - return Some(PeerRef { - id: channel_peer_id, - auth: info.auth(), - }); + if let Some(peer_id) = channel_peer_id { + if let Some(peer_ref) = lookup_peer_in_session(session, peer_id).await { + return Some(peer_ref); + } } let user_peer_id = PeerId::user(chat_id); - if let Some(info) = session.peer(user_peer_id) { - return Some(PeerRef { - id: user_peer_id, - auth: info.auth(), - }); + if let Some(peer_id) = user_peer_id { + if let Some(peer_ref) = lookup_peer_in_session(session, peer_id).await { + return Some(peer_ref); + } } if chat_id > 0 && chat_id <= 999999999999 { - let chat_peer_id = PeerId::chat(chat_id); - if let Some(info) = session.peer(chat_peer_id) { - return Some(PeerRef { - id: chat_peer_id, - auth: info.auth(), - }); - } - if stored_access_hash.is_some() { - return Some(PeerRef { - id: chat_peer_id, - auth: PeerAuth::default(), - }); + if let Some(chat_peer_id) = PeerId::chat(chat_id) { + if let Some(peer_ref) = lookup_peer_in_session(session, chat_peer_id).await { + return Some(peer_ref); + } + if stored_access_hash.is_some() { + return Some(PeerRef { + id: chat_peer_id, + auth: PeerAuth::default(), + }); + } } } if let Some(hash) = stored_access_hash { - return Some(PeerRef { - id: channel_peer_id, - auth: PeerAuth::from_hash(hash), - }); + if let Some(channel_peer_id) = channel_peer_id { + return Some(PeerRef { + id: channel_peer_id, + auth: PeerAuth::from_hash(hash), + }); + } } None diff --git a/src/cmd/chats.rs b/src/cmd/chats.rs index 154138e..8111486 100644 --- a/src/cmd/chats.rs +++ b/src/cmd/chats.rs @@ -4,7 +4,7 @@ use crate::store::Store; use crate::Cli; use anyhow::Result; use clap::{ArgAction, Subcommand}; -use grammers_session::defs::{PeerAuth, PeerId, PeerRef}; +use grammers_session::types::{PeerAuth, PeerId, PeerRef}; use grammers_session::Session; use grammers_tl_types as tl; use serde::Serialize; @@ -229,8 +229,8 @@ fn format_user_status(status: &tl::enums::UserStatus) -> String { } } -fn format_role(role: &grammers_client::types::Role) -> String { - use grammers_client::types::Role; +fn format_role(role: &grammers_client::peer::Role) -> String { + use grammers_client::peer::Role; match role { Role::User(_) => "member".to_string(), Role::Creator(_) => "creator".to_string(), @@ -361,7 +361,7 @@ pub async fn run(cli: &Cli, cmd: &ChatsCommand) -> Result<()> { let peer_ref = if let Some(ref username) = chat_username { // Resolve via username match app.tg.client.resolve_username(username).await? { - Some(peer) => PeerRef::from(peer), + Some(peer) => crate::tg::peer_to_ref_async(&peer).await?, None => { anyhow::bail!("Could not resolve username @{}. The username may not exist or may be misspelled.", username); } @@ -369,31 +369,27 @@ pub async fn run(cli: &Cli, cmd: &ChatsCommand) -> Result<()> { } else { // Try to get from session, fall back to trying different ID types let channel_peer_id = PeerId::channel(*id); - let chat_peer_id = if *id > 0 && *id <= 999999999999 { - Some(PeerId::chat(*id)) - } else { - None + let chat_peer_id = PeerId::chat(*id); + + // Check session for the channel first. `peer_ref` yields `None` + // when the peer is absent or cached without usable auth. + let cached = match channel_peer_id { + Some(peer_id) => app.tg.session.peer_ref(peer_id).await?, + None => None, }; - // Check session for channel first - if let Some(info) = app.tg.session.peer(channel_peer_id) { - PeerRef { - id: channel_peer_id, - auth: info.auth(), - } - } else if let Some(chat_id) = chat_peer_id { - // Try as small group chat (no access_hash needed) - PeerRef { - id: chat_id, - auth: PeerAuth::default(), - } - } else { - // Last resort: try channel with no access_hash - // This will likely fail but provides a clear error - PeerRef { - id: channel_peer_id, - auth: PeerAuth::default(), - } + match cached { + Some(peer_ref) => peer_ref, + // Try as a small group chat (no access_hash needed), then + // fall back to the channel with no access_hash. The latter + // will likely fail, but it produces a clear error. + None => match chat_peer_id.or(channel_peer_id) { + Some(id) => PeerRef { + id, + auth: PeerAuth::default(), + }, + None => anyhow::bail!("Invalid peer ID: {}", id), + }, } }; @@ -405,7 +401,7 @@ pub async fn run(cli: &Cli, cmd: &ChatsCommand) -> Result<()> { while let Some(participant) = participants.next().await? { let user = &participant.user; let member = MemberInfo { - id: user.bare_id(), + id: user.id().bare_id().unwrap_or(0), username: user.username().map(|s| s.to_string()), first_name: user.first_name().map(|s| s.to_string()), last_name: user.last_name().map(|s| s.to_string()), @@ -1102,34 +1098,19 @@ async fn batch_pin(cli: &Cli, chat_ids: &[i64], pin: bool, folder_id: i32) -> Re /// Resolve a chat ID to an InputPeer async fn resolve_chat_to_input_peer(app: &App, chat_id: i64) -> Result { - // First check session for channel - let channel_peer_id = PeerId::channel(chat_id); - if let Some(info) = app.tg.session.peer(channel_peer_id) { - let peer_ref = PeerRef { - id: channel_peer_id, - auth: info.auth(), - }; - return Ok(peer_ref.into()); - } - - // Try as user - let user_peer_id = PeerId::user(chat_id); - if let Some(info) = app.tg.session.peer(user_peer_id) { - let peer_ref = PeerRef { - id: user_peer_id, - auth: info.auth(), - }; - return Ok(peer_ref.into()); - } - - // Try as small group chat - if chat_id > 0 && chat_id <= 999999999999 { - let chat_peer_id = PeerId::chat(chat_id); - if let Some(info) = app.tg.session.peer(chat_peer_id) { - let peer_ref = PeerRef { - id: chat_peer_id, - auth: info.auth(), - }; + // Try the session cache as a channel, then a user, then a small group chat. + // `peer_ref` yields `None` for peers cached without usable auth, so those + // fall through to the dialog scan below rather than producing a `PeerRef` + // with a zeroed access_hash. + for peer_id in [ + PeerId::channel(chat_id), + PeerId::user(chat_id), + PeerId::chat(chat_id), + ] + .into_iter() + .flatten() + { + if let Some(peer_ref) = app.tg.session.peer_ref(peer_id).await? { return Ok(peer_ref.into()); } } @@ -1138,8 +1119,8 @@ async fn resolve_chat_to_input_peer(app: &App, chat_id: i64) -> Result i64 { +/// Extract chat_id from a Peer. +/// +/// `None` only for the self-user placeholder, which never reaches us here, but +/// the chat_id is a database key so we skip rather than invent one. +fn extract_chat_id_from_peer(peer: &Peer) -> Option { peer.id().bare_id() } /// Extract sender_id from a Message update -fn extract_sender_id(msg: &grammers_client::types::update::Message) -> i64 { - msg.sender().map(|s| s.id().bare_id()).unwrap_or(0) +fn extract_sender_id(msg: &grammers_client::message::Message) -> i64 { + msg.sender().and_then(|s| s.id().bare_id()).unwrap_or(0) } /// Extract topic_id from a raw update if present @@ -98,11 +102,11 @@ fn chat_name_from_peer(peer: &Peer) -> String { Peer::User(u) => u .first_name() .map(|s| s.to_string()) - .unwrap_or_else(|| format!("User {}", u.bare_id())), + .unwrap_or_else(|| format!("User {}", u.id().bare_id().unwrap_or(0))), Peer::Group(g) => g .title() .map(|s| s.to_string()) - .unwrap_or_else(|| format!("Group {}", g.id().bare_id())), + .unwrap_or_else(|| format!("Group {}", g.id().bare_id().unwrap_or(0))), Peer::Channel(c) => c.title().to_string(), } } @@ -166,13 +170,18 @@ pub async fn run(cli: &Cli, args: &DaemonArgs) -> Result<()> { // Start the update stream - this subscribes to updates immediately // catch_up: true means it will also fetch any missed updates since last session - let mut update_stream = app.tg.client.stream_updates( - updates_rx, - UpdatesConfiguration { - catch_up: !args.no_backfill, // Catch up on missed updates if backfill enabled - ..Default::default() - }, - ); + let mut update_stream = app + .tg + .client + .stream_updates( + updates_rx, + UpdatesConfiguration { + catch_up: !args.no_backfill, // Catch up on missed updates if backfill enabled + ..Default::default() + }, + ) + .await + .map_err(|e| anyhow::anyhow!("failed to start update stream: {e}"))?; // Spawn background sync task if backfill is enabled let backfill_handle = if !args.no_backfill { @@ -271,14 +280,17 @@ pub async fn run(cli: &Cli, args: &DaemonArgs) -> Result<()> { Update::NewMessage(msg) => { // Get the peer (chat) from the message let peer = match msg.peer() { - Ok(p) => p.clone(), - Err(_) => { + Some(p) => p.clone(), + None => { log::warn!("Could not resolve peer for message {}", msg.id()); continue; } }; - let chat_id = extract_chat_id_from_peer(&peer); + let Some(chat_id) = extract_chat_id_from_peer(&peer) else { + log::warn!("Could not resolve chat id for message {}", msg.id()); + continue; + }; let chat_kind = chat_kind_from_peer(&peer); // Check ignore filters @@ -372,14 +384,20 @@ pub async fn run(cli: &Cli, args: &DaemonArgs) -> Result<()> { Update::MessageEdited(msg) => { // Get the peer (chat) from the message let peer = match msg.peer() { - Ok(p) => p, - Err(_) => { + Some(p) => p, + None => { log::warn!("Could not resolve peer for edited message {}", msg.id()); continue; } }; - let chat_id = extract_chat_id_from_peer(peer); + let Some(chat_id) = extract_chat_id_from_peer(peer) else { + log::warn!( + "Could not resolve chat id for edited message {}", + msg.id() + ); + continue; + }; // Check ignore filters if ignore_set.contains(&chat_id) { @@ -470,7 +488,10 @@ pub async fn run(cli: &Cli, args: &DaemonArgs) -> Result<()> { if !args.quiet { eprintln!("Syncing session state..."); } - update_stream.sync_update_state(); + update_stream + .sync_update_state() + .await + .map_err(|e| anyhow::anyhow!("failed to sync update state: {e}"))?; if !args.quiet { eprintln!( diff --git a/src/cmd/export.rs b/src/cmd/export.rs index 00e5e04..65cd592 100644 --- a/src/cmd/export.rs +++ b/src/cmd/export.rs @@ -176,7 +176,7 @@ async fn fetch_messages_from_api( } } - let sender_id = msg.sender().map(|s| s.id().bare_id()).unwrap_or(0); + let sender_id = msg.sender().and_then(|s| s.id().bare_id()).unwrap_or(0); let from_me = msg.outgoing(); messages.push(ExportMessage { @@ -489,14 +489,12 @@ fn parse_date(s: &str) -> Result> { ) } -async fn resolve_peer_ref(app: &App, chat_id: i64) -> Result { - use grammers_session::defs::PeerRef; - +async fn resolve_peer_ref(app: &App, chat_id: i64) -> Result { let mut dialogs = app.tg.client.iter_dialogs(); while let Some(dialog) = dialogs.next().await? { let peer = dialog.peer(); - if peer.id().bare_id() == chat_id { - return Ok(PeerRef::from(peer)); + if peer.id().bare_id() == Some(chat_id) { + return crate::tg::peer_to_ref_async(peer).await; } } anyhow::bail!( diff --git a/src/cmd/folders.rs b/src/cmd/folders.rs index 4173cf9..767e3ce 100644 --- a/src/cmd/folders.rs +++ b/src/cmd/folders.rs @@ -4,7 +4,7 @@ use crate::out::markdown::{format_folder_chats, format_folders, FolderChatMd, Fo use crate::Cli; use anyhow::Result; use clap::Subcommand; -use grammers_session::defs::{PeerId, PeerRef}; +use grammers_session::types::PeerId; use grammers_session::Session; use grammers_tl_types as tl; use serde::Serialize; @@ -802,34 +802,19 @@ async fn resolve_chat_to_input_peer(app: &App, chat_id: i64) -> Result = app.get_store().await?.get_chat(chat_id).await?; - // Try to find via session - let channel_peer_id = PeerId::channel(chat_id); - if let Some(info) = app.tg.session.peer(channel_peer_id) { - let peer_ref = PeerRef { - id: channel_peer_id, - auth: info.auth(), - }; - return Ok(peer_ref.into()); - } - - // Try as user - let user_peer_id = PeerId::user(chat_id); - if let Some(info) = app.tg.session.peer(user_peer_id) { - let peer_ref = PeerRef { - id: user_peer_id, - auth: info.auth(), - }; - return Ok(peer_ref.into()); - } - - // Try as small group chat - if chat_id > 0 && chat_id <= 999999999999 { - let chat_peer_id = PeerId::chat(chat_id); - if let Some(info) = app.tg.session.peer(chat_peer_id) { - let peer_ref = PeerRef { - id: chat_peer_id, - auth: info.auth(), - }; + // Try the session cache as a channel, then a user, then a small group chat. + // `peer_ref` yields `None` for peers cached without usable auth, so those + // fall through to the dialog scan below rather than producing a `PeerRef` + // with a zeroed access_hash. + for peer_id in [ + PeerId::channel(chat_id), + PeerId::user(chat_id), + PeerId::chat(chat_id), + ] + .into_iter() + .flatten() + { + if let Some(peer_ref) = app.tg.session.peer_ref(peer_id).await? { return Ok(peer_ref.into()); } } @@ -839,8 +824,8 @@ async fn resolve_chat_to_input_peer(app: &App, chat_id: i64) -> Result Result<()> { }; let profile = ProfileInfo { - id: me.bare_id(), + id: me.id().bare_id().unwrap_or(0), first_name: me.first_name().unwrap_or("").to_string(), last_name: me.last_name().map(|s| s.to_string()), username: me.username().map(|s| s.to_string()), diff --git a/src/cmd/users.rs b/src/cmd/users.rs index 4ea5569..7d505d3 100644 --- a/src/cmd/users.rs +++ b/src/cmd/users.rs @@ -4,7 +4,7 @@ use crate::out::markdown::{ToMarkdown, UserInfoMd}; use crate::Cli; use anyhow::{Context, Result}; use clap::Subcommand; -use grammers_session::defs::{PeerId, PeerRef}; +use grammers_session::types::PeerId; use grammers_session::Session; use grammers_tl_types as tl; use serde::Serialize; @@ -221,23 +221,22 @@ async fn block_user(cli: &Cli, user_id: i64, block: bool) -> Result<()> { /// Resolve a user ID to an InputUser for API calls. async fn resolve_user_to_input_user(app: &App, user_id: i64) -> Result { - // First check session for the user's access_hash - let user_peer_id = PeerId::user(user_id); - if let Some(info) = app.tg.session.peer(user_peer_id) { - let peer_ref = PeerRef { - id: user_peer_id, - auth: info.auth(), - }; - // PeerRef has From for tl::enums::InputUser - return Ok(peer_ref.into()); + // First check session for the user's access_hash. `peer_ref` yields `None` + // for peers cached without usable auth, so those fall through to the + // dialog scan below instead of producing a zeroed access_hash. + if let Some(user_peer_id) = PeerId::user(user_id) { + if let Some(peer_ref) = app.tg.session.peer_ref(user_peer_id).await? { + // PeerRef has From for tl::enums::InputUser + return Ok(peer_ref.into()); + } } // Try to find user in dialogs let mut dialogs = app.tg.client.iter_dialogs(); while let Some(dialog) = dialogs.next().await? { let peer = dialog.peer(); - if peer.id().bare_id() == user_id { - let peer_ref = PeerRef::from(peer); + if peer.id().bare_id() == Some(user_id) { + let peer_ref = crate::tg::peer_to_ref_async(peer).await?; return Ok(peer_ref.into()); } } @@ -250,22 +249,21 @@ async fn resolve_user_to_input_user(app: &App, user_id: i64) -> Result Result { - // First check session for the user's access_hash - let user_peer_id = PeerId::user(user_id); - if let Some(info) = app.tg.session.peer(user_peer_id) { - let peer_ref = PeerRef { - id: user_peer_id, - auth: info.auth(), - }; - return Ok(peer_ref.into()); + // First check session for the user's access_hash. `peer_ref` yields `None` + // for peers cached without usable auth, so those fall through to the + // dialog scan below instead of producing a zeroed access_hash. + if let Some(user_peer_id) = PeerId::user(user_id) { + if let Some(peer_ref) = app.tg.session.peer_ref(user_peer_id).await? { + return Ok(peer_ref.into()); + } } // Try to find user in dialogs let mut dialogs = app.tg.client.iter_dialogs(); while let Some(dialog) = dialogs.next().await? { let peer = dialog.peer(); - if peer.id().bare_id() == user_id { - return Ok(PeerRef::from(peer).into()); + if peer.id().bare_id() == Some(user_id) { + return Ok(crate::tg::peer_to_ref_async(peer).await?.into()); } } diff --git a/src/tg/mod.rs b/src/tg/mod.rs index d0ad27e..0f525e1 100644 --- a/src/tg/mod.rs +++ b/src/tg/mod.rs @@ -1,8 +1,11 @@ use anyhow::{Context, Result}; +use grammers_client::peer::{Group, Peer, User}; use grammers_client::Client; use grammers_mtsender::SenderPool; use grammers_session::storages::SqliteSession; +use grammers_session::types::{PeerAuth, PeerRef}; use grammers_session::updates::UpdatesLike; +use grammers_tl_types as tl; use std::sync::Arc; use tokio::sync::mpsc; @@ -20,10 +23,10 @@ pub struct TgClient { impl TgClient { /// Connect with updates support. /// Returns the client and an updates receiver. - pub fn connect_with_updates( + pub async fn connect_with_updates( session_path: &str, ) -> Result<(Self, mpsc::UnboundedReceiver)> { - let session = Arc::new(SqliteSession::open(session_path).with_context(|| { + let session = Arc::new(SqliteSession::open(session_path).await.with_context(|| { format!( "Failed to open session database at '{}'. Check file permissions.", session_path @@ -31,7 +34,7 @@ impl TgClient { })?); let pool = SenderPool::new(Arc::clone(&session) as Arc, API_ID); - let client = Client::new(&pool); + let client = Client::new(pool.handle); let SenderPool { runner, updates, .. @@ -52,6 +55,65 @@ impl TgClient { } } +/// Resolve a [`Peer`] to a [`PeerRef`]. +/// +/// Prefers grammers' own resolution ([`Peer::to_ref`]), which uses the peer's +/// non-`min` auth when it has one and otherwise consults the session cache. +/// When neither is available, fall back to whatever access hash the raw peer +/// carries, which is what the pre-0.10 `PeerRef::from(&peer)` did. +pub async fn peer_to_ref_async(peer: &Peer) -> Result { + let cached = peer + .to_ref() + .await + .map_err(|e| anyhow::anyhow!("Failed to look up peer in the session cache: {e}"))?; + + Ok(cached.unwrap_or_else(|| peer_to_ref(peer))) +} + +/// Build a [`PeerRef`] from the access hash carried by the raw peer object, +/// falling back to ambient auth. +/// +/// Only used when [`Peer::to_ref`] cannot produce a reference; this replicates +/// the pre-0.10 `PeerRef::from(&peer)` behavior. +fn peer_to_ref(peer: &Peer) -> PeerRef { + let auth = match peer { + Peer::User(user) => user_auth(user), + Peer::Channel(channel) => channel_auth(&channel.raw), + Peer::Group(group) => group_auth(group), + }; + PeerRef { + id: peer.id(), + auth, + } +} + +fn user_auth(user: &User) -> PeerAuth { + match &user.raw { + tl::enums::User::User(inner) => inner + .access_hash + .map(PeerAuth::from_hash) + .unwrap_or_default(), + _ => PeerAuth::default(), + } +} + +/// A [`Group`] wraps `tl::enums::Chat`, which is either a basic group (addressed +/// by ID alone) or a megagroup/gigagroup channel that does need an access hash. +fn group_auth(group: &Group) -> PeerAuth { + match &group.raw { + tl::enums::Chat::Channel(channel) => channel_auth(channel), + tl::enums::Chat::ChannelForbidden(channel) => PeerAuth::from_hash(channel.access_hash), + _ => PeerAuth::default(), + } +} + +fn channel_auth(channel: &tl::types::Channel) -> PeerAuth { + channel + .access_hash + .map(PeerAuth::from_hash) + .unwrap_or_default() +} + impl Drop for TgClient { fn drop(&mut self) { self.client.disconnect();