move gotd fork into repo. (#111)

- update to latest telegram layer
- remove some references to fields in tg.Entities that don't exist in
the schema
- originally added here:
https://github.com/beeper/td/commit/820929062a2ba0104397bc01235ab58a9cff780e
  - referenced here
-
https://github.com/mautrix/telegramgo/commit/124f0967ed195b5a380c9bd02e170ada9710dde3
-
https://github.com/mautrix/telegramgo/commit/4205047aab2e0639217148b5d125bfaab668bd8e
This commit is contained in:
Adam Van Ymeren
2025-06-27 20:03:37 -07:00
committed by GitHub
parent 0952df0244
commit 7a04f298d2
19264 changed files with 1539697 additions and 84 deletions
+254
View File
@@ -0,0 +1,254 @@
// Package dialogs contains dialog iteration helper.
package dialogs
import (
"context"
"github.com/go-faster/errors"
"go.mau.fi/mautrix-telegram/pkg/gotd/telegram/message/peer"
"go.mau.fi/mautrix-telegram/pkg/gotd/tg"
)
// Elem is a dialog iterator element.
type Elem struct {
Dialog tg.DialogClass
Peer tg.InputPeerClass
Last tg.NotEmptyMessage
Entities peer.Entities
}
// Iterator is a dialog stream iterator.
type Iterator struct {
// Current state.
lastErr error
// Buffer state.
buf []Elem
bufCur int
// Request state.
limit int
lastBatch bool
// Offset parameters state.
offsetID int
offsetDate int
offsetPeer tg.InputPeerClass
// Remote state.
count int
totalGot bool
// Query builder.
query Query
}
// NewIterator creates new iterator.
func NewIterator(query Query, limit int) *Iterator {
return &Iterator{
buf: make([]Elem, 0, limit),
bufCur: -1,
limit: limit,
query: query,
offsetPeer: &tg.InputPeerEmpty{},
}
}
// OffsetID sets OffsetID request parameter.
func (m *Iterator) OffsetID(offsetID int) *Iterator {
m.offsetID = offsetID
return m
}
// OffsetDate sets OffsetDate request parameter.
func (m *Iterator) OffsetDate(offsetDate int) *Iterator {
m.offsetDate = offsetDate
return m
}
// OffsetPeer sets OffsetPeer request parameter.
func (m *Iterator) OffsetPeer(offsetPeer tg.InputPeerClass) *Iterator {
m.offsetPeer = offsetPeer
return m
}
// messageMap is a helper to store messages for multiple peers.
type messageMap map[DialogKey]tg.NotEmptyMessage
func (m messageMap) collect(messages tg.MessageClassArray) error {
for _, msg := range messages {
nonEmpty, ok := msg.AsNotEmpty()
if !ok {
// TODO(tdakkota): Maybe should I return error here?
continue
}
var key DialogKey
if err := key.FromPeer(nonEmpty.GetPeerID()); err != nil {
return err
}
m[key] = nonEmpty
}
return nil
}
func (m *Iterator) apply(r tg.MessagesDialogsClass) error {
if m.lastBatch {
return nil
}
var (
messages tg.MessageClassArray
dialogs tg.DialogClassArray
entities peer.Entities
)
switch dlgs := r.(type) {
case *tg.MessagesDialogs: // messages.dialogs#15ba6c40
dialogs = dlgs.Dialogs
messages = dlgs.Messages
entities = peer.EntitiesFromResult(dlgs)
m.count = len(messages)
m.lastBatch = true
case *tg.MessagesDialogsSlice: // messages.dialogsSlice#71e094f3
dialogs = dlgs.Dialogs
messages = dlgs.Messages
entities = peer.EntitiesFromResult(dlgs)
m.count = dlgs.Count
m.lastBatch = len(dlgs.Dialogs) == 0
default: // messages.dialogsNotModified#f0e3e596
return errors.Errorf("unexpected type %T", r)
}
m.totalGot = true
msgMap := make(messageMap, len(messages))
if err := msgMap.collect(messages); err != nil {
return errors.Wrap(err, "collect last messages")
}
m.bufCur = -1
m.buf = m.buf[:0]
var last tg.NotEmptyMessage
for _, dlg := range dialogs {
var key DialogKey
if err := key.FromPeer(dlg.GetPeer()); err == nil {
last = msgMap[key]
}
p, err := entities.ExtractPeer(dlg.GetPeer())
if err != nil {
p = &tg.InputPeerEmpty{}
}
m.buf = append(m.buf, Elem{
Dialog: dlg,
Peer: p,
Last: last,
Entities: entities,
})
}
if !m.lastBatch && len(m.buf) > 0 {
if last != nil {
m.offsetID = last.GetID()
m.offsetDate = last.GetDate()
}
p, err := entities.ExtractPeer(dialogs[len(m.buf)-1].GetPeer())
if err != nil {
return errors.Wrap(err, "get offset peer")
}
m.offsetPeer = p
}
return nil
}
func (m *Iterator) requestNext(ctx context.Context) error {
r, err := m.query.Query(ctx, Request{
OffsetID: m.offsetID,
OffsetDate: m.offsetDate,
OffsetPeer: m.offsetPeer,
Limit: m.limit,
})
if err != nil {
return err
}
return m.apply(r)
}
func (m *Iterator) bufNext() bool {
if len(m.buf)-1 <= m.bufCur {
return false
}
m.bufCur++
return true
}
// Total returns last fetched count of elements.
// If count was not fetched before, it requests server using FetchTotal.
func (m *Iterator) Total(ctx context.Context) (int, error) {
if m.totalGot {
return m.count, nil
}
return m.FetchTotal(ctx)
}
// FetchTotal fetches and returns count of elements.
func (m *Iterator) FetchTotal(ctx context.Context) (int, error) {
r, err := m.query.Query(ctx, Request{
Limit: 1,
OffsetPeer: &tg.InputPeerEmpty{},
})
if err != nil {
return 0, errors.Wrap(err, "fetch total")
}
switch dlgs := r.(type) {
case *tg.MessagesDialogs: // messages.dialogs#15ba6c40
m.count = len(dlgs.Dialogs)
case *tg.MessagesDialogsSlice: // messages.dialogsSlice#71e094f3
m.count = dlgs.Count
default: // messages.dialogsNotModified#f0e3e596
return 0, errors.Errorf("unexpected type %T", r)
}
m.totalGot = true
return m.count, nil
}
// Next prepares the next message for reading with the Value method.
// It returns true on success, or false if there is no next message or an error happened while preparing it.
// Err should be consulted to distinguish between the two cases.
func (m *Iterator) Next(ctx context.Context) bool {
if m.lastErr != nil {
return false
}
if !m.bufNext() {
// If buffer is empty, we should fetch next batch.
if err := m.requestNext(ctx); err != nil {
m.lastErr = err
return false
}
// Try again with new buffer.
return m.bufNext()
}
return true
}
// Value returns current message.
func (m *Iterator) Value() Elem {
return m.buf[m.bufCur]
}
// Err returns the error, if any, that was encountered during iteration.
func (m *Iterator) Err() error {
return m.lastErr
}
@@ -0,0 +1,87 @@
package dialogs
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"go.mau.fi/mautrix-telegram/pkg/gotd/tg"
"go.mau.fi/mautrix-telegram/pkg/gotd/tgmock"
)
func generateDialogs(count int) []tg.DialogClass {
r := make([]tg.DialogClass, 0, count)
for i := 0; i < count; i++ {
r = append(r, &tg.Dialog{
Peer: &tg.PeerChannel{ChannelID: int64(i)},
})
}
return r
}
func result(r []tg.DialogClass, count int) tg.MessagesDialogsClass {
msgs := make([]tg.MessageClass, 0, len(r))
for i, dlg := range r {
msgs = append(msgs, &tg.Message{
ID: i,
PeerID: dlg.GetPeer(),
})
}
chats := make([]tg.ChatClass, 0, len(r))
for i, dlg := range r {
id := dlg.GetPeer().(*tg.PeerChannel).ChannelID
chats = append(chats, &tg.Channel{
ID: id,
AccessHash: 10,
Photo: &tg.ChatPhoto{
PhotoID: int64(i),
},
})
}
return &tg.MessagesDialogsSlice{
Dialogs: r,
Messages: msgs,
Chats: chats,
Count: count,
}
}
func TestIterator(t *testing.T) {
ctx := context.Background()
mock := tgmock.NewRequire(t)
limit := 10
totalRows := 3 * limit
expected := generateDialogs(totalRows)
raw := tg.NewClient(mock)
mock.Expect().ThenResult(result(expected[0:limit], totalRows))
mock.Expect().ThenResult(result(expected[limit:2*limit], totalRows))
mock.Expect().ThenResult(result(expected[2*limit:3*limit], totalRows))
mock.Expect().ThenResult(result(expected[3*limit:], totalRows))
iter := NewQueryBuilder(raw).GetDialogs().BatchSize(10).Iter()
i := 0
for iter.Next(ctx) {
require.Equal(t, expected[i].GetPeer(), iter.Value().Dialog.GetPeer())
i++
}
require.NoError(t, iter.Err())
require.Equal(t, totalRows, i)
total, err := iter.Total(ctx)
require.NoError(t, err)
require.Equal(t, totalRows, total)
mock.ExpectCall(&tg.MessagesGetDialogsRequest{
OffsetPeer: &tg.InputPeerEmpty{},
Limit: 1,
}).ThenResult(result(expected[:0], totalRows))
total, err = iter.FetchTotal(ctx)
require.NoError(t, err)
require.Equal(t, totalRows, total)
}
+66
View File
@@ -0,0 +1,66 @@
package dialogs
import (
"github.com/go-faster/errors"
"go.mau.fi/mautrix-telegram/pkg/gotd/tg"
)
// PeerKind represents peer kind.
type PeerKind int
const (
// User is a private chat with user.
User PeerKind = iota
// Chat is a legacy chat.
Chat
// Channel is a supergroup/channel.
Channel
)
// DialogKey is a generic peer key.
type DialogKey struct {
Kind PeerKind
ID int64
AccessHash int64
}
// FromInputPeer fills key using given peer.
func (d *DialogKey) FromInputPeer(peer tg.InputPeerClass) error {
switch v := peer.(type) {
case *tg.InputPeerUser:
d.Kind = User
d.ID = v.UserID
d.AccessHash = v.AccessHash
case *tg.InputPeerChat:
d.Kind = Chat
d.ID = v.ChatID
case *tg.InputPeerChannel:
d.Kind = Channel
d.ID = v.ChannelID
d.AccessHash = v.AccessHash
default:
return errors.Errorf("unexpected type %T", peer)
}
return nil
}
// FromPeer fills key using given peer.
func (d *DialogKey) FromPeer(peer tg.PeerClass) error {
switch v := peer.(type) {
case *tg.PeerUser:
d.Kind = User
d.ID = v.UserID
case *tg.PeerChat:
d.Kind = Chat
d.ID = v.ChatID
case *tg.PeerChannel:
d.Kind = Channel
d.ID = v.ChannelID
default:
return errors.Errorf("unexpected type %T", peer)
}
return nil
}
@@ -0,0 +1,151 @@
// Code generated by itergen, DO NOT EDIT.
package dialogs
import (
"context"
"github.com/go-faster/errors"
"go.mau.fi/mautrix-telegram/pkg/gotd/tg"
)
// No-op definition for keeping imports.
var _ = context.Background()
// Request is a parameter for Query.
type Request struct {
OffsetDate int
OffsetID int
OffsetPeer tg.InputPeerClass
Limit int
}
// Query is an abstraction for dialogs request.
// NB: iterator mutates returned data (sorts, at least).
type Query interface {
Query(ctx context.Context, req Request) (tg.MessagesDialogsClass, error)
}
// QueryFunc is a function adapter for Query.
type QueryFunc func(ctx context.Context, req Request) (tg.MessagesDialogsClass, error)
// Query implements Query interface.
func (q QueryFunc) Query(ctx context.Context, req Request) (tg.MessagesDialogsClass, error) {
return q(ctx, req)
}
// QueryBuilder is a helper to create message queries.
type QueryBuilder struct {
raw *tg.Client
}
// NewQueryBuilder creates new QueryBuilder.
func NewQueryBuilder(raw *tg.Client) *QueryBuilder {
return &QueryBuilder{raw: raw}
}
// GetDialogsQueryBuilder is query builder of MessagesGetDialogs.
type GetDialogsQueryBuilder struct {
raw *tg.Client
req tg.MessagesGetDialogsRequest
batchSize int
offsetDate int
offsetID int
offsetPeer tg.InputPeerClass
}
// GetDialogs creates query builder of MessagesGetDialogs.
func (q *QueryBuilder) GetDialogs() *GetDialogsQueryBuilder {
b := &GetDialogsQueryBuilder{
raw: q.raw,
batchSize: 1,
req: tg.MessagesGetDialogsRequest{},
}
return b
}
// BatchSize sets buffer of message loaded from one request.
// Be carefully, when set this limit, because Telegram does not return error if limit is too big,
// so results can be incorrect.
func (b *GetDialogsQueryBuilder) BatchSize(batchSize int) *GetDialogsQueryBuilder {
b.batchSize = batchSize
return b
}
// OffsetDate sets offsetDate from which iterate start.
func (b *GetDialogsQueryBuilder) OffsetDate(offsetDate int) *GetDialogsQueryBuilder {
b.offsetDate = offsetDate
return b
}
// OffsetID sets offsetID from which iterate start.
func (b *GetDialogsQueryBuilder) OffsetID(offsetID int) *GetDialogsQueryBuilder {
b.offsetID = offsetID
return b
}
// FolderID sets FolderID field of GetDialogs query.
func (b *GetDialogsQueryBuilder) FolderID(paramFolderID int) *GetDialogsQueryBuilder {
b.req.FolderID = paramFolderID
return b
}
// Query implements Query interface.
func (b *GetDialogsQueryBuilder) Query(ctx context.Context, req Request) (tg.MessagesDialogsClass, error) {
r := &tg.MessagesGetDialogsRequest{
Limit: req.Limit,
}
r.FolderID = b.req.FolderID
r.OffsetDate = req.OffsetDate
r.OffsetID = req.OffsetID
r.OffsetPeer = req.OffsetPeer
return b.raw.MessagesGetDialogs(ctx, r)
}
// Iter returns iterator using built query.
func (b *GetDialogsQueryBuilder) Iter() *Iterator {
iter := NewIterator(b, b.batchSize)
iter = iter.OffsetDate(b.offsetDate)
iter = iter.OffsetID(b.offsetID)
return iter
}
// ForEach calls given callback on each iterator element.
func (b *GetDialogsQueryBuilder) ForEach(ctx context.Context, cb func(context.Context, Elem) error) error {
iter := b.Iter()
for iter.Next(ctx) {
if err := cb(ctx, iter.Value()); err != nil {
return err
}
}
return iter.Err()
}
// Count fetches remote state to get number of elements.
func (b *GetDialogsQueryBuilder) Count(ctx context.Context) (int, error) {
iter := b.Iter()
c, err := iter.Total(ctx)
if err != nil {
return 0, errors.Wrap(err, "get total")
}
return c, nil
}
// Collect creates iterator and collects all elements to slice.
func (b *GetDialogsQueryBuilder) Collect(ctx context.Context) ([]Elem, error) {
iter := b.Iter()
c, err := iter.Total(ctx)
if err != nil {
return nil, errors.Wrap(err, "get total")
}
r := make([]Elem, 0, c)
for iter.Next(ctx) {
r = append(r, iter.Value())
}
return r, iter.Err()
}
+3
View File
@@ -0,0 +1,3 @@
package dialogs
//go:generate go run go.mau.fi/mautrix-telegram/pkg/gotd/telegram/query/internal/itergen -result=MessagesDialogsClass -package=dialogs -out=queries.gen.go
@@ -0,0 +1,60 @@
package dialogs
import (
"go.mau.fi/mautrix-telegram/pkg/gotd/telegram/message/peer"
"go.mau.fi/mautrix-telegram/pkg/gotd/telegram/query/channels/participants"
"go.mau.fi/mautrix-telegram/pkg/gotd/telegram/query/messages"
"go.mau.fi/mautrix-telegram/pkg/gotd/telegram/query/photos"
"go.mau.fi/mautrix-telegram/pkg/gotd/tg"
)
// Messages returns new messages history query builder for current dialog.
func (e Elem) Messages(raw *tg.Client) *messages.GetHistoryQueryBuilder {
return messages.NewQueryBuilder(raw).GetHistory(e.Peer)
}
// Search returns new search query builder for current dialog.
func (e Elem) Search(raw *tg.Client) *messages.SearchQueryBuilder {
return messages.NewQueryBuilder(raw).Search(e.Peer)
}
// Replies returns new replies query builder for current dialog.
func (e Elem) Replies(raw *tg.Client) *messages.GetRepliesQueryBuilder {
return messages.NewQueryBuilder(raw).GetReplies(e.Peer)
}
// UnreadMentions returns new unread mentions query builder for current dialog.
func (e Elem) UnreadMentions(raw *tg.Client) *messages.GetUnreadMentionsQueryBuilder {
return messages.NewQueryBuilder(raw).GetUnreadMentions(e.Peer)
}
// RecentLocations returns new live location history query builder for current dialog.
func (e Elem) RecentLocations(raw *tg.Client) *messages.GetRecentLocationsQueryBuilder {
return messages.NewQueryBuilder(raw).GetRecentLocations(e.Peer)
}
// UserPhotos returns new user photo query builder for current dialog.
// If peer is not user, returns false.
func (e Elem) UserPhotos(raw *tg.Client) (*photos.GetUserPhotosQueryBuilder, bool) {
user, ok := peer.ToInputUser(e.Peer)
if !ok {
return nil, false
}
return photos.NewQueryBuilder(raw).GetUserPhotos(user), true
}
// Participants returns new channel participants query builder for current dialog.
// If peer is not channel, returns false.
func (e Elem) Participants(raw *tg.Client) (*participants.GetParticipantsQueryBuilder, bool) {
channel, ok := peer.ToInputChannel(e.Peer)
if !ok {
return nil, false
}
return participants.NewQueryBuilder(raw).GetParticipants(channel), true
}
// Deleted denotes that dialog is deleted.
func (e Elem) Deleted() bool {
_, ok := e.Peer.(*tg.InputPeerEmpty)
return ok
}
@@ -0,0 +1,54 @@
package dialogs
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"go.mau.fi/mautrix-telegram/pkg/gotd/tg"
"go.mau.fi/mautrix-telegram/pkg/gotd/tgerr"
"go.mau.fi/mautrix-telegram/pkg/gotd/tgmock"
)
func TestElem(t *testing.T) {
ctx := context.Background()
mock := tgmock.NewRequire(t)
raw := tg.NewClient(mock)
ch := Elem{
Peer: &tg.InputPeerChannel{},
}
testErr := tgerr.New(1337, "TEST_ERROR")
var err error
mock.Expect().ThenRPCErr(testErr)
_, err = ch.Messages(raw).Count(ctx)
require.Error(t, err)
mock.Expect().ThenRPCErr(testErr)
_, err = ch.Search(raw).Count(ctx)
require.Error(t, err)
mock.Expect().ThenRPCErr(testErr)
_, err = ch.Replies(raw).Count(ctx)
require.Error(t, err)
mock.Expect().ThenRPCErr(testErr)
_, err = ch.UnreadMentions(raw).Count(ctx)
require.Error(t, err)
mock.Expect().ThenRPCErr(testErr)
_, err = ch.RecentLocations(raw).Count(ctx)
require.Error(t, err)
_, ok := ch.Participants(raw)
require.True(t, ok)
_, ok = ch.UserPhotos(raw)
require.False(t, ok)
ch = Elem{
Peer: &tg.InputPeerUser{},
}
_, ok = ch.Participants(raw)
require.False(t, ok)
_, ok = ch.UserPhotos(raw)
require.True(t, ok)
}