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
@@ -0,0 +1,167 @@
// Package participants contains channel participants iteration helper.
package participants
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 channel participants iterator element.
type Elem struct {
Participant tg.ChannelParticipantClass
Entities peer.Entities
}
// Iterator is a channel participants stream iterator.
type Iterator struct {
// Current state.
lastErr error
// Buffer state.
buf []Elem
bufCur int
// Request state.
limit int
lastBatch bool
// Offset parameters state.
offset int
// 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,
}
}
// Offset sets Offset request parameter.
func (m *Iterator) Offset(offset int) *Iterator {
m.offset = offset
return m
}
func (m *Iterator) apply(r tg.ChannelsChannelParticipantsClass) error {
if m.lastBatch {
return nil
}
var (
participants tg.ChannelParticipantClassArray
entities peer.Entities
)
switch prts := r.(type) {
case *tg.ChannelsChannelParticipants: // channels.channelParticipants#f56ee2a8
participants = prts.Participants
entities = peer.NewEntities(prts.MapUsers().UserToMap(), map[int64]*tg.Chat{}, map[int64]*tg.Channel{})
m.count = prts.Count
m.lastBatch = len(participants) < 1
default: // channels.channelParticipantsNotModified#f0173fe9
return errors.Errorf("unexpected type %T", r)
}
m.totalGot = true
m.offset += len(participants)
m.bufCur = -1
m.buf = m.buf[:0]
for i := range participants {
m.buf = append(m.buf, Elem{Participant: participants[i], Entities: entities})
}
return nil
}
func (m *Iterator) requestNext(ctx context.Context) error {
r, err := m.query.Query(ctx, Request{
Offset: m.offset,
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,
})
if err != nil {
return 0, errors.Wrap(err, "fetch total")
}
switch prts := r.(type) {
case *tg.ChannelsChannelParticipants: // channels.channelParticipants#f56ee2a8
m.count = prts.Count
default: // channels.channelParticipantsNotModified#f0173fe9
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.
//
// 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,92 @@
package participants
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 generateParticipants(count int) []tg.ChannelParticipantClass {
r := make([]tg.ChannelParticipantClass, 0, count)
for i := 0; i < count; i++ {
r = append(r, &tg.ChannelParticipant{
UserID: int64(i),
Date: i,
})
}
return r
}
func result(r []tg.ChannelParticipantClass, count int) tg.ChannelsChannelParticipantsClass {
return &tg.ChannelsChannelParticipants{
Participants: r,
Count: count,
}
}
func TestIterator(t *testing.T) {
ctx := context.Background()
mock := tgmock.NewRequire(t)
limit := 10
totalRecords := 3 * limit
expected := generateParticipants(totalRecords)
raw := tg.NewClient(mock)
ch := &tg.InputChannel{
ChannelID: 10,
AccessHash: 10,
}
mock.ExpectCall(&tg.ChannelsGetParticipantsRequest{
Channel: ch,
Filter: &tg.ChannelParticipantsRecent{},
Offset: 0,
Limit: limit,
}).ThenResult(result(expected[0:limit], totalRecords))
mock.ExpectCall(&tg.ChannelsGetParticipantsRequest{
Channel: ch,
Filter: &tg.ChannelParticipantsRecent{},
Offset: limit,
Limit: limit,
}).ThenResult(result(expected[limit:2*limit], totalRecords))
mock.ExpectCall(&tg.ChannelsGetParticipantsRequest{
Channel: ch,
Filter: &tg.ChannelParticipantsRecent{},
Offset: 2 * limit,
Limit: limit,
}).ThenResult(result(expected[2*limit:3*limit], totalRecords))
mock.ExpectCall(&tg.ChannelsGetParticipantsRequest{
Channel: ch,
Filter: &tg.ChannelParticipantsRecent{},
Offset: 3 * limit,
Limit: limit,
}).ThenResult(result(expected[3*limit:], totalRecords))
iter := NewQueryBuilder(raw).GetParticipants(ch).BatchSize(10).Iter()
i := 0
for iter.Next(ctx) {
require.Equal(t, expected[i], iter.Value().Participant)
i++
}
require.NoError(t, iter.Err())
require.Equal(t, totalRecords, i)
total, err := iter.Total(ctx)
require.NoError(t, err)
require.Equal(t, totalRecords, total)
mock.ExpectCall(&tg.ChannelsGetParticipantsRequest{
Channel: ch,
Filter: &tg.ChannelParticipantsRecent{},
Offset: 0,
Limit: 1,
}).ThenResult(result(expected[:0], totalRecords))
total, err = iter.FetchTotal(ctx)
require.NoError(t, err)
require.Equal(t, totalRecords, total)
}
@@ -0,0 +1,201 @@
// Code generated by itergen, DO NOT EDIT.
package participants
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 {
Offset int
Limit int
}
// Query is an abstraction for participants request.
// NB: iterator mutates returned data (sorts, at least).
type Query interface {
Query(ctx context.Context, req Request) (tg.ChannelsChannelParticipantsClass, error)
}
// QueryFunc is a function adapter for Query.
type QueryFunc func(ctx context.Context, req Request) (tg.ChannelsChannelParticipantsClass, error)
// Query implements Query interface.
func (q QueryFunc) Query(ctx context.Context, req Request) (tg.ChannelsChannelParticipantsClass, 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}
}
// GetParticipantsQueryBuilder is query builder of ChannelsGetParticipants.
type GetParticipantsQueryBuilder struct {
raw *tg.Client
req tg.ChannelsGetParticipantsRequest
batchSize int
offset int
}
// GetParticipants creates query builder of ChannelsGetParticipants.
func (q *QueryBuilder) GetParticipants(paramChannel tg.InputChannelClass) *GetParticipantsQueryBuilder {
b := &GetParticipantsQueryBuilder{
raw: q.raw,
batchSize: 1,
req: tg.ChannelsGetParticipantsRequest{
Filter: &tg.ChannelParticipantsRecent{},
},
}
b.req.Channel = paramChannel
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 *GetParticipantsQueryBuilder) BatchSize(batchSize int) *GetParticipantsQueryBuilder {
b.batchSize = batchSize
return b
}
// Channel sets Channel field of GetParticipants query.
func (b *GetParticipantsQueryBuilder) Channel(paramChannel tg.InputChannelClass) *GetParticipantsQueryBuilder {
b.req.Channel = paramChannel
return b
}
// Filter sets Filter field of GetParticipants query.
func (b *GetParticipantsQueryBuilder) Filter(paramFilter tg.ChannelParticipantsFilterClass) *GetParticipantsQueryBuilder {
b.req.Filter = paramFilter
return b
}
// Admins sets Filter field of GetParticipants query.
func (b *GetParticipantsQueryBuilder) Admins() *GetParticipantsQueryBuilder {
b.req.Filter = &tg.ChannelParticipantsAdmins{}
return b
}
// Banned sets Filter field of GetParticipants query.
func (b *GetParticipantsQueryBuilder) Banned(paramQ string) *GetParticipantsQueryBuilder {
b.req.Filter = &tg.ChannelParticipantsBanned{
Q: paramQ,
}
return b
}
// Bots sets Filter field of GetParticipants query.
func (b *GetParticipantsQueryBuilder) Bots() *GetParticipantsQueryBuilder {
b.req.Filter = &tg.ChannelParticipantsBots{}
return b
}
// Contacts sets Filter field of GetParticipants query.
func (b *GetParticipantsQueryBuilder) Contacts(paramQ string) *GetParticipantsQueryBuilder {
b.req.Filter = &tg.ChannelParticipantsContacts{
Q: paramQ,
}
return b
}
// Kicked sets Filter field of GetParticipants query.
func (b *GetParticipantsQueryBuilder) Kicked(paramQ string) *GetParticipantsQueryBuilder {
b.req.Filter = &tg.ChannelParticipantsKicked{
Q: paramQ,
}
return b
}
// Mentions sets Filter field of GetParticipants query.
func (b *GetParticipantsQueryBuilder) Mentions(paramQ string, paramTopMsgID int) *GetParticipantsQueryBuilder {
b.req.Filter = &tg.ChannelParticipantsMentions{
Q: paramQ,
TopMsgID: paramTopMsgID,
}
return b
}
// Recent sets Filter field of GetParticipants query.
func (b *GetParticipantsQueryBuilder) Recent() *GetParticipantsQueryBuilder {
b.req.Filter = &tg.ChannelParticipantsRecent{}
return b
}
// Search sets Filter field of GetParticipants query.
func (b *GetParticipantsQueryBuilder) Search(paramQ string) *GetParticipantsQueryBuilder {
b.req.Filter = &tg.ChannelParticipantsSearch{
Q: paramQ,
}
return b
}
// Query implements Query interface.
func (b *GetParticipantsQueryBuilder) Query(ctx context.Context, req Request) (tg.ChannelsChannelParticipantsClass, error) {
r := &tg.ChannelsGetParticipantsRequest{
Limit: req.Limit,
}
r.Channel = b.req.Channel
r.Filter = b.req.Filter
r.Offset = req.Offset
return b.raw.ChannelsGetParticipants(ctx, r)
}
// Iter returns iterator using built query.
func (b *GetParticipantsQueryBuilder) Iter() *Iterator {
iter := NewIterator(b, b.batchSize)
iter = iter.Offset(b.offset)
return iter
}
// ForEach calls given callback on each iterator element.
func (b *GetParticipantsQueryBuilder) 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 *GetParticipantsQueryBuilder) 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 *GetParticipantsQueryBuilder) 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()
}
@@ -0,0 +1,3 @@
package participants
//go:generate go run go.mau.fi/mautrix-telegram/pkg/gotd/telegram/query/internal/itergen -result=ChannelsChannelParticipantsClass -package=participants -prefix=Channels -out=queries.gen.go
@@ -0,0 +1,62 @@
package participants
import (
"go.mau.fi/mautrix-telegram/pkg/gotd/telegram/query/photos"
"go.mau.fi/mautrix-telegram/pkg/gotd/tg"
)
// UserPhotos returns new user photo query builder for participant.
func (e Elem) UserPhotos(raw *tg.Client) (*photos.GetUserPhotosQueryBuilder, bool) {
user, ok := e.User()
if !ok {
return nil, false
}
return photos.NewQueryBuilder(raw).GetUserPhotos(user.AsInput()), true
}
// User tries to get participant user object.
func (e Elem) User() (*tg.User, bool) {
switch part := e.Participant.(type) {
case interface{ GetUserID() int64 }:
return e.Entities.User(part.GetUserID())
case interface{ GetPeer() tg.PeerClass }:
user, ok := part.GetPeer().(*tg.PeerUser)
if !ok {
return nil, false
}
return e.Entities.User(user.GetUserID())
default:
return nil, false
}
}
// Creator returns participant user object and meta info if participant is a creator of channel.
func (e Elem) Creator() (*tg.User, *tg.ChannelParticipantCreator, bool) {
part, ok := e.Participant.(*tg.ChannelParticipantCreator)
if !ok {
return nil, nil, false
}
user, ok := e.User()
if !ok {
return nil, nil, false
}
return user, part, true
}
// Admin returns participant user object and meta info if participant is admin of channel.
func (e Elem) Admin() (*tg.User, *tg.ChannelParticipantAdmin, bool) {
part, ok := e.Participant.(*tg.ChannelParticipantAdmin)
if !ok {
return nil, nil, false
}
user, ok := e.User()
if !ok {
return nil, nil, false
}
return user, part, true
}
@@ -0,0 +1,58 @@
package participants
import (
"testing"
"github.com/stretchr/testify/require"
"go.mau.fi/mautrix-telegram/pkg/gotd/telegram/message/peer"
"go.mau.fi/mautrix-telegram/pkg/gotd/tg"
)
func TestElem(t *testing.T) {
entities := peer.NewEntities(
map[int64]*tg.User{10: {}},
map[int64]*tg.Chat{},
map[int64]*tg.Channel{},
)
type results struct {
admin, creator, photos bool
}
tests := []struct {
Name string
Part tg.ChannelParticipantClass
results
}{
{"UnknownPlain", &tg.ChannelParticipant{UserID: 45}, results{}},
{"UnknownBanned", &tg.ChannelParticipantBanned{Peer: &tg.PeerUser{UserID: 45}},
results{}},
{"UnknownAdmin", &tg.ChannelParticipantAdmin{UserID: 45}, results{}},
{"UnknownCreator", &tg.ChannelParticipantCreator{UserID: 45}, results{}},
{"Plain", &tg.ChannelParticipant{UserID: 10}, results{photos: true}},
{"Banned", &tg.ChannelParticipantBanned{Peer: &tg.PeerUser{UserID: 10}},
results{photos: true}},
{"Admin", &tg.ChannelParticipantAdmin{UserID: 10}, results{
admin: true,
photos: true,
}},
{"Creator", &tg.ChannelParticipantCreator{UserID: 10}, results{
creator: true,
photos: true,
}},
}
for _, test := range tests {
t.Run(test.Name, func(t *testing.T) {
a := require.New(t)
var ok bool
elem := Elem{Participant: test.Part, Entities: entities}
_, ok = elem.UserPhotos(nil)
a.Equal(test.photos, ok)
_, _, ok = elem.Admin()
a.Equal(test.admin, ok)
_, _, ok = elem.Creator()
a.Equal(test.creator, ok)
})
}
}