Skip to content

Commit d99b2fa

Browse files
committed
fix(sync): store access_hash for local-only mode
- Add access_hash column to chats table - Update upsert_chat() to accept and store access_hash - Extract access_hash from peers during regular sync - Use stored access_hash in resolve_peer_from_session() as fallback - Fix sync subcommands (chats/msgs) to work with local_only option This enables --local-only sync to work without needing iter_dialogs() first, by using the stored access_hash to construct valid InputPeer.
1 parent 47cc5db commit d99b2fa

4 files changed

Lines changed: 221 additions & 78 deletions

File tree

src/app/send.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ impl App {
6565

6666
// Update chat's last_message_ts
6767
self.store
68-
.upsert_chat(chat_id, "user", "", None, Some(now), false)
68+
.upsert_chat(chat_id, "user", "", None, Some(now), false, None)
6969
.await?;
7070

7171
Ok(msg.id() as i64)
@@ -196,7 +196,7 @@ impl App {
196196

197197
// Update chat's last_message_ts
198198
self.store
199-
.upsert_chat(chat_id, "user", "", None, Some(now), false)
199+
.upsert_chat(chat_id, "user", "", None, Some(now), false, None)
200200
.await?;
201201

202202
Ok(msg_id)
@@ -279,7 +279,7 @@ impl App {
279279

280280
// Update chat's last_message_ts
281281
self.store
282-
.upsert_chat(chat_id, "user", "", None, Some(now), false)
282+
.upsert_chat(chat_id, "user", "", None, Some(now), false, None)
283283
.await?;
284284

285285
Ok(msg_id)
@@ -542,7 +542,7 @@ impl App {
542542

543543
// Update chat's last_message_ts
544544
self.store
545-
.upsert_chat(chat_id, "user", "", None, Some(now), false)
545+
.upsert_chat(chat_id, "user", "", None, Some(now), false, None)
546546
.await?;
547547

548548
Ok(msg.id() as i64)
@@ -587,7 +587,7 @@ impl App {
587587

588588
// Update chat's last_message_ts
589589
self.store
590-
.upsert_chat(chat_id, "user", "", None, Some(now), false)
590+
.upsert_chat(chat_id, "user", "", None, Some(now), false, None)
591591
.await?;
592592

593593
Ok(msg.id() as i64)
@@ -644,7 +644,7 @@ impl App {
644644

645645
// Update chat's last_message_ts
646646
self.store
647-
.upsert_chat(chat_id, "user", "", None, Some(now), false)
647+
.upsert_chat(chat_id, "user", "", None, Some(now), false, None)
648648
.await?;
649649

650650
Ok(msg.id() as i64)
@@ -693,7 +693,7 @@ impl App {
693693

694694
// Update chat's last_message_ts
695695
self.store
696-
.upsert_chat(chat_id, "user", "", None, Some(now), false)
696+
.upsert_chat(chat_id, "user", "", None, Some(now), false, None)
697697
.await?;
698698

699699
Ok(msg.id() as i64)
@@ -748,7 +748,7 @@ impl App {
748748

749749
// Update chat's last_message_ts
750750
self.store
751-
.upsert_chat(chat_id, "user", "", None, Some(now), false)
751+
.upsert_chat(chat_id, "user", "", None, Some(now), false, None)
752752
.await?;
753753

754754
Ok(msg.id() as i64)
@@ -1019,7 +1019,7 @@ impl App {
10191019

10201020
// Update chat's last_message_ts
10211021
self.store
1022-
.upsert_chat(chat_id, "user", "", None, Some(now), false)
1022+
.upsert_chat(chat_id, "user", "", None, Some(now), false, None)
10231023
.await?;
10241024

10251025
Ok(msg_id)

src/app/sync.rs

Lines changed: 105 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use crate::store::UpsertMessageParams;
33
use anyhow::{Context, Result};
44
use chrono::{DateTime, Utc};
55
use grammers_client::types::{Media, Message as TgMessage, Peer};
6-
use grammers_session::defs::{PeerId, PeerRef};
6+
use grammers_session::defs::{PeerAuth, PeerId, PeerRef};
77
use grammers_session::Session;
88
use grammers_tl_types as tl;
99
use std::collections::HashSet;
@@ -147,8 +147,14 @@ pub struct SyncResult {
147147

148148
impl App {
149149
/// Try to resolve a chat ID to a PeerRef from the session cache (no API calls).
150-
/// Returns None if the chat is not cached in the session.
151-
fn resolve_peer_from_session(&self, chat_id: i64, kind: &str) -> Option<PeerRef> {
150+
/// If the peer is not in the session cache but we have a stored access_hash, use that.
151+
/// Returns None if the chat is not cached and we have no stored access_hash.
152+
fn resolve_peer_from_session(
153+
&self,
154+
chat_id: i64,
155+
kind: &str,
156+
stored_access_hash: Option<i64>,
157+
) -> Option<PeerRef> {
152158
// Try based on the known type first
153159
match kind {
154160
"channel" | "group" => {
@@ -160,6 +166,13 @@ impl App {
160166
auth: info.auth(),
161167
});
162168
}
169+
// Fallback to stored access_hash if available
170+
if let Some(hash) = stored_access_hash {
171+
return Some(PeerRef {
172+
id: channel_peer_id,
173+
auth: PeerAuth::from_hash(hash),
174+
});
175+
}
163176
}
164177
"user" => {
165178
let user_peer_id = PeerId::user(chat_id);
@@ -169,11 +182,18 @@ impl App {
169182
auth: info.auth(),
170183
});
171184
}
185+
// Fallback to stored access_hash if available
186+
if let Some(hash) = stored_access_hash {
187+
return Some(PeerRef {
188+
id: user_peer_id,
189+
auth: PeerAuth::from_hash(hash),
190+
});
191+
}
172192
}
173193
_ => {}
174194
}
175195

176-
// Fallback: try all peer types
196+
// Fallback: try all peer types from session cache
177197
// Try as channel first (most common for groups)
178198
let channel_peer_id = PeerId::channel(chat_id);
179199
if let Some(info) = self.tg.session.peer(channel_peer_id) {
@@ -201,6 +221,22 @@ impl App {
201221
auth: info.auth(),
202222
});
203223
}
224+
// Small group chats don't need access_hash, so try with default auth
225+
if stored_access_hash.is_some() {
226+
return Some(PeerRef {
227+
id: chat_peer_id,
228+
auth: PeerAuth::default(),
229+
});
230+
}
231+
}
232+
233+
// Last resort: try to construct from stored access_hash with best-guess peer type
234+
if let Some(hash) = stored_access_hash {
235+
// Most likely a channel/group if we have an access_hash
236+
return Some(PeerRef {
237+
id: channel_peer_id,
238+
auth: PeerAuth::from_hash(hash),
239+
});
204240
}
205241

206242
None
@@ -312,15 +348,19 @@ impl App {
312348
continue;
313349
}
314350

315-
// Try to resolve peer from session (no API call)
316-
let peer_ref = match self.resolve_peer_from_session(chat.id, &chat.kind) {
351+
// Try to resolve peer from session or stored access_hash (no API call)
352+
let peer_ref = match self.resolve_peer_from_session(
353+
chat.id,
354+
&chat.kind,
355+
chat.access_hash,
356+
) {
317357
Some(p) => p,
318358
None => {
319359
log::debug!(
320-
"Skipping chat {} ({}) - not in session cache",
321-
chat.name,
322-
chat.id
323-
);
360+
"Skipping chat {} ({}) - not in session cache and no stored access_hash",
361+
chat.name,
362+
chat.id
363+
);
324364
continue;
325365
}
326366
};
@@ -497,6 +537,7 @@ impl App {
497537
chat.username.as_deref(),
498538
Some(ts),
499539
chat.is_forum,
540+
chat.access_hash,
500541
)
501542
.await?;
502543
}
@@ -596,7 +637,7 @@ impl App {
596637
.context("Failed to fetch dialogs from Telegram")?
597638
{
598639
let peer = dialog.peer();
599-
let (kind, name, username, is_forum) = peer_info(peer);
640+
let (kind, name, username, is_forum, access_hash) = peer_info(peer);
600641
let id = peer.id().bare_id();
601642

602643
// Skip ignored chats.
@@ -605,7 +646,15 @@ impl App {
605646
}
606647

607648
self.store
608-
.upsert_chat(id, &kind, &name, username.as_deref(), None, is_forum)
649+
.upsert_chat(
650+
id,
651+
&kind,
652+
&name,
653+
username.as_deref(),
654+
None,
655+
is_forum,
656+
access_hash,
657+
)
609658
.await?;
610659
chats_stored += 1;
611660

@@ -796,7 +845,15 @@ impl App {
796845
// Update chat's last_message_ts
797846
if let Some(ts) = latest_ts {
798847
self.store
799-
.upsert_chat(id, &kind, &name, username.as_deref(), Some(ts), is_forum)
848+
.upsert_chat(
849+
id,
850+
&kind,
851+
&name,
852+
username.as_deref(),
853+
Some(ts),
854+
is_forum,
855+
access_hash,
856+
)
800857
.await?;
801858
}
802859

@@ -874,7 +931,7 @@ impl App {
874931

875932
let archived_peers = self.fetch_archived_dialogs().await?;
876933
for peer in archived_peers {
877-
let (kind, name, username, is_forum) = peer_info(&peer);
934+
let (kind, name, username, is_forum, access_hash) = peer_info(&peer);
878935
let id = peer.id().bare_id();
879936

880937
// Skip ignored chats.
@@ -883,7 +940,15 @@ impl App {
883940
}
884941

885942
self.store
886-
.upsert_chat(id, &kind, &name, username.as_deref(), None, is_forum)
943+
.upsert_chat(
944+
id,
945+
&kind,
946+
&name,
947+
username.as_deref(),
948+
None,
949+
is_forum,
950+
access_hash,
951+
)
887952
.await?;
888953
chats_stored += 1;
889954

@@ -1013,7 +1078,15 @@ impl App {
10131078
// Update chat's last_message_ts
10141079
if let Some(ts) = latest_ts {
10151080
self.store
1016-
.upsert_chat(id, &kind, &name, username.as_deref(), Some(ts), is_forum)
1081+
.upsert_chat(
1082+
id,
1083+
&kind,
1084+
&name,
1085+
username.as_deref(),
1086+
Some(ts),
1087+
is_forum,
1088+
access_hash,
1089+
)
10171090
.await?;
10181091
}
10191092

@@ -1282,28 +1355,36 @@ impl App {
12821355
}
12831356
}
12841357

1285-
/// Returns (kind, name, username, is_forum)
1286-
fn peer_info(peer: &Peer) -> (String, String, Option<String>, bool) {
1358+
/// Returns (kind, name, username, is_forum, access_hash)
1359+
fn peer_info(peer: &Peer) -> (String, String, Option<String>, bool, Option<i64>) {
12871360
match peer {
12881361
Peer::User(user) => {
12891362
let name = user.full_name();
12901363
let username = user.username().map(|s| s.to_string());
1291-
("user".to_string(), name, username, false)
1364+
// Extract access_hash from User raw type
1365+
let access_hash = match &user.raw {
1366+
tl::enums::User::User(u) => u.access_hash,
1367+
tl::enums::User::Empty(_) => None,
1368+
};
1369+
("user".to_string(), name, username, false, access_hash)
12921370
}
12931371
Peer::Group(group) => {
12941372
let name = group.title().map(|s| s.to_string()).unwrap_or_default();
12951373
let username = group.username().map(|s| s.to_string());
12961374
// Check if this is a forum group (megagroup with forum flag)
1297-
let is_forum = match &group.raw {
1298-
tl::enums::Chat::Channel(channel) => channel.forum,
1299-
_ => false,
1375+
// Extract access_hash from Channel (megagroups are channels internally)
1376+
let (is_forum, access_hash) = match &group.raw {
1377+
tl::enums::Chat::Channel(channel) => (channel.forum, channel.access_hash),
1378+
_ => (false, None),
13001379
};
1301-
("group".to_string(), name, username, is_forum)
1380+
("group".to_string(), name, username, is_forum, access_hash)
13021381
}
13031382
Peer::Channel(channel) => {
13041383
let name = channel.title().to_string();
13051384
let username = channel.username().map(|s| s.to_string());
1306-
("channel".to_string(), name, username, false)
1385+
// Extract access_hash from Channel raw type (it's directly a tl::types::Channel)
1386+
let access_hash = channel.raw.access_hash;
1387+
("channel".to_string(), name, username, false, access_hash)
13071388
}
13081389
}
13091390
}

0 commit comments

Comments
 (0)