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:
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user