From 838f291220d4995c90cbfb7d122d1a49a57b74a8 Mon Sep 17 00:00:00 2001 From: Sumner Evans Date: Thu, 8 Aug 2024 12:12:17 -0600 Subject: [PATCH] store: move the access_hash and username to separate per-user table Signed-off-by: Sumner Evans --- pkg/connector/chatinfo.go | 2 +- pkg/connector/client.go | 43 ++++++++---- pkg/connector/config.go | 6 +- pkg/connector/store/scoped_store.go | 65 +++++++++++++++++++ pkg/connector/store/upgrades/00-latest.sql | 10 +++ .../03-move-user-access-hash-to-table.sql | 15 +++++ pkg/connector/telegram.go | 10 +-- 7 files changed, 129 insertions(+), 22 deletions(-) create mode 100644 pkg/connector/store/upgrades/03-move-user-access-hash-to-table.sql diff --git a/pkg/connector/chatinfo.go b/pkg/connector/chatinfo.go index e9e85c52..2930513d 100644 --- a/pkg/connector/chatinfo.go +++ b/pkg/connector/chatinfo.go @@ -25,7 +25,7 @@ func (t *TelegramClient) getDMChatInfo(ctx context.Context, userID int64) (*brid return nil, err } else if len(users) == 0 { return nil, fmt.Errorf("failed to get user info for user %d", userID) - } else if userInfo, err := t.getUserInfoFromTelegramUser(users[0]); err != nil { + } else if userInfo, err := t.getUserInfoFromTelegramUser(ctx, users[0]); err != nil { return nil, err } else if err = t.updateGhostWithUserInfo(ctx, userID, userInfo); err != nil { return nil, err diff --git a/pkg/connector/client.go b/pkg/connector/client.go index ad60bb83..3fc6a7aa 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -247,13 +247,19 @@ func NewTelegramClient(ctx context.Context, tc *TelegramConnector, login *bridge } client.matrixParser = &matrixfmt.HTMLParser{ GetGhostDetails: func(ctx context.Context, ui id.UserID) (networkid.UserID, string, int64, bool) { - if userID, ok := tc.Bridge.Matrix.ParseGhostMXID(ui); !ok { + userID, ok := tc.Bridge.Matrix.ParseGhostMXID(ui) + if !ok { return "", "", 0, false - } else if ghost, err := tc.Bridge.GetGhostByID(ctx, userID); err != nil { - return "", "", 0, false - } else { - return userID, ghost.Metadata.(*GhostMetadata).Username, ghost.Metadata.(*GhostMetadata).AccessHash, true } + telegramUserID, err := ids.ParseUserID(userID) + if err != nil { + return "", "", 0, false + } + username, accessHash, found, err := tc.Store.GetScopedStore(telegramUserID).GetUserMetadata(ctx, telegramUserID) + if err != nil || !found { + return "", "", 0, false + } + return userID, username, accessHash, true }, } @@ -330,9 +336,15 @@ func (t *TelegramClient) GetUserInfo(ctx context.Context, ghost *bridgev2.Ghost) if err != nil { return nil, err } + accessHash, found, err := t.ScopedStore.GetUserAccessHash(ctx, id) + if err != nil { + return nil, fmt.Errorf("failed to get access hash for user %d: %w", id, err) + } else if !found { + return nil, fmt.Errorf("access hash not found for user %d", id) + } users, err := t.client.API().UsersGetUsers(ctx, []tg.InputUserClass{&tg.InputUser{ UserID: id, - AccessHash: ghost.Metadata.(*GhostMetadata).AccessHash, + AccessHash: accessHash, }}) if err != nil { return nil, err @@ -340,20 +352,28 @@ func (t *TelegramClient) GetUserInfo(ctx context.Context, ghost *bridgev2.Ghost) if len(users) == 0 { return nil, fmt.Errorf("failed to get user info for user %d", id) } - userInfo, err := t.getUserInfoFromTelegramUser(users[0]) + userInfo, err := t.getUserInfoFromTelegramUser(ctx, users[0]) if err != nil { return nil, err } return userInfo, t.updateGhostWithUserInfo(ctx, id, userInfo) } -func (t *TelegramClient) getUserInfoFromTelegramUser(u tg.UserClass) (*bridgev2.UserInfo, error) { +func (t *TelegramClient) getUserInfoFromTelegramUser(ctx context.Context, u tg.UserClass) (*bridgev2.UserInfo, error) { user, ok := u.(*tg.User) if !ok { return nil, fmt.Errorf("user is %T not *tg.User", user) } var identifiers []string - if !user.Min { + if user.Min { + if err := t.ScopedStore.SetUserAccessHash(ctx, user.ID, user.AccessHash); err != nil { + return nil, err + } + } else { + if err := t.ScopedStore.SetUserMetadata(ctx, user.ID, user.Username, user.AccessHash); err != nil { + return nil, err + } + if username, ok := user.GetUsername(); ok { identifiers = append(identifiers, fmt.Sprintf("telegram:%s", username)) } @@ -388,13 +408,10 @@ func (t *TelegramClient) getUserInfoFromTelegramUser(u tg.UserClass) (*bridgev2. ExtraUpdates: func(ctx context.Context, ghost *bridgev2.Ghost) (changed bool) { meta := ghost.Metadata.(*GhostMetadata) if !user.Min { - changed = changed || meta.IsPremium != user.Premium || meta.IsBot != user.Bot || meta.Username != user.Username + changed = changed || meta.IsPremium != user.Premium || meta.IsBot != user.Bot meta.IsPremium = user.Premium meta.IsBot = user.Bot - meta.Username = user.Username } - changed = changed || meta.AccessHash != user.AccessHash - meta.AccessHash = user.AccessHash return changed }, }, nil diff --git a/pkg/connector/config.go b/pkg/connector/config.go index f22e22af..a1b3444d 100644 --- a/pkg/connector/config.go +++ b/pkg/connector/config.go @@ -88,10 +88,8 @@ func (tg *TelegramConnector) GetDBMetaTypes() database.MetaTypes { } type GhostMetadata struct { - AccessHash int64 `json:"access_hash"` - IsPremium bool `json:"is_premium,omitempty"` - IsBot bool `json:"is_bot,omitempty"` - Username string `json:"username,omitempty"` + IsPremium bool `json:"is_premium,omitempty"` + IsBot bool `json:"is_bot,omitempty"` } type MessageMetadata struct { diff --git a/pkg/connector/store/scoped_store.go b/pkg/connector/store/scoped_store.go index baa67d44..622e661b 100644 --- a/pkg/connector/store/scoped_store.go +++ b/pkg/connector/store/scoped_store.go @@ -58,6 +58,32 @@ const ( VALUES ($1, $2, $3) ON CONFLICT (user_id, channel_id) DO UPDATE SET access_hash=excluded.access_hash ` + + // User Access Hash Queries + getUserAccessHashQuery = "SELECT access_hash FROM telegram_user_metadata WHERE receiver_id=$1 AND user_id=$2" + setUserAccessHashQuery = ` + INSERT INTO telegram_user_metadata (receiver_id, user_id, access_hash) + VALUES ($1, $2, $3) + ON CONFLICT (receiver_id, user_id) DO UPDATE SET access_hash=excluded.access_hash + ` + + // User Username Queries + getUserUsernameQuery = "SELECT username FROM telegram_user_metadata WHERE receiver_id=$1 AND user_id=$2" + setUserUsernameQuery = ` + INSERT INTO telegram_user_metadata (receiver_id, user_id, username) + VALUES ($1, $2, $3) + ON CONFLICT (receiver_id, user_id) DO UPDATE SET username=excluded.username + ` + + // User Metadata Queries + getUserMetadataQuery = "SELECT username, access_hash FROM telegram_user_metadata WHERE receiver_id=$1 AND user_id=$2" + setUserMetadataQuery = ` + INSERT INTO telegram_user_metadata (receiver_id, user_id, username, access_hash) + VALUES ($1, $2, $3, $4) + ON CONFLICT (receiver_id, user_id) DO UPDATE SET + username=excluded.username, + access_hash=excluded.access_hash + ` ) var _ session.Storage = (*ScopedStore)(nil) @@ -170,6 +196,45 @@ func (s *ScopedStore) SetChannelAccessHash(ctx context.Context, userID int64, ch return } +func (s *ScopedStore) GetUserAccessHash(ctx context.Context, userID int64) (accessHash int64, found bool, err error) { + err = s.db.QueryRow(ctx, getUserAccessHashQuery, s.telegramUserID, userID).Scan(&accessHash) + if errors.Is(err, sql.ErrNoRows) { + return 0, false, nil + } + return accessHash, err == nil, err +} + +func (s *ScopedStore) SetUserAccessHash(ctx context.Context, userID, accessHash int64) (err error) { + _, err = s.db.Exec(ctx, setUserAccessHashQuery, s.telegramUserID, userID, accessHash) + return +} + +func (s *ScopedStore) GetUserUsername(ctx context.Context, userID int64) (username string, found bool, err error) { + err = s.db.QueryRow(ctx, getUserUsernameQuery, s.telegramUserID, userID).Scan(&username) + if errors.Is(err, sql.ErrNoRows) { + return "", false, nil + } + return username, err == nil, err +} + +func (s *ScopedStore) SetUserUsername(ctx context.Context, userID int64, username string) (err error) { + _, err = s.db.Exec(ctx, setUserUsernameQuery, s.telegramUserID, userID, username) + return +} + +func (s *ScopedStore) GetUserMetadata(ctx context.Context, userID int64) (username string, accessHash int64, found bool, err error) { + err = s.db.QueryRow(ctx, getUserMetadataQuery, s.telegramUserID, userID).Scan(&username, &accessHash) + if errors.Is(err, sql.ErrNoRows) { + return "", 0, false, nil + } + return username, accessHash, err == nil, err +} + +func (s *ScopedStore) SetUserMetadata(ctx context.Context, userID int64, username string, accessHash int64) (err error) { + _, err = s.db.Exec(ctx, setUserMetadataQuery, s.telegramUserID, userID, username, accessHash) + return +} + // Helper Functions func (s *ScopedStore) assertUserIDMatches(userID int64) { diff --git a/pkg/connector/store/upgrades/00-latest.sql b/pkg/connector/store/upgrades/00-latest.sql index 4f771ccb..2b53580a 100644 --- a/pkg/connector/store/upgrades/00-latest.sql +++ b/pkg/connector/store/upgrades/00-latest.sql @@ -31,6 +31,16 @@ CREATE TABLE telegram_channel_access_hashes ( PRIMARY KEY (user_id, channel_id) ); +CREATE TABLE telegram_user_metadata ( + receiver_id INTEGER, + user_id INTEGER, + + access_hash INTEGER NOT NULL, + username TEXT, + + PRIMARY KEY (receiver_id, user_id) +); + CREATE TABLE telegram_file ( id TEXT PRIMARY KEY, mxc TEXT NOT NULL, diff --git a/pkg/connector/store/upgrades/03-move-user-access-hash-to-table.sql b/pkg/connector/store/upgrades/03-move-user-access-hash-to-table.sql new file mode 100644 index 00000000..1e885eec --- /dev/null +++ b/pkg/connector/store/upgrades/03-move-user-access-hash-to-table.sql @@ -0,0 +1,15 @@ +-- v3: Move the user access hash to a table so it can be per-user + +CREATE TABLE telegram_user_metadata ( + receiver_id INTEGER, + user_id INTEGER, + + access_hash INTEGER NOT NULL, + username TEXT, + + PRIMARY KEY (receiver_id, user_id) +); + +INSERT INTO telegram_user_metadata (receiver_id, user_id, access_hash, username) +SELECT ul.id, g.id, g.metadata->>'access_hash', g.metadata->>'username' +FROM user_login ul, ghost g; diff --git a/pkg/connector/telegram.go b/pkg/connector/telegram.go index 966095e7..7bf2b3d5 100644 --- a/pkg/connector/telegram.go +++ b/pkg/connector/telegram.go @@ -235,7 +235,7 @@ func (t *TelegramClient) updateGhost(ctx context.Context, userID int64, user *tg if err != nil { return err } - userInfo, err := t.getUserInfoFromTelegramUser(user) + userInfo, err := t.getUserInfoFromTelegramUser(ctx, user) if err != nil { return err } @@ -468,10 +468,12 @@ func (t *TelegramClient) inputPeerForPortalID(ctx context.Context, portalID netw } switch peerType { case ids.PeerTypeUser: - if ghost, err := t.main.Bridge.DB.Ghost.GetByID(ctx, ids.MakeUserID(id)); err != nil { - return nil, err + if accessHash, found, err := t.ScopedStore.GetUserAccessHash(ctx, id); err != nil { + return nil, fmt.Errorf("failed to get user access hash for %d: %w", id, err) + } else if !found { + return nil, fmt.Errorf("user access hash not found for %d", id) } else { - return &tg.InputPeerUser{UserID: id, AccessHash: ghost.Metadata.(*GhostMetadata).AccessHash}, nil + return &tg.InputPeerUser{UserID: id, AccessHash: accessHash}, nil } case ids.PeerTypeChat: return &tg.InputPeerChat{ChatID: id}, nil