all: add support for topics and refactor other things
This commit is contained in:
@@ -20,6 +20,7 @@ import (
|
||||
"context"
|
||||
|
||||
"go.mau.fi/util/dbutil"
|
||||
"go.mau.fi/util/exsync"
|
||||
|
||||
"go.mau.fi/mautrix-telegram/pkg/connector/store/upgrades"
|
||||
)
|
||||
@@ -28,6 +29,9 @@ type Container struct {
|
||||
*dbutil.Database
|
||||
|
||||
TelegramFile *TelegramFileQuery
|
||||
Username *UsernameQuery
|
||||
PhoneNumber *PhoneNumberQuery
|
||||
Topic *TopicQuery
|
||||
}
|
||||
|
||||
func NewStore(db *dbutil.Database, log dbutil.DatabaseLogger) *Container {
|
||||
@@ -35,6 +39,9 @@ func NewStore(db *dbutil.Database, log dbutil.DatabaseLogger) *Container {
|
||||
Database: db.Child("telegram_version", upgrades.Table, log),
|
||||
|
||||
TelegramFile: &TelegramFileQuery{dbutil.MakeQueryHelper(db, newTelegramFile)},
|
||||
Username: &UsernameQuery{db},
|
||||
PhoneNumber: &PhoneNumberQuery{db},
|
||||
Topic: &TopicQuery{db: db, existingTopics: exsync.NewSet[topicKey]()},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
// mautrix-telegram - A Matrix-Telegram puppeting bridge.
|
||||
// Copyright (C) 2025 Tulir Asokan
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
|
||||
"go.mau.fi/util/dbutil"
|
||||
)
|
||||
|
||||
type PhoneNumberQuery struct {
|
||||
db *dbutil.Database
|
||||
}
|
||||
|
||||
const (
|
||||
getEntityIDForPhoneNumber = "SELECT entity_id FROM telegram_phone_number WHERE phone_number=$1"
|
||||
setPhoneNumberQuery = `
|
||||
INSERT INTO telegram_phone_number (phone_number, entity_id)
|
||||
VALUES ($1, $2)
|
||||
ON CONFLICT (phone_number) DO UPDATE SET entity_id=excluded.entity_id
|
||||
`
|
||||
clearPhoneNumberQuery = "DELETE FROM telegram_phone_number WHERE entity_id=$1"
|
||||
)
|
||||
|
||||
func (s *PhoneNumberQuery) GetUserID(ctx context.Context, phoneNumber string) (userID int64, err error) {
|
||||
err = s.db.QueryRow(ctx, getEntityIDForPhoneNumber, phoneNumber).Scan(&userID)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *PhoneNumberQuery) Set(ctx context.Context, userID int64, phoneNumber string) (err error) {
|
||||
if phoneNumber == "" {
|
||||
_, err = s.db.Exec(ctx, clearPhoneNumberQuery, userID)
|
||||
} else {
|
||||
_, err = s.db.Exec(ctx, setPhoneNumberQuery, phoneNumber, userID)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -21,7 +21,6 @@ import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"go.mau.fi/util/dbutil"
|
||||
|
||||
@@ -71,27 +70,6 @@ const (
|
||||
ON CONFLICT (user_id, entity_type, entity_id) DO UPDATE SET access_hash=excluded.access_hash
|
||||
`
|
||||
deleteAccessHashesForUserQuery = "DELETE FROM telegram_access_hash WHERE user_id=$1"
|
||||
|
||||
// User Username Queries
|
||||
getUsernameQuery = "SELECT username FROM telegram_username WHERE entity_type=$1 AND entity_id=$2"
|
||||
setUsernameQuery = `
|
||||
INSERT INTO telegram_username (username, entity_type, entity_id)
|
||||
VALUES ($1, $2, $3)
|
||||
ON CONFLICT (username) DO UPDATE SET
|
||||
entity_type=excluded.entity_type,
|
||||
entity_id=excluded.entity_id
|
||||
`
|
||||
getByUsernameQuery = "SELECT entity_type, entity_id FROM telegram_username WHERE LOWER(username)=$1"
|
||||
clearUsernameQuery = `DELETE FROM telegram_username WHERE entity_type=$1 AND entity_id=$2`
|
||||
|
||||
// User Phone Number Queries
|
||||
getEntityIDForPhoneNumber = "SELECT entity_id FROM telegram_phone_number WHERE phone_number=$1"
|
||||
setPhoneNumberQuery = `
|
||||
INSERT INTO telegram_phone_number (phone_number, entity_id)
|
||||
VALUES ($1, $2)
|
||||
ON CONFLICT (phone_number) DO UPDATE SET entity_id=excluded.entity_id
|
||||
`
|
||||
clearPhoneNumberQuery = "DELETE FROM telegram_phone_number WHERE entity_id=$1"
|
||||
)
|
||||
|
||||
var _ updates.StateStorage = (*ScopedStore)(nil)
|
||||
@@ -242,48 +220,6 @@ func (s *ScopedStore) DeleteAccessHashesForUser(ctx context.Context) (err error)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *ScopedStore) GetUsername(ctx context.Context, entityType ids.PeerType, userID int64) (username string, err error) {
|
||||
err = s.db.QueryRow(ctx, getUsernameQuery, entityType, userID).Scan(&username)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *ScopedStore) SetUsername(ctx context.Context, entityType ids.PeerType, entityID int64, username string) (err error) {
|
||||
if username == "" {
|
||||
_, err = s.db.Exec(ctx, clearUsernameQuery, entityType, entityID)
|
||||
} else {
|
||||
_, err = s.db.Exec(ctx, setUsernameQuery, username, entityType, entityID)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *ScopedStore) GetEntityIDByUsername(ctx context.Context, username string) (entityType ids.PeerType, entityID int64, err error) {
|
||||
err = s.db.QueryRow(ctx, getByUsernameQuery, strings.ToLower(username)).Scan(&entityType, &entityID)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *ScopedStore) GetUserIDByPhoneNumber(ctx context.Context, phoneNumber string) (userID int64, err error) {
|
||||
err = s.db.QueryRow(ctx, getEntityIDForPhoneNumber, phoneNumber).Scan(&userID)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *ScopedStore) SetPhoneNumber(ctx context.Context, userID int64, phoneNumber string) (err error) {
|
||||
if phoneNumber == "" {
|
||||
_, err = s.db.Exec(ctx, clearPhoneNumberQuery, userID)
|
||||
} else {
|
||||
_, err = s.db.Exec(ctx, setPhoneNumberQuery, phoneNumber, userID)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Helper Functions
|
||||
|
||||
func (s *ScopedStore) assertUserIDMatches(userID int64) {
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
// mautrix-telegram - A Matrix-Telegram puppeting bridge.
|
||||
// Copyright (C) 2025 Tulir Asokan
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"go.mau.fi/util/dbutil"
|
||||
"go.mau.fi/util/exsync"
|
||||
)
|
||||
|
||||
type topicKey struct {
|
||||
ChannelID int64
|
||||
TopicID int
|
||||
}
|
||||
|
||||
type TopicQuery struct {
|
||||
db *dbutil.Database
|
||||
existingTopics *exsync.Set[topicKey]
|
||||
}
|
||||
|
||||
const (
|
||||
getAllTopicsQuery = `SELECT topic_id FROM telegram_topic WHERE channel_id=$1`
|
||||
addTopicQuery = `INSERT INTO telegram_topic (channel_id, topic_id) VALUES ($1, $2) ON CONFLICT DO NOTHING`
|
||||
deleteTopicQuery = `DELETE FROM telegram_topic WHERE channel_id=$1 AND topic_id=$2`
|
||||
)
|
||||
|
||||
func (s *TopicQuery) Add(ctx context.Context, channelID int64, topicID int) (err error) {
|
||||
if channelID == 0 {
|
||||
return nil
|
||||
}
|
||||
if s.existingTopics.Add(topicKey{ChannelID: channelID, TopicID: topicID}) {
|
||||
_, err = s.db.Exec(ctx, addTopicQuery, channelID, topicID)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *TopicQuery) Delete(ctx context.Context, channelID int64, topicID int) (err error) {
|
||||
s.existingTopics.Remove(topicKey{ChannelID: channelID, TopicID: topicID})
|
||||
_, err = s.db.Exec(ctx, deleteTopicQuery, channelID, topicID)
|
||||
return
|
||||
}
|
||||
|
||||
var intScanner = dbutil.ConvertRowFn[int](dbutil.ScanSingleColumn[int])
|
||||
|
||||
func (s *TopicQuery) GetAll(ctx context.Context, channelID int64) (topics []int, err error) {
|
||||
return intScanner.NewRowIter(s.db.Query(ctx, getAllTopicsQuery, channelID)).AsList()
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
-- v0 -> v5 (compatible with v2+): Latest revision
|
||||
-- v0 -> v6 (compatible with v2+): Latest revision
|
||||
|
||||
CREATE TABLE telegram_user_state (
|
||||
user_id BIGINT NOT NULL PRIMARY KEY,
|
||||
@@ -51,3 +51,10 @@ CREATE TABLE telegram_file (
|
||||
mime_type TEXT,
|
||||
size BIGINT
|
||||
);
|
||||
|
||||
CREATE TABLE telegram_topic (
|
||||
channel_id BIGINT NOT NULL,
|
||||
topic_id BIGINT NOT NULL,
|
||||
|
||||
PRIMARY KEY (channel_id, topic_id)
|
||||
);
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
-- v6 (compatible with v2+): Add table for topics
|
||||
|
||||
CREATE TABLE telegram_topic (
|
||||
channel_id BIGINT NOT NULL,
|
||||
topic_id BIGINT NOT NULL,
|
||||
|
||||
PRIMARY KEY (channel_id, topic_id)
|
||||
);
|
||||
@@ -0,0 +1,70 @@
|
||||
// mautrix-telegram - A Matrix-Telegram puppeting bridge.
|
||||
// Copyright (C) 2025 Tulir Asokan
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"go.mau.fi/util/dbutil"
|
||||
|
||||
"go.mau.fi/mautrix-telegram/pkg/connector/ids"
|
||||
)
|
||||
|
||||
type UsernameQuery struct {
|
||||
db *dbutil.Database
|
||||
}
|
||||
|
||||
const (
|
||||
getUsernameQuery = "SELECT username FROM telegram_username WHERE entity_type=$1 AND entity_id=$2"
|
||||
setUsernameQuery = `
|
||||
INSERT INTO telegram_username (username, entity_type, entity_id)
|
||||
VALUES ($1, $2, $3)
|
||||
ON CONFLICT (username) DO UPDATE SET
|
||||
entity_type=excluded.entity_type,
|
||||
entity_id=excluded.entity_id
|
||||
`
|
||||
getByUsernameQuery = "SELECT entity_type, entity_id FROM telegram_username WHERE LOWER(username)=$1"
|
||||
clearUsernameQuery = `DELETE FROM telegram_username WHERE entity_type=$1 AND entity_id=$2`
|
||||
)
|
||||
|
||||
func (s *UsernameQuery) Get(ctx context.Context, entityType ids.PeerType, userID int64) (username string, err error) {
|
||||
err = s.db.QueryRow(ctx, getUsernameQuery, entityType, userID).Scan(&username)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *UsernameQuery) Set(ctx context.Context, entityType ids.PeerType, entityID int64, username string) (err error) {
|
||||
if username == "" {
|
||||
_, err = s.db.Exec(ctx, clearUsernameQuery, entityType, entityID)
|
||||
} else {
|
||||
_, err = s.db.Exec(ctx, setUsernameQuery, username, entityType, entityID)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *UsernameQuery) GetEntityID(ctx context.Context, username string) (entityType ids.PeerType, entityID int64, err error) {
|
||||
err = s.db.QueryRow(ctx, getByUsernameQuery, strings.ToLower(username)).Scan(&entityType, &entityID)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user