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
+269
View File
@@ -0,0 +1,269 @@
// Package messages contains message iteration helper.
package messages
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 message iterator element.
type Elem struct {
Msg tg.NotEmptyMessage
Peer tg.InputPeerClass
Entities peer.Entities
}
// Iterator is a message stream iterator.
type Iterator struct {
// Current state.
lastErr error
// Buffer state.
buf []Elem
bufCur int
// Request state.
addOffset int
limit int
lastBatch bool
// Offset parameters state.
offsetID int
offsetDate int
offsetPeer tg.InputPeerClass
offsetRate 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,
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
}
// OffsetRate sets OffsetRate request parameter.
func (m *Iterator) OffsetRate(offsetRate int) *Iterator {
m.offsetRate = offsetRate
return m
}
// OffsetPeer sets OffsetPeer request parameter.
func (m *Iterator) OffsetPeer(offsetPeer tg.InputPeerClass) *Iterator {
m.offsetPeer = offsetPeer
return m
}
func (m *Iterator) apply(r tg.MessagesMessagesClass) error {
if m.lastBatch {
return nil
}
var (
messages tg.MessageClassArray
entities peer.Entities
)
switch msgs := r.(type) {
case *tg.MessagesMessages: // messages.messages#8c718e87
messages = msgs.Messages
entities = peer.EntitiesFromResult(msgs)
m.count = len(messages)
m.lastBatch = true
case *tg.MessagesMessagesSlice: // messages.messagesSlice#3a54685e
messages = msgs.Messages
entities = peer.EntitiesFromResult(msgs)
m.offsetRate = msgs.NextRate
m.count = msgs.Count
m.lastBatch = len(msgs.Messages) < m.limit
case *tg.MessagesChannelMessages: // messages.channelMessages#64479808
messages = msgs.Messages
entities = peer.EntitiesFromResult(msgs)
m.count = msgs.Count
m.lastBatch = len(msgs.Messages) < m.limit
default: // messages.messagesNotModified#74535f21
return errors.Errorf("unexpected type %T", r)
}
m.totalGot = true
// Sort messages to guarantee order and find the last message.
messages = messages.SortStable(func(a, b tg.MessageClass) bool {
return a.GetID() > b.GetID()
})
// Get the last message (with smallest ID).
msg, ok := messages.Last()
if !ok {
// If Last() returned false, result is empty, so we this is a last batch.
m.lastBatch = true
return nil
}
// Update offsetID and offsetDate, if can to prevent duplication in case
// when there a lot new messages in a chat/channel between previous and current request.
//
// Illustration of problem:
//
// Remote state:
// [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
// ^ offset = 0
//
// First request(offset = 0, limit = 5):
// [10, 9, 8, 7, 6]
// offset = 5
//
// Remote state:
// [15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
// ^ offset = 5
//
// Second request(offset = 5, limit = 5):
// [10, 9, 8, 7, 6]
// offset = 10
//
m.offsetID = msg.GetID()
if nonEmpty, ok := msg.AsNotEmpty(); ok {
m.offsetDate = nonEmpty.GetDate()
p, err := entities.ExtractPeer(nonEmpty.GetPeerID())
if err == nil {
m.offsetPeer = p
}
}
m.bufCur = -1
m.buf = m.buf[:0]
for _, msg := range messages {
nonEmpty, ok := msg.AsNotEmpty()
if !ok {
continue
}
msgPeer, err := entities.ExtractPeer(nonEmpty.GetPeerID())
if err != nil {
msgPeer = &tg.InputPeerEmpty{}
}
m.buf = append(m.buf, Elem{
Msg: nonEmpty,
Peer: msgPeer,
Entities: entities,
})
}
return nil
}
func (m *Iterator) requestNext(ctx context.Context) error {
r, err := m.query.Query(ctx, Request{
OffsetID: m.offsetID,
AddOffset: m.addOffset,
OffsetDate: m.offsetDate,
OffsetRate: m.offsetRate,
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 msgs := r.(type) {
case *tg.MessagesMessages: // messages.messages#8c718e87
m.count = len(msgs.Messages)
case *tg.MessagesMessagesSlice: // messages.messagesSlice#3a54685e
m.count = msgs.Count
case *tg.MessagesChannelMessages: // messages.channelMessages#64479808
m.count = msgs.Count
default: // messages.messagesNotModified#74535f21
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,107 @@
package messages
import (
"context"
"strconv"
"testing"
"github.com/stretchr/testify/require"
"go.mau.fi/mautrix-telegram/pkg/gotd/tg"
"go.mau.fi/mautrix-telegram/pkg/gotd/tgmock"
)
func generateMessages(count int) []tg.MessageClass {
r := make([]tg.MessageClass, 0, count)
for i := 0; i < count; i++ {
r = append(r, &tg.Message{
ID: i,
PeerID: &tg.PeerUser{UserID: 10},
Message: strconv.Itoa(i),
})
}
return r
}
func messagesClass(r []tg.MessageClass, count int) tg.MessagesMessagesClass {
return &tg.MessagesChannelMessages{
Messages: r,
Count: count,
}
}
func TestIterator(t *testing.T) {
ctx := context.Background()
mock := tgmock.NewRequire(t)
limit := 10
totalMessages := 3 * limit
expected := generateMessages(totalMessages)
raw := tg.NewClient(mock)
mock.ExpectCall(&tg.MessagesSearchRequest{
Q: "query",
Peer: &tg.InputPeerSelf{},
OffsetID: 0,
FromID: &tg.InputPeerEmpty{},
Filter: &tg.InputMessagesFilterEmpty{},
SavedPeerID: &tg.InputPeerEmpty{},
Limit: limit,
}).ThenResult(messagesClass(expected[2*limit:3*limit], totalMessages))
mock.ExpectCall(&tg.MessagesSearchRequest{
Q: "query",
Peer: &tg.InputPeerSelf{},
OffsetID: 20,
FromID: &tg.InputPeerEmpty{},
Filter: &tg.InputMessagesFilterEmpty{},
SavedPeerID: &tg.InputPeerEmpty{},
Limit: limit,
}).ThenResult(messagesClass(expected[limit:2*limit], totalMessages))
mock.ExpectCall(&tg.MessagesSearchRequest{
Q: "query",
Peer: &tg.InputPeerSelf{},
OffsetID: 10,
FromID: &tg.InputPeerEmpty{},
Filter: &tg.InputMessagesFilterEmpty{},
SavedPeerID: &tg.InputPeerEmpty{},
Limit: limit,
}).ThenResult(messagesClass(expected[:limit], totalMessages))
mock.ExpectCall(&tg.MessagesSearchRequest{
Q: "query",
Peer: &tg.InputPeerSelf{},
OffsetID: 0,
FromID: &tg.InputPeerEmpty{},
Filter: &tg.InputMessagesFilterEmpty{},
SavedPeerID: &tg.InputPeerEmpty{},
Limit: limit,
}).ThenResult(messagesClass(expected[:0], totalMessages))
iter := NewQueryBuilder(raw).Search(&tg.InputPeerSelf{}).
Filter(&tg.InputMessagesFilterEmpty{}).
Q("query").BatchSize(10).Iter()
i := 0
for iter.Next(ctx) {
require.Equal(t, expected[len(expected)-i-1], iter.Value().Msg)
i++
}
require.NoError(t, iter.Err())
require.Equal(t, totalMessages, i)
total, err := iter.Total(ctx)
require.NoError(t, err)
require.Equal(t, totalMessages, total)
mock.ExpectCall(&tg.MessagesSearchRequest{
Q: "query",
Peer: &tg.InputPeerSelf{},
OffsetID: 0,
FromID: &tg.InputPeerEmpty{},
Filter: &tg.InputMessagesFilterEmpty{},
SavedPeerID: &tg.InputPeerEmpty{},
Limit: 1,
}).ThenResult(messagesClass(expected[:0], totalMessages))
total, err = iter.FetchTotal(ctx)
require.NoError(t, err)
require.Equal(t, totalMessages, total)
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,3 @@
package messages
//go:generate go run go.mau.fi/mautrix-telegram/pkg/gotd/telegram/query/internal/itergen -out=queries.gen.go
@@ -0,0 +1,177 @@
package messages
import (
"fmt"
"time"
"go.mau.fi/mautrix-telegram/pkg/gotd/tg"
)
// Document returns document object if message has a document attachment (video, voice, audio,
// basically every type except photo).
func (e Elem) Document() (*tg.Document, bool) {
msg, ok := e.Msg.(*tg.Message)
if !ok {
return nil, false
}
media, ok := msg.Media.(*tg.MessageMediaDocument)
if !ok {
return nil, false
}
return media.Document.AsNotEmpty()
}
// Photo returns photo object if message has a photo attachment.
func (e Elem) Photo() (*tg.Photo, bool) {
msg, ok := e.Msg.(*tg.Message)
if !ok {
return nil, false
}
media, ok := msg.Media.(*tg.MessageMediaPhoto)
if !ok {
return nil, false
}
return media.Photo.AsNotEmpty()
}
// File represents file attachment.
type File struct {
Name string
MIMEType string
Location tg.InputFileLocationClass
}
const dateLayout = "2006-01-02_15-04-05"
func getDocFilename(doc *tg.Document) string {
var filename, ext string
for _, attr := range doc.Attributes {
switch v := attr.(type) {
case *tg.DocumentAttributeImageSize:
switch doc.MimeType {
case "image/png":
ext = ".png"
case "image/webp":
ext = ".webp"
case "image/tiff":
ext = ".tif"
default:
ext = ".jpg"
}
case *tg.DocumentAttributeAnimated:
ext = ".gif"
case *tg.DocumentAttributeSticker:
ext = ".webp"
case *tg.DocumentAttributeVideo:
switch doc.MimeType {
case "video/mpeg":
ext = ".mpeg"
case "video/webm":
ext = ".webm"
case "video/ogg":
ext = ".ogg"
default:
ext = ".mp4"
}
case *tg.DocumentAttributeAudio:
switch doc.MimeType {
case "audio/webm":
ext = ".webm"
case "audio/aac":
ext = ".aac"
case "audio/ogg":
ext = ".ogg"
default:
ext = ".mp3"
}
case *tg.DocumentAttributeFilename:
filename = v.FileName
}
}
if filename == "" {
filename = fmt.Sprintf(
"doc%d_%s%s", doc.GetID(),
time.Unix(int64(doc.Date), 0).Format(dateLayout),
ext,
)
}
return filename
}
type sizedPhoto interface {
GetW() int
GetH() int
GetType() string
}
var (
_ sizedPhoto = (*tg.PhotoSize)(nil)
_ sizedPhoto = (*tg.PhotoCachedSize)(nil)
_ sizedPhoto = (*tg.PhotoSizeProgressive)(nil)
)
// File returns file location if message has a file attachment.
func (e Elem) File() (File, bool) {
msg, ok := e.Msg.(*tg.Message)
if !ok {
return File{}, false
}
switch media := msg.Media.(type) {
case *tg.MessageMediaPhoto:
photo, ok := media.Photo.AsNotEmpty()
if !ok {
return File{}, false
}
filename := fmt.Sprintf(
"photo%d_%s.jpg", photo.GetID(),
time.Unix(int64(photo.Date), 0).Format(dateLayout),
)
var (
thumbSize string
maxW, maxH int
)
for _, g := range photo.Sizes {
// TODO(tdakkota): add helpers to choose photo size.
if sz, ok := g.(sizedPhoto); ok && maxW < sz.GetW() && maxH < sz.GetH() {
thumbSize = sz.GetType()
}
}
if thumbSize == "" {
return File{}, false
}
return File{
Name: filename,
MIMEType: "image/jpeg",
Location: &tg.InputPhotoFileLocation{
ID: photo.ID,
AccessHash: photo.AccessHash,
FileReference: photo.FileReference,
ThumbSize: thumbSize,
},
}, true
case *tg.MessageMediaDocument:
doc, ok := media.Document.AsNotEmpty()
if !ok {
return File{}, false
}
return File{
Name: getDocFilename(doc),
MIMEType: doc.MimeType,
Location: doc.AsInputDocumentFileLocation(),
}, true
default:
return File{}, false
}
}
@@ -0,0 +1,133 @@
package messages
import (
"testing"
"time"
"github.com/stretchr/testify/require"
"go.mau.fi/mautrix-telegram/pkg/gotd/tg"
)
func Test_getDocFilename(t *testing.T) {
date := time.Now()
f := date.Format(dateLayout)
tests := []struct {
name string
args *tg.Document
want string
}{
{
"Doc",
&tg.Document{
Date: int(date.Unix()),
Attributes: []tg.DocumentAttributeClass{
&tg.DocumentAttributeFilename{FileName: "10.jpg"},
},
},
"10.jpg",
},
{
"Gif",
&tg.Document{
Date: int(date.Unix()),
Attributes: []tg.DocumentAttributeClass{
&tg.DocumentAttributeAnimated{},
},
},
"doc0_" + f + ".gif",
},
{
"Video",
&tg.Document{
Date: int(date.Unix()),
Attributes: []tg.DocumentAttributeClass{
&tg.DocumentAttributeVideo{},
},
},
"doc0_" + f + ".mp4",
},
{
"Photo",
&tg.Document{
Date: int(date.Unix()),
Attributes: []tg.DocumentAttributeClass{
&tg.DocumentAttributeImageSize{},
},
},
"doc0_" + f + ".jpg",
},
{
"Audio",
&tg.Document{
Date: int(date.Unix()),
Attributes: []tg.DocumentAttributeClass{
&tg.DocumentAttributeAudio{},
},
},
"doc0_" + f + ".mp3",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require.Equal(t, tt.want, getDocFilename(tt.args))
})
}
}
func TestElem_File(t *testing.T) {
type results struct {
file, doc, photo bool
}
tests := []struct {
Name string
Msg tg.NotEmptyMessage
results
}{
{"EmptyMessage", &tg.Message{}, results{}},
{"ServiceMessage", &tg.MessageService{}, results{}},
{"EmptyPhoto", &tg.Message{
Media: &tg.MessageMediaPhoto{
Photo: &tg.PhotoEmpty{},
},
}, results{}},
{"EmptyDoc", &tg.Message{
Media: &tg.MessageMediaDocument{
Document: &tg.DocumentEmpty{},
},
}, results{}},
{"Photo", &tg.Message{
Media: &tg.MessageMediaPhoto{
Photo: &tg.Photo{
Sizes: []tg.PhotoSizeClass{
&tg.PhotoSize{
Type: "cock",
W: 10,
H: 10,
},
},
},
},
}, results{file: true, photo: true}},
{"Document", &tg.Message{
Media: &tg.MessageMediaDocument{
Document: &tg.Document{},
},
}, results{file: true, doc: true}},
}
for _, test := range tests {
t.Run(test.Name, func(t *testing.T) {
a := require.New(t)
var ok bool
elem := Elem{Msg: test.Msg}
_, ok = elem.File()
a.Equal(test.file, ok)
_, ok = elem.Document()
a.Equal(test.doc, ok)
_, ok = elem.Photo()
a.Equal(test.photo, ok)
})
}
}
@@ -0,0 +1,166 @@
// Package featured contains featured stickers iteration helper.
package featured
import (
"context"
"github.com/go-faster/errors"
"go.mau.fi/mautrix-telegram/pkg/gotd/tg"
)
// Elem is a sticker iterator element.
type Elem struct {
Sticker tg.StickerSetCoveredClass
// IDs of new featured stickersets
Unread []int64
}
// Iterator is a featured stickers 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.MessagesFeaturedStickersClass) error {
if m.lastBatch {
return nil
}
var (
stickers []tg.StickerSetCoveredClass
unread []int64
)
switch stks := r.(type) {
case *tg.MessagesFeaturedStickers: // messages.featuredStickers#b6abc341
stickers = stks.Sets
unread = stks.Unread
m.count = stks.Count
m.lastBatch = len(stickers) < m.limit
default:
return errors.Errorf("unexpected type %T", r)
}
m.totalGot = true
m.offset += len(stickers)
m.bufCur = -1
m.buf = m.buf[:0]
for i := range stickers {
m.buf = append(m.buf, Elem{Sticker: stickers[i], Unread: unread})
}
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 stks := r.(type) {
case *tg.MessagesFeaturedStickers: // messages.featuredStickers#b6abc341
m.count = stks.Count
default:
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,85 @@
package featured
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 generateStickers(count int) []tg.StickerSetCoveredClass {
r := make([]tg.StickerSetCoveredClass, 0, count)
for i := 0; i < count; i++ {
r = append(r, &tg.StickerSetCovered{
Set: tg.StickerSet{
ID: int64(i + 1),
AccessHash: int64(i + 1),
},
Cover: &tg.Document{
ID: int64(i + 1),
AccessHash: int64(i + 1),
FileReference: []uint8{uint8(i)},
},
})
}
return r
}
func result(r []tg.StickerSetCoveredClass, count int) tg.MessagesFeaturedStickersClass {
return &tg.MessagesFeaturedStickers{
Sets: r,
Count: count,
}
}
func TestIterator(t *testing.T) {
ctx := context.Background()
mock := tgmock.NewRequire(t)
limit := 10
totalRecords := 3 * limit
expected := generateStickers(totalRecords)
raw := tg.NewClient(mock)
mock.ExpectCall(&tg.MessagesGetOldFeaturedStickersRequest{
Offset: 0,
Limit: limit,
}).ThenResult(result(expected[0:limit], totalRecords))
mock.ExpectCall(&tg.MessagesGetOldFeaturedStickersRequest{
Offset: limit,
Limit: limit,
}).ThenResult(result(expected[limit:2*limit], totalRecords))
mock.ExpectCall(&tg.MessagesGetOldFeaturedStickersRequest{
Offset: 2 * limit,
Limit: limit,
}).ThenResult(result(expected[2*limit:3*limit], totalRecords))
mock.ExpectCall(&tg.MessagesGetOldFeaturedStickersRequest{
Offset: 3 * limit,
Limit: limit,
}).ThenResult(result(expected[3*limit:], totalRecords))
iter := NewQueryBuilder(raw).GetOldFeaturedStickers().BatchSize(10).Iter()
i := 0
for iter.Next(ctx) {
require.Equal(t, expected[i], iter.Value().Sticker)
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.MessagesGetOldFeaturedStickersRequest{
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,125 @@
// Code generated by itergen, DO NOT EDIT.
package featured
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 featured request.
// NB: iterator mutates returned data (sorts, at least).
type Query interface {
Query(ctx context.Context, req Request) (tg.MessagesFeaturedStickersClass, error)
}
// QueryFunc is a function adapter for Query.
type QueryFunc func(ctx context.Context, req Request) (tg.MessagesFeaturedStickersClass, error)
// Query implements Query interface.
func (q QueryFunc) Query(ctx context.Context, req Request) (tg.MessagesFeaturedStickersClass, 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}
}
// GetOldFeaturedStickersQueryBuilder is query builder of MessagesGetOldFeaturedStickers.
type GetOldFeaturedStickersQueryBuilder struct {
raw *tg.Client
req tg.MessagesGetOldFeaturedStickersRequest
batchSize int
offset int
}
// GetOldFeaturedStickers creates query builder of MessagesGetOldFeaturedStickers.
func (q *QueryBuilder) GetOldFeaturedStickers() *GetOldFeaturedStickersQueryBuilder {
b := &GetOldFeaturedStickersQueryBuilder{
raw: q.raw,
batchSize: 1,
req: tg.MessagesGetOldFeaturedStickersRequest{},
}
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 *GetOldFeaturedStickersQueryBuilder) BatchSize(batchSize int) *GetOldFeaturedStickersQueryBuilder {
b.batchSize = batchSize
return b
}
// Query implements Query interface.
func (b *GetOldFeaturedStickersQueryBuilder) Query(ctx context.Context, req Request) (tg.MessagesFeaturedStickersClass, error) {
r := &tg.MessagesGetOldFeaturedStickersRequest{
Limit: req.Limit,
}
r.Offset = req.Offset
return b.raw.MessagesGetOldFeaturedStickers(ctx, r)
}
// Iter returns iterator using built query.
func (b *GetOldFeaturedStickersQueryBuilder) Iter() *Iterator {
iter := NewIterator(b, b.batchSize)
iter = iter.Offset(b.offset)
return iter
}
// ForEach calls given callback on each iterator element.
func (b *GetOldFeaturedStickersQueryBuilder) 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 *GetOldFeaturedStickersQueryBuilder) 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 *GetOldFeaturedStickersQueryBuilder) 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 featured
//go:generate go run go.mau.fi/mautrix-telegram/pkg/gotd/telegram/query/internal/itergen -result=MessagesFeaturedStickersClass -package=featured -out=queries.gen.go