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,45 @@
|
||||
package fileid
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
|
||||
"github.com/go-faster/errors"
|
||||
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/bin"
|
||||
)
|
||||
|
||||
const (
|
||||
persistentIDVersionOld = 2
|
||||
persistentIDVersionMap = 3
|
||||
persistentIDVersion = 4
|
||||
)
|
||||
|
||||
// DecodeFileID parses FileID from a string.
|
||||
func DecodeFileID(s string) (fileID FileID, _ error) {
|
||||
if s == "" {
|
||||
return FileID{}, errors.New("input is empty")
|
||||
}
|
||||
data, err := base64Decode(s)
|
||||
if err != nil {
|
||||
return FileID{}, errors.Wrap(err, "base64")
|
||||
}
|
||||
data = rleDecode(data)
|
||||
if len(data) < 2 {
|
||||
return FileID{}, errors.New("RLE-decoded data is too small")
|
||||
}
|
||||
|
||||
switch version := data[len(data)-1]; version {
|
||||
case persistentIDVersionOld, persistentIDVersionMap:
|
||||
return FileID{}, errors.Errorf("%v is unsupported now", version)
|
||||
case persistentIDVersion:
|
||||
data = data[:len(data)-1]
|
||||
err := fileID.decodeLatestFileID(&bin.Buffer{Buf: data})
|
||||
return fileID, err
|
||||
default:
|
||||
return FileID{}, errors.Errorf("unknown file_id version %x", version)
|
||||
}
|
||||
}
|
||||
|
||||
func base64Decode(s string) ([]byte, error) {
|
||||
return base64.RawURLEncoding.DecodeString(s)
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
//go:build go1.18
|
||||
|
||||
package fileid
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func FuzzDecodeFileID(f *testing.F) {
|
||||
for _, input := range testData {
|
||||
f.Add(input)
|
||||
}
|
||||
|
||||
f.Fuzz(func(t *testing.T, fileID string) {
|
||||
_, err := DecodeFileID(fileID)
|
||||
if err != nil {
|
||||
t.Skip(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
package fileid
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var testData = map[string]string{
|
||||
"Sticker": "CAACAgIAAxkBAAM6YZlDEHCmaTKrUhCIjxAPtPtjVx4AAicAA4dXjx6dGLyHwXVNcCIE",
|
||||
"AnimatedSticker": "CAACAgIAAxkBAANCYZzsGL3c2jB4BE46_bD9-aaYH10AApEOAAJZQylKSstCqeiyJ5giBA",
|
||||
"GIF": "CgACAgIAAxkBAAM7YZqVjhoGXOIk6qgVu7xd0QvyRVEAArQQAAK7XrBIi5xgKHPRFpQiBA",
|
||||
"GIFThumbnail": "AAMCAgADGQEAAzthmpWOGgZc4iTqqBW7vF3RC_JFUQACtBAAArtesEiLnGAoc9EWlAEAB20AAyIE",
|
||||
"Photo": "AgACAgIAAxkBAAM9YZqXG-B0WHEv7lFlQxOQDs6jrGQAAoa7MRvdfNlIhJa73cDxR0kBAAMCAAN4AAMiBA",
|
||||
"Video": "BAACAgIAAxkBAANAYZzjSkCVY7Ttrp2l92eCQzYYxVEAAkoRAAJIYKFIRionwJTz4kIiBA",
|
||||
"VideoThumbnail": "AAMCAgADGQEAA0BhnONKQJVjtO2unaX3Z4JDNhjFUQACShEAAkhgoUhGKifAlPPiQgEAB20AAyIE",
|
||||
"ChatPhoto": "AQADAgAD7a8xG75QcEkACAMAA2jAIuIW____cd7THMWjNdIiBA",
|
||||
"Voice": "AwACAgIAAxkBAANDYZzsXw55-6fljCSeQXEP3dX5_egAAlkSAAJStulIAYO3JdIypKQiBA",
|
||||
"Audio": "CQACAgIAAxkBAANEYZzt3rDAw5CkHSU8RZA8AzTTsyMAAvACAAKoAAF4SjhQUd8y3lIoIgQ",
|
||||
}
|
||||
|
||||
var wantData = map[string]FileID{
|
||||
"Sticker": {
|
||||
Type: Sticker,
|
||||
DC: 2,
|
||||
ID: 2202074980139663399,
|
||||
AccessHash: 8092253579521038493,
|
||||
FileReference: []byte("\x01\x00\x00\x00:a\x99C\x10p\xa6i2\xabR\x10\x88\x8f\x10\x0f\xb4\xfbcW\x1e"),
|
||||
},
|
||||
"AnimatedSticker": {
|
||||
Type: Sticker,
|
||||
DC: 2,
|
||||
ID: 5343876482382958225,
|
||||
AccessHash: -7482815543510906038,
|
||||
FileReference: []byte("\x01\x00\x00\x00Ba\x9c\xec\x18\xbd\xdc\xda0x\x04N:\xfd\xb0\xfd\xf9\xa6\x98\x1f]"),
|
||||
},
|
||||
"GIF": {
|
||||
Type: Animation,
|
||||
DC: 2,
|
||||
ID: 5237790523883786420,
|
||||
AccessHash: -7775797414079718261,
|
||||
FileReference: []byte("\x01\x00\x00\x00;a\x9a\x95\x8e\x1a\x06\\\xe2$\xea\xa8\x15\xbb\xbc]\xd1\v\xf2EQ"),
|
||||
},
|
||||
"GIFThumbnail": {
|
||||
Type: Thumbnail,
|
||||
DC: 2,
|
||||
ID: 5237790523883786420,
|
||||
AccessHash: -7775797414079718261,
|
||||
FileReference: []byte("\x01\x00\x00\x00;a\x9a\x95\x8e\x1a\x06\\\xe2$\xea\xa8\x15\xbb\xbc]\xd1\v\xf2EQ"),
|
||||
PhotoSizeSource: PhotoSizeSource{
|
||||
Type: PhotoSizeSourceThumbnail,
|
||||
ThumbnailType: 109,
|
||||
},
|
||||
},
|
||||
"Photo": {
|
||||
Type: Photo,
|
||||
DC: 2,
|
||||
ID: 5249364129762884486,
|
||||
AccessHash: 5280454898771269252,
|
||||
FileReference: []byte("\x01\x00\x00\x00=a\x9a\x97\x1b\xe0tXq/\xeeQeC\x13\x90\x0eΣ\xacd"),
|
||||
PhotoSizeSource: PhotoSizeSource{
|
||||
Type: PhotoSizeSourceThumbnail,
|
||||
FileType: Photo,
|
||||
ThumbnailType: 120,
|
||||
},
|
||||
},
|
||||
"Video": {
|
||||
Type: Video,
|
||||
DC: 2,
|
||||
ID: 5233570104335143242,
|
||||
AccessHash: 4819682371444353606,
|
||||
FileReference: []byte("\x01\x00\x00\x00@a\x9c\xe3J@\x95c\xb4\xed\xae\x9d\xa5\xf7g\x82C6\x18\xc5Q"),
|
||||
},
|
||||
"VideoThumbnail": {
|
||||
Type: Thumbnail,
|
||||
DC: 2,
|
||||
ID: 5233570104335143242,
|
||||
AccessHash: 4819682371444353606,
|
||||
FileReference: []byte("\x01\x00\x00\x00@a\x9c\xe3J@\x95c\xb4\xed\xae\x9d\xa5\xf7g\x82C6\x18\xc5Q"),
|
||||
PhotoSizeSource: PhotoSizeSource{
|
||||
Type: PhotoSizeSourceThumbnail,
|
||||
ThumbnailType: 109,
|
||||
},
|
||||
},
|
||||
"ChatPhoto": {
|
||||
Type: ProfilePhoto,
|
||||
DC: 2,
|
||||
ID: 5291818339590582253,
|
||||
PhotoSizeSource: PhotoSizeSource{
|
||||
Type: PhotoSizeSourceDialogPhotoBig,
|
||||
DialogID: -1001228418968,
|
||||
DialogAccessHash: -3299551084991488399,
|
||||
},
|
||||
},
|
||||
"Voice": {
|
||||
Type: Voice,
|
||||
DC: 2,
|
||||
ID: 5253930903607972441,
|
||||
AccessHash: -6583080877151517951,
|
||||
FileReference: []byte("\x01\x00\x00\x00Ca\x9c\xec_\x0ey\xfb\xa7\xe5\x8c$\x9eAq\x0f\xdd\xd5\xf9\xfd\xe8"),
|
||||
},
|
||||
"Audio": {
|
||||
Type: Audio,
|
||||
DC: 2,
|
||||
ID: 5366039677566452464,
|
||||
AccessHash: 2905629019683770424,
|
||||
FileReference: []byte("\x01\x00\x00\x00Da\x9c\xedް\xc0Ð\xa4\x1d%<E\x90<\x034ӳ#"),
|
||||
},
|
||||
}
|
||||
|
||||
func TestDecodeFileID(t *testing.T) {
|
||||
type testCase struct {
|
||||
name string
|
||||
input string
|
||||
want FileID
|
||||
wantErr bool
|
||||
}
|
||||
tests := []testCase{
|
||||
{
|
||||
"Empty",
|
||||
"",
|
||||
FileID{},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"InvalidBase64",
|
||||
"/-*-/--+",
|
||||
FileID{},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"TooSmallLength",
|
||||
base64.RawURLEncoding.EncodeToString([]byte{1}),
|
||||
FileID{},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"UnsupportedVersion",
|
||||
base64.RawURLEncoding.EncodeToString([]byte{1, persistentIDVersionOld}),
|
||||
FileID{},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"UnknownVersion",
|
||||
base64.RawURLEncoding.EncodeToString([]byte{1, 20}),
|
||||
FileID{},
|
||||
true,
|
||||
},
|
||||
}
|
||||
for name, input := range testData {
|
||||
expect, ok := wantData[name]
|
||||
if !ok {
|
||||
t.Fatalf("Update wantData[%q]", name)
|
||||
}
|
||||
tests = append(tests, testCase{
|
||||
name: name,
|
||||
input: input,
|
||||
want: expect,
|
||||
wantErr: false,
|
||||
})
|
||||
}
|
||||
sort.Slice(tests, func(i, j int) bool {
|
||||
return tests[i].name < tests[j].name
|
||||
})
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
a := require.New(t)
|
||||
|
||||
got, err := DecodeFileID(tt.input)
|
||||
if tt.wantErr {
|
||||
a.Error(err)
|
||||
} else {
|
||||
a.NoError(err)
|
||||
a.Equal(tt.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package fileid_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-faster/errors"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zaptest"
|
||||
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/fileid"
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/telegram"
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/telegram/downloader"
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/testutil"
|
||||
)
|
||||
|
||||
func runBot(ctx context.Context, token, fileID string, logger *zap.Logger) error {
|
||||
bot := telegram.NewClient(telegram.TestAppID, telegram.TestAppHash, telegram.Options{
|
||||
Logger: logger,
|
||||
})
|
||||
d := downloader.NewDownloader()
|
||||
|
||||
return bot.Run(ctx, func(ctx context.Context) error {
|
||||
auth, err := bot.Auth().Bot(ctx, token)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "auth bot")
|
||||
}
|
||||
user, ok := auth.User.AsNotEmpty()
|
||||
if !ok {
|
||||
return errors.Errorf("unexpected type %T", auth.User)
|
||||
}
|
||||
_ = user
|
||||
|
||||
decoded, err := fileid.DecodeFileID(fileID)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "decode FileID")
|
||||
}
|
||||
|
||||
loc, ok := decoded.AsInputFileLocation()
|
||||
if !ok {
|
||||
return errors.Errorf("can't map %q", fileID)
|
||||
}
|
||||
|
||||
filename := "file.dat"
|
||||
switch decoded.Type {
|
||||
case fileid.Thumbnail, fileid.ProfilePhoto, fileid.Photo:
|
||||
filename = "file.jpg"
|
||||
case fileid.Video,
|
||||
fileid.Animation,
|
||||
fileid.VideoNote:
|
||||
filename = "file.mp4"
|
||||
case fileid.Audio:
|
||||
filename = "file.mp3"
|
||||
case fileid.Voice:
|
||||
filename = "file.ogg"
|
||||
case fileid.Sticker:
|
||||
filename = "file.png"
|
||||
}
|
||||
|
||||
if _, err := d.Download(bot.API(), loc).ToPath(ctx, filename); err != nil {
|
||||
return errors.Wrap(err, "download")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func TestExternalE2ECheckFileID(t *testing.T) {
|
||||
testutil.SkipExternal(t)
|
||||
token := os.Getenv("GOTD_E2E_BOT_TOKEN")
|
||||
if token == "" {
|
||||
t.Skip("Set GOTD_E2E_BOT_TOKEN env to run test.")
|
||||
}
|
||||
fileID := os.Getenv("GOTD_E2E_FILE_ID")
|
||||
if fileID == "" {
|
||||
t.Skip("Set GOTD_E2E_FILE_ID env to run test.")
|
||||
}
|
||||
logger := zaptest.NewLogger(t)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||
defer cancel()
|
||||
|
||||
if err := runBot(ctx, token, fileID, logger.Named("bot")); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package fileid
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/bin"
|
||||
)
|
||||
|
||||
// EncodeFileID parses FileID to a string.
|
||||
func EncodeFileID(id FileID) (string, error) {
|
||||
var buf bin.Buffer
|
||||
id.encodeLatestFileID(&buf)
|
||||
buf.Buf = append(buf.Buf, persistentIDVersion)
|
||||
buf.Buf = rleEncode(buf.Buf)
|
||||
return base64Encode(buf.Buf), nil
|
||||
}
|
||||
|
||||
func base64Encode(s []byte) string {
|
||||
return base64.RawURLEncoding.EncodeToString(s)
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package fileid
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestFileIDEncodeDecode(t *testing.T) {
|
||||
for name, input := range testData {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
a := require.New(t)
|
||||
fileID, err := DecodeFileID(input)
|
||||
a.NoError(err)
|
||||
|
||||
output, err := EncodeFileID(fileID)
|
||||
a.NoError(err)
|
||||
|
||||
decoded, err := DecodeFileID(output)
|
||||
a.NoError(err)
|
||||
a.Equal(fileID, decoded)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package fileid
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/go-faster/errors"
|
||||
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/bin"
|
||||
)
|
||||
|
||||
// FileID represents parsed Telegram Bot API file_id.
|
||||
type FileID struct {
|
||||
Type Type
|
||||
DC int
|
||||
ID int64
|
||||
AccessHash int64
|
||||
FileReference []byte
|
||||
URL string
|
||||
PhotoSizeSource PhotoSizeSource
|
||||
}
|
||||
|
||||
const (
|
||||
webLocationFlag = 1 << 24
|
||||
fileReferenceFlag = 1 << 25
|
||||
)
|
||||
|
||||
func (f *FileID) decodeLatestFileID(b *bin.Buffer) error {
|
||||
if len(b.Buf) < 1 {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
var subVersion = b.Buf[len(b.Buf)-1]
|
||||
|
||||
typeID, err := b.Uint32()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read type_id")
|
||||
}
|
||||
|
||||
hasWebLocation := typeID&webLocationFlag != 0
|
||||
hasReference := typeID&fileReferenceFlag != 0
|
||||
|
||||
typeID &^= webLocationFlag
|
||||
typeID &^= fileReferenceFlag
|
||||
if typeID >= uint32(lastType) {
|
||||
return errors.Errorf("unknown type %d", typeID)
|
||||
}
|
||||
f.Type = Type(typeID)
|
||||
|
||||
{
|
||||
dcID, err := b.Uint32()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read dc_id")
|
||||
}
|
||||
f.DC = int(dcID)
|
||||
}
|
||||
|
||||
if hasReference {
|
||||
reference, err := b.Bytes()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read file_reference")
|
||||
}
|
||||
f.FileReference = reference
|
||||
}
|
||||
if hasWebLocation {
|
||||
url, err := b.String()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read url")
|
||||
}
|
||||
f.URL = url
|
||||
return nil
|
||||
}
|
||||
|
||||
{
|
||||
id, err := b.Long()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read id")
|
||||
}
|
||||
f.ID = id
|
||||
}
|
||||
|
||||
{
|
||||
accessHash, err := b.Long()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read access_hash")
|
||||
}
|
||||
f.AccessHash = accessHash
|
||||
}
|
||||
|
||||
switch Type(typeID) {
|
||||
case Thumbnail, Photo, ProfilePhoto:
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := f.PhotoSizeSource.decode(b, subVersion); err != nil {
|
||||
return errors.Wrap(err, "decode photo_size")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FileID) encodeLatestFileID(b *bin.Buffer) {
|
||||
hasWebLocation := f.URL != ""
|
||||
hasReference := len(f.FileReference) != 0
|
||||
|
||||
{
|
||||
typeID := f.Type
|
||||
if hasWebLocation {
|
||||
typeID |= webLocationFlag
|
||||
}
|
||||
if hasReference {
|
||||
typeID |= fileReferenceFlag
|
||||
}
|
||||
b.PutUint32(uint32(typeID))
|
||||
}
|
||||
b.PutUint32(uint32(f.DC))
|
||||
if hasReference {
|
||||
b.PutBytes(f.FileReference)
|
||||
}
|
||||
if hasWebLocation {
|
||||
b.PutString(f.URL)
|
||||
return
|
||||
}
|
||||
b.PutLong(f.ID)
|
||||
b.PutLong(f.AccessHash)
|
||||
|
||||
switch f.Type {
|
||||
case Thumbnail, Photo, ProfilePhoto:
|
||||
f.PhotoSizeSource.encode(b)
|
||||
}
|
||||
|
||||
b.Buf = append(b.Buf, latestSubVersion)
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
//go:build go1.18
|
||||
// +build go1.18
|
||||
|
||||
package fileid
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/k0kubun/pp/v3"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/bin"
|
||||
)
|
||||
|
||||
func FuzzDecodeEncodeDecode(f *testing.F) {
|
||||
for name, input := range testData {
|
||||
data, err := base64Decode(input)
|
||||
if err != nil {
|
||||
f.Fatal(name, err)
|
||||
}
|
||||
data = rleDecode(data)
|
||||
f.Add(data)
|
||||
}
|
||||
|
||||
f.Fuzz(func(t *testing.T, data []byte) {
|
||||
a := require.New(t)
|
||||
input := bin.Buffer{Buf: data}
|
||||
|
||||
var fileID FileID
|
||||
if err := fileID.decodeLatestFileID(&input); err != nil {
|
||||
t.Skip(err)
|
||||
}
|
||||
if data[len(data)-1] < 32 {
|
||||
t.Skip("Legacy file_id encoding is not supported")
|
||||
}
|
||||
if fileID.PhotoSizeSource.Type >= PhotoSizeSourceStickerSetThumbnail {
|
||||
t.Log(pp.Sprint(input))
|
||||
}
|
||||
input.Reset()
|
||||
|
||||
fileID.encodeLatestFileID(&input)
|
||||
|
||||
var decoded FileID
|
||||
a.NoError(decoded.decodeLatestFileID(&input))
|
||||
a.Equal(fileID, decoded)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
// Package fileid contains BotAPI and tdlib file_id decoder.
|
||||
package fileid
|
||||
@@ -0,0 +1,81 @@
|
||||
package fileid
|
||||
|
||||
import (
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/constant"
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/tg"
|
||||
)
|
||||
|
||||
// FromDocument creates FileID from tg.Document.
|
||||
func FromDocument(doc *tg.Document) FileID {
|
||||
fileID := FileID{
|
||||
Type: DocumentAsFile,
|
||||
DC: doc.DCID,
|
||||
ID: doc.ID,
|
||||
AccessHash: doc.AccessHash,
|
||||
FileReference: doc.FileReference,
|
||||
}
|
||||
for _, attr := range doc.Attributes {
|
||||
switch attr := attr.(type) {
|
||||
case *tg.DocumentAttributeAnimated:
|
||||
fileID.Type = Animation
|
||||
case *tg.DocumentAttributeSticker:
|
||||
fileID.Type = Sticker
|
||||
case *tg.DocumentAttributeVideo:
|
||||
fileID.Type = Video
|
||||
if attr.RoundMessage {
|
||||
fileID.Type = VideoNote
|
||||
}
|
||||
case *tg.DocumentAttributeAudio:
|
||||
fileID.Type = Audio
|
||||
if attr.Voice {
|
||||
fileID.Type = Voice
|
||||
}
|
||||
}
|
||||
}
|
||||
return fileID
|
||||
}
|
||||
|
||||
// FromPhoto creates FileID from tg.Photo.
|
||||
func FromPhoto(photo *tg.Photo, thumbType rune) FileID {
|
||||
return FileID{
|
||||
Type: Photo,
|
||||
DC: photo.DCID,
|
||||
ID: photo.ID,
|
||||
AccessHash: photo.AccessHash,
|
||||
FileReference: photo.FileReference,
|
||||
PhotoSizeSource: PhotoSizeSource{
|
||||
Type: PhotoSizeSourceThumbnail,
|
||||
FileType: Photo,
|
||||
ThumbnailType: thumbType,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ChatPhoto is interface for user profile photo and chat photo structures.
|
||||
type ChatPhoto interface {
|
||||
GetDCID() int
|
||||
GetPhotoID() int64
|
||||
}
|
||||
|
||||
var _ = []ChatPhoto{
|
||||
(*tg.ChatPhoto)(nil),
|
||||
(*tg.UserProfilePhoto)(nil),
|
||||
}
|
||||
|
||||
// FromChatPhoto creates new FileID from ChatPhoto.
|
||||
func FromChatPhoto(id constant.TDLibPeerID, accessHash int64, photo ChatPhoto, big bool) FileID {
|
||||
typ := PhotoSizeSourceDialogPhotoSmall
|
||||
if big {
|
||||
typ = PhotoSizeSourceDialogPhotoBig
|
||||
}
|
||||
return FileID{
|
||||
Type: ProfilePhoto,
|
||||
DC: photo.GetDCID(),
|
||||
ID: photo.GetPhotoID(),
|
||||
PhotoSizeSource: PhotoSizeSource{
|
||||
Type: typ,
|
||||
DialogID: id,
|
||||
DialogAccessHash: accessHash,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package fileid
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/tg"
|
||||
)
|
||||
|
||||
func TestFromDocument(t *testing.T) {
|
||||
doc := func(attrs ...tg.DocumentAttributeClass) *tg.Document {
|
||||
return &tg.Document{
|
||||
ID: 1,
|
||||
AccessHash: 2,
|
||||
FileReference: []byte{3},
|
||||
DCID: 4,
|
||||
Attributes: attrs,
|
||||
}
|
||||
}
|
||||
fileID := func(typ Type) FileID {
|
||||
return FileID{
|
||||
Type: typ,
|
||||
ID: 1,
|
||||
AccessHash: 2,
|
||||
FileReference: []byte{3},
|
||||
DC: 4,
|
||||
}
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
doc *tg.Document
|
||||
want FileID
|
||||
}{
|
||||
{"File", doc(), fileID(DocumentAsFile)},
|
||||
{"Animation", doc(&tg.DocumentAttributeAnimated{}), fileID(Animation)},
|
||||
{"Sticker", doc(&tg.DocumentAttributeSticker{}), fileID(Sticker)},
|
||||
{"Video", doc(&tg.DocumentAttributeVideo{}), fileID(Video)},
|
||||
{"VideoNote", doc(&tg.DocumentAttributeVideo{RoundMessage: true}), fileID(VideoNote)},
|
||||
{"Audio", doc(&tg.DocumentAttributeAudio{}), fileID(Audio)},
|
||||
{"Voice", doc(&tg.DocumentAttributeAudio{Voice: true}), fileID(Voice)},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
require.Equal(t, tt.want, FromDocument(tt.doc))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFromPhoto(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
photo *tg.Photo
|
||||
size rune
|
||||
want FileID
|
||||
}{
|
||||
{
|
||||
"Photo",
|
||||
&tg.Photo{
|
||||
ID: 1,
|
||||
AccessHash: 2,
|
||||
FileReference: []byte{3},
|
||||
DCID: 4,
|
||||
},
|
||||
'x',
|
||||
FileID{
|
||||
Type: Photo,
|
||||
ID: 1,
|
||||
AccessHash: 2,
|
||||
FileReference: []byte{3},
|
||||
DC: 4,
|
||||
PhotoSizeSource: PhotoSizeSource{
|
||||
Type: PhotoSizeSourceThumbnail,
|
||||
FileType: Photo,
|
||||
ThumbnailType: 'x',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
require.Equal(t, tt.want, FromPhoto(tt.photo, tt.size))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package fileid
|
||||
|
||||
import "go.mau.fi/mautrix-telegram/pkg/gotd/tg"
|
||||
|
||||
// AsInputWebFileLocation converts file ID to tg.InputWebFileLocationClass.
|
||||
func (f FileID) AsInputWebFileLocation() (tg.InputWebFileLocationClass, bool) {
|
||||
if f.URL == "" {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return &tg.InputWebFileLocation{
|
||||
URL: f.URL,
|
||||
AccessHash: f.AccessHash,
|
||||
}, true
|
||||
}
|
||||
|
||||
func (f FileID) asPhotoLocation() (tg.InputFileLocationClass, bool) {
|
||||
switch src := f.PhotoSizeSource; src.Type {
|
||||
case PhotoSizeSourceLegacy:
|
||||
case PhotoSizeSourceThumbnail:
|
||||
switch src.FileType {
|
||||
case Photo, Thumbnail:
|
||||
return &tg.InputPhotoFileLocation{
|
||||
ID: f.ID,
|
||||
AccessHash: f.AccessHash,
|
||||
FileReference: f.FileReference,
|
||||
ThumbSize: string(f.PhotoSizeSource.ThumbnailType),
|
||||
}, true
|
||||
}
|
||||
case PhotoSizeSourceDialogPhotoSmall,
|
||||
PhotoSizeSourceDialogPhotoBig:
|
||||
return &tg.InputPeerPhotoFileLocation{
|
||||
Big: src.Type == PhotoSizeSourceDialogPhotoBig,
|
||||
Peer: src.dialogPeer(),
|
||||
PhotoID: f.ID,
|
||||
}, true
|
||||
case PhotoSizeSourceStickerSetThumbnail:
|
||||
case PhotoSizeSourceFullLegacy:
|
||||
return &tg.InputPhotoLegacyFileLocation{
|
||||
ID: f.ID,
|
||||
AccessHash: f.AccessHash,
|
||||
FileReference: f.FileReference,
|
||||
VolumeID: f.PhotoSizeSource.VolumeID,
|
||||
LocalID: f.PhotoSizeSource.LocalID,
|
||||
Secret: f.PhotoSizeSource.Secret,
|
||||
}, true
|
||||
case PhotoSizeSourceDialogPhotoSmallLegacy,
|
||||
PhotoSizeSourceDialogPhotoBigLegacy:
|
||||
return &tg.InputPeerPhotoFileLocationLegacy{
|
||||
Big: src.Type == PhotoSizeSourceDialogPhotoBigLegacy,
|
||||
Peer: src.dialogPeer(),
|
||||
VolumeID: f.PhotoSizeSource.VolumeID,
|
||||
LocalID: f.PhotoSizeSource.LocalID,
|
||||
}, true
|
||||
case PhotoSizeSourceStickerSetThumbnailLegacy:
|
||||
return &tg.InputStickerSetThumbLegacy{
|
||||
Stickerset: f.PhotoSizeSource.stickerSet(),
|
||||
VolumeID: f.PhotoSizeSource.VolumeID,
|
||||
LocalID: f.PhotoSizeSource.LocalID,
|
||||
}, true
|
||||
case PhotoSizeSourceStickerSetThumbnailVersion:
|
||||
return &tg.InputStickerSetThumb{
|
||||
Stickerset: f.PhotoSizeSource.stickerSet(),
|
||||
ThumbVersion: int(f.PhotoSizeSource.StickerVersion),
|
||||
}, true
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// AsInputFileLocation converts file ID to tg.InputFileLocationClass.
|
||||
func (f FileID) AsInputFileLocation() (tg.InputFileLocationClass, bool) {
|
||||
switch f.Type {
|
||||
case Thumbnail, ProfilePhoto, Photo:
|
||||
return f.asPhotoLocation()
|
||||
case Encrypted:
|
||||
return &tg.InputEncryptedFileLocation{
|
||||
ID: f.ID,
|
||||
AccessHash: f.AccessHash,
|
||||
}, true
|
||||
case SecureRaw,
|
||||
Secure:
|
||||
return &tg.InputSecureFileLocation{
|
||||
ID: f.ID,
|
||||
AccessHash: f.AccessHash,
|
||||
}, true
|
||||
case Video,
|
||||
Voice,
|
||||
Document,
|
||||
Sticker,
|
||||
Audio,
|
||||
Animation,
|
||||
VideoNote,
|
||||
Background,
|
||||
DocumentAsFile:
|
||||
return &tg.InputDocumentFileLocation{
|
||||
ID: f.ID,
|
||||
AccessHash: f.AccessHash,
|
||||
FileReference: f.FileReference,
|
||||
ThumbSize: "", // ?
|
||||
}, true
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
package fileid
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/constant"
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/tg"
|
||||
)
|
||||
|
||||
func TestFileID_AsInputFileLocation(t *testing.T) {
|
||||
type testCase struct {
|
||||
name string
|
||||
fileID FileID
|
||||
want tg.InputFileLocationClass
|
||||
wantOk bool
|
||||
}
|
||||
tests := []testCase{
|
||||
{
|
||||
"Sticker",
|
||||
wantData["Sticker"],
|
||||
&tg.InputDocumentFileLocation{
|
||||
ID: 2202074980139663399,
|
||||
AccessHash: 8092253579521038493,
|
||||
FileReference: []byte("\x01\x00\x00\x00:a\x99C\x10p\xa6i2\xabR\x10\x88\x8f\x10\x0f\xb4\xfbcW\x1e"),
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"AnimatedSticker",
|
||||
wantData["AnimatedSticker"],
|
||||
&tg.InputDocumentFileLocation{
|
||||
ID: 5343876482382958225,
|
||||
AccessHash: -7482815543510906038,
|
||||
FileReference: []byte("\x01\x00\x00\x00Ba\x9c\xec\x18\xbd\xdc\xda0x\x04N:\xfd\xb0\xfd\xf9\xa6\x98\x1f]"),
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"GIF",
|
||||
wantData["GIF"],
|
||||
&tg.InputDocumentFileLocation{
|
||||
ID: 5237790523883786420,
|
||||
AccessHash: -7775797414079718261,
|
||||
FileReference: []byte("\x01\x00\x00\x00;a\x9a\x95\x8e\x1a\x06\\\xe2$\xea\xa8\x15\xbb\xbc]\xd1\v\xf2EQ"),
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"GIFThumbnail",
|
||||
wantData["GIFThumbnail"],
|
||||
&tg.InputPhotoFileLocation{
|
||||
ID: 5237790523883786420,
|
||||
AccessHash: -7775797414079718261,
|
||||
FileReference: []byte("\x01\x00\x00\x00;a\x9a\x95\x8e\x1a\x06\\\xe2$\xea\xa8\x15\xbb\xbc]\xd1\v\xf2EQ"),
|
||||
ThumbSize: "m",
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"Photo",
|
||||
wantData["Photo"],
|
||||
&tg.InputPhotoFileLocation{
|
||||
ID: 5249364129762884486,
|
||||
AccessHash: 5280454898771269252,
|
||||
FileReference: []byte("\x01\x00\x00\x00=a\x9a\x97\x1b\xe0tXq/\xeeQeC\x13\x90\x0eΣ\xacd"),
|
||||
ThumbSize: "x",
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"Video",
|
||||
wantData["Video"],
|
||||
&tg.InputDocumentFileLocation{
|
||||
ID: 5233570104335143242,
|
||||
AccessHash: 4819682371444353606,
|
||||
FileReference: []byte("\x01\x00\x00\x00@a\x9c\xe3J@\x95c\xb4\xed\xae\x9d\xa5\xf7g\x82C6\x18\xc5Q"),
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"VideoThumbnail",
|
||||
wantData["VideoThumbnail"],
|
||||
&tg.InputPhotoFileLocation{
|
||||
ID: 5233570104335143242,
|
||||
AccessHash: 4819682371444353606,
|
||||
FileReference: []byte("\x01\x00\x00\x00@a\x9c\xe3J@\x95c\xb4\xed\xae\x9d\xa5\xf7g\x82C6\x18\xc5Q"),
|
||||
ThumbSize: "m",
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"ChatPhoto",
|
||||
wantData["ChatPhoto"],
|
||||
&tg.InputPeerPhotoFileLocation{
|
||||
Big: true,
|
||||
Peer: &tg.InputPeerChannel{
|
||||
ChannelID: 1228418968,
|
||||
AccessHash: -3299551084991488399,
|
||||
},
|
||||
PhotoID: 5291818339590582253,
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"Voice",
|
||||
wantData["Voice"],
|
||||
&tg.InputDocumentFileLocation{
|
||||
ID: 5253930903607972441,
|
||||
AccessHash: -6583080877151517951,
|
||||
FileReference: []byte("\x01\x00\x00\x00Ca\x9c\xec_\x0ey\xfb\xa7\xe5\x8c$\x9eAq\x0f\xdd\xd5\xf9\xfd\xe8"),
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"Audio",
|
||||
wantData["Audio"],
|
||||
&tg.InputDocumentFileLocation{
|
||||
ID: 5366039677566452464,
|
||||
AccessHash: 2905629019683770424,
|
||||
FileReference: []byte("\x01\x00\x00\x00Da\x9c\xedް\xc0Ð\xa4\x1d%<E\x90<\x034ӳ#"),
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"Secure",
|
||||
FileID{
|
||||
Type: Secure,
|
||||
ID: 10,
|
||||
AccessHash: 10,
|
||||
},
|
||||
&tg.InputSecureFileLocation{
|
||||
ID: 10,
|
||||
AccessHash: 10,
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"Encrypted",
|
||||
FileID{
|
||||
Type: Encrypted,
|
||||
ID: 10,
|
||||
AccessHash: 10,
|
||||
},
|
||||
&tg.InputEncryptedFileLocation{
|
||||
ID: 10,
|
||||
AccessHash: 10,
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"PhotoSizeSourceFullLegacy",
|
||||
FileID{
|
||||
Type: Photo,
|
||||
ID: 10,
|
||||
AccessHash: 11,
|
||||
FileReference: []byte{12},
|
||||
PhotoSizeSource: PhotoSizeSource{
|
||||
Type: PhotoSizeSourceFullLegacy,
|
||||
VolumeID: 13,
|
||||
LocalID: 14,
|
||||
Secret: 15,
|
||||
},
|
||||
},
|
||||
&tg.InputPhotoLegacyFileLocation{
|
||||
ID: 10,
|
||||
AccessHash: 11,
|
||||
FileReference: []byte{12},
|
||||
VolumeID: 13,
|
||||
LocalID: 14,
|
||||
Secret: 15,
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
name: "PhotoSizeSourceDialogPhotoBigLegacy",
|
||||
fileID: FileID{
|
||||
Type: ProfilePhoto,
|
||||
PhotoSizeSource: PhotoSizeSource{
|
||||
Type: PhotoSizeSourceDialogPhotoBigLegacy,
|
||||
VolumeID: 13,
|
||||
LocalID: 14,
|
||||
DialogID: constant.MaxTDLibUserID - 1,
|
||||
DialogAccessHash: 15,
|
||||
},
|
||||
},
|
||||
want: &tg.InputPeerPhotoFileLocationLegacy{
|
||||
Big: true,
|
||||
Peer: &tg.InputPeerUser{
|
||||
UserID: constant.MaxTDLibUserID - 1,
|
||||
AccessHash: 15,
|
||||
},
|
||||
VolumeID: 13,
|
||||
LocalID: 14,
|
||||
},
|
||||
wantOk: true,
|
||||
},
|
||||
{
|
||||
name: "PhotoSizeSourceStickerSetThumbnailLegacy",
|
||||
fileID: FileID{
|
||||
Type: Thumbnail,
|
||||
PhotoSizeSource: PhotoSizeSource{
|
||||
Type: PhotoSizeSourceStickerSetThumbnailLegacy,
|
||||
VolumeID: 10,
|
||||
LocalID: 11,
|
||||
StickerSetID: 12,
|
||||
StickerSetAccessHash: 13,
|
||||
},
|
||||
},
|
||||
want: &tg.InputStickerSetThumbLegacy{
|
||||
Stickerset: &tg.InputStickerSetID{
|
||||
ID: 12,
|
||||
AccessHash: 13,
|
||||
},
|
||||
VolumeID: 10,
|
||||
LocalID: 11,
|
||||
},
|
||||
wantOk: true,
|
||||
},
|
||||
{
|
||||
name: "PhotoSizeSourceStickerSetThumbnailLegacy",
|
||||
fileID: FileID{
|
||||
Type: Thumbnail,
|
||||
PhotoSizeSource: PhotoSizeSource{
|
||||
Type: PhotoSizeSourceStickerSetThumbnailVersion,
|
||||
StickerSetID: 12,
|
||||
StickerSetAccessHash: 13,
|
||||
StickerVersion: 1,
|
||||
},
|
||||
},
|
||||
want: &tg.InputStickerSetThumb{
|
||||
Stickerset: &tg.InputStickerSetID{
|
||||
ID: 12,
|
||||
AccessHash: 13,
|
||||
},
|
||||
ThumbVersion: 1,
|
||||
},
|
||||
wantOk: true,
|
||||
},
|
||||
{
|
||||
"PhotoSizeSourceLegacy",
|
||||
FileID{Type: Photo, PhotoSizeSource: PhotoSizeSource{
|
||||
Type: PhotoSizeSourceLegacy,
|
||||
}},
|
||||
nil,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"PhotoSizeSourceStickerSetThumbnail",
|
||||
FileID{Type: Thumbnail, PhotoSizeSource: PhotoSizeSource{
|
||||
Type: PhotoSizeSourceStickerSetThumbnail,
|
||||
}},
|
||||
nil,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"Temp",
|
||||
FileID{Type: Temp},
|
||||
nil,
|
||||
false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
a := require.New(t)
|
||||
got, ok := tt.fileID.AsInputFileLocation()
|
||||
a.Equal(tt.wantOk, ok)
|
||||
a.Equal(tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileID_AsInputWebFileLocation(t *testing.T) {
|
||||
a := require.New(t)
|
||||
fileID := FileID{
|
||||
AccessHash: 10,
|
||||
}
|
||||
|
||||
loc, ok := fileID.AsInputWebFileLocation()
|
||||
a.False(ok)
|
||||
a.Nil(loc)
|
||||
|
||||
fileID.URL = "a"
|
||||
loc, ok = fileID.AsInputWebFileLocation()
|
||||
a.True(ok)
|
||||
a.Equal(&tg.InputWebFileLocation{
|
||||
URL: "a",
|
||||
AccessHash: 10,
|
||||
}, loc)
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
package fileid
|
||||
|
||||
import (
|
||||
"github.com/go-faster/errors"
|
||||
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/bin"
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/constant"
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/tg"
|
||||
)
|
||||
|
||||
// PhotoSizeSource represents photo metadata stored in file_id.
|
||||
type PhotoSizeSource struct {
|
||||
Type PhotoSizeSourceType
|
||||
VolumeID int64
|
||||
LocalID int
|
||||
Secret int64
|
||||
PhotoSize string
|
||||
|
||||
FileType Type
|
||||
ThumbnailType rune
|
||||
|
||||
DialogID constant.TDLibPeerID
|
||||
DialogAccessHash int64
|
||||
|
||||
StickerSetID int64
|
||||
StickerSetAccessHash int64
|
||||
StickerVersion int32
|
||||
}
|
||||
|
||||
func (p *PhotoSizeSource) stickerSet() tg.InputStickerSetClass {
|
||||
return &tg.InputStickerSetID{
|
||||
ID: p.StickerSetID,
|
||||
AccessHash: p.StickerSetAccessHash,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PhotoSizeSource) dialogPeer() tg.InputPeerClass {
|
||||
switch id := p.DialogID; {
|
||||
case id.IsUser():
|
||||
return &tg.InputPeerUser{
|
||||
UserID: id.ToPlain(),
|
||||
AccessHash: p.DialogAccessHash,
|
||||
}
|
||||
case id.IsChat():
|
||||
return &tg.InputPeerChat{
|
||||
ChatID: id.ToPlain(),
|
||||
}
|
||||
case id.IsChannel():
|
||||
return &tg.InputPeerChannel{
|
||||
ChannelID: id.ToPlain(),
|
||||
AccessHash: p.DialogAccessHash,
|
||||
}
|
||||
}
|
||||
return &tg.InputPeerEmpty{}
|
||||
}
|
||||
|
||||
func (p *PhotoSizeSource) readLocalIDVolumeID(b *bin.Buffer) error {
|
||||
{
|
||||
v, err := b.Long()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read volume_id")
|
||||
}
|
||||
p.VolumeID = v
|
||||
}
|
||||
{
|
||||
v, err := b.Int()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read local_id")
|
||||
}
|
||||
p.LocalID = v
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PhotoSizeSource) readDialog(b *bin.Buffer) error {
|
||||
{
|
||||
v, err := b.Long()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read dialog_id")
|
||||
}
|
||||
p.DialogID = constant.TDLibPeerID(v)
|
||||
}
|
||||
{
|
||||
v, err := b.Long()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read dialog_access_hash")
|
||||
}
|
||||
p.DialogAccessHash = v
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PhotoSizeSource) readStickerSet(b *bin.Buffer) error {
|
||||
{
|
||||
v, err := b.Long()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read sticker_set_id")
|
||||
}
|
||||
p.StickerSetID = v
|
||||
}
|
||||
{
|
||||
v, err := b.Long()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read sticker_set_access_hash")
|
||||
}
|
||||
p.StickerSetAccessHash = v
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const latestSubVersion = 34
|
||||
|
||||
func (p *PhotoSizeSource) decode(b *bin.Buffer, subVersion byte) error {
|
||||
if subVersion < 32 {
|
||||
{
|
||||
v, err := b.Long()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read volume_id")
|
||||
}
|
||||
p.VolumeID = v
|
||||
}
|
||||
|
||||
if subVersion < 22 {
|
||||
{
|
||||
v, err := b.Long()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read secret")
|
||||
}
|
||||
p.Secret = v
|
||||
}
|
||||
{
|
||||
v, err := b.Int()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read local_id")
|
||||
}
|
||||
p.LocalID = v
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var photoSizeType PhotoSizeSourceType
|
||||
if subVersion >= 4 {
|
||||
v, err := b.Int()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read photo_size_type")
|
||||
}
|
||||
photoSizeType = PhotoSizeSourceType(v)
|
||||
}
|
||||
if photoSizeType < 0 || photoSizeType >= lastPhotoSizeSourceType {
|
||||
return errors.Errorf("unknown photo size source type %d", photoSizeType)
|
||||
}
|
||||
p.Type = photoSizeType
|
||||
|
||||
switch photoSizeType {
|
||||
case PhotoSizeSourceLegacy:
|
||||
v, err := b.Long()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read secret")
|
||||
}
|
||||
p.Secret = v
|
||||
case PhotoSizeSourceThumbnail:
|
||||
{
|
||||
v, err := b.Uint32()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read file_type")
|
||||
}
|
||||
p.FileType = Type(v)
|
||||
}
|
||||
{
|
||||
v, err := b.Int32()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read thumbnail_type")
|
||||
}
|
||||
p.ThumbnailType = v
|
||||
}
|
||||
case PhotoSizeSourceDialogPhotoBig, PhotoSizeSourceDialogPhotoSmall:
|
||||
if err := p.readDialog(b); err != nil {
|
||||
return errors.Wrap(err, "read dialog")
|
||||
}
|
||||
case PhotoSizeSourceStickerSetThumbnail:
|
||||
if err := p.readStickerSet(b); err != nil {
|
||||
return errors.Wrap(err, "read sticker_set")
|
||||
}
|
||||
case PhotoSizeSourceFullLegacy:
|
||||
{
|
||||
v, err := b.Long()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read volume_id")
|
||||
}
|
||||
p.VolumeID = v
|
||||
}
|
||||
{
|
||||
v, err := b.Long()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read secret")
|
||||
}
|
||||
p.Secret = v
|
||||
}
|
||||
{
|
||||
v, err := b.Int()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read local_id")
|
||||
}
|
||||
p.LocalID = v
|
||||
}
|
||||
case PhotoSizeSourceDialogPhotoBigLegacy, PhotoSizeSourceDialogPhotoSmallLegacy:
|
||||
if err := p.readDialog(b); err != nil {
|
||||
return errors.Wrap(err, "read dialog")
|
||||
}
|
||||
if err := p.readLocalIDVolumeID(b); err != nil {
|
||||
return errors.Wrap(err, "read legacy photo")
|
||||
}
|
||||
|
||||
case PhotoSizeSourceStickerSetThumbnailLegacy:
|
||||
if err := p.readStickerSet(b); err != nil {
|
||||
return errors.Wrap(err, "read sticker_set")
|
||||
}
|
||||
if err := p.readLocalIDVolumeID(b); err != nil {
|
||||
return errors.Wrap(err, "read legacy photo")
|
||||
}
|
||||
|
||||
case PhotoSizeSourceStickerSetThumbnailVersion:
|
||||
if err := p.readStickerSet(b); err != nil {
|
||||
return errors.Wrap(err, "read sticker_set")
|
||||
}
|
||||
{
|
||||
v, err := b.Int32()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read sticker_version")
|
||||
}
|
||||
p.StickerVersion = v
|
||||
}
|
||||
}
|
||||
|
||||
if subVersion < 32 && subVersion >= 22 {
|
||||
v, err := b.Int()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read local_id")
|
||||
}
|
||||
p.LocalID = v
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PhotoSizeSource) writeLocalIDVolumeID(b *bin.Buffer) {
|
||||
b.PutLong(p.VolumeID)
|
||||
b.PutInt(p.LocalID)
|
||||
}
|
||||
|
||||
func (p *PhotoSizeSource) writeDialog(b *bin.Buffer) {
|
||||
b.PutLong(int64(p.DialogID))
|
||||
b.PutLong(p.DialogAccessHash)
|
||||
}
|
||||
|
||||
func (p *PhotoSizeSource) writeStickerSet(b *bin.Buffer) {
|
||||
b.PutLong(p.StickerSetID)
|
||||
b.PutLong(p.StickerSetAccessHash)
|
||||
}
|
||||
|
||||
func (p *PhotoSizeSource) encode(b *bin.Buffer) {
|
||||
b.PutInt(int(p.Type))
|
||||
switch p.Type {
|
||||
case PhotoSizeSourceLegacy:
|
||||
b.PutLong(p.Secret)
|
||||
case PhotoSizeSourceThumbnail:
|
||||
b.PutUint32(uint32(p.FileType))
|
||||
b.PutInt32(p.ThumbnailType)
|
||||
case PhotoSizeSourceDialogPhotoBig, PhotoSizeSourceDialogPhotoSmall:
|
||||
p.writeDialog(b)
|
||||
case PhotoSizeSourceStickerSetThumbnail:
|
||||
p.writeStickerSet(b)
|
||||
case PhotoSizeSourceFullLegacy:
|
||||
b.PutLong(p.VolumeID)
|
||||
b.PutLong(p.Secret)
|
||||
b.PutInt(p.LocalID)
|
||||
case PhotoSizeSourceDialogPhotoBigLegacy, PhotoSizeSourceDialogPhotoSmallLegacy:
|
||||
p.writeDialog(b)
|
||||
p.writeLocalIDVolumeID(b)
|
||||
case PhotoSizeSourceStickerSetThumbnailLegacy:
|
||||
p.writeStickerSet(b)
|
||||
p.writeLocalIDVolumeID(b)
|
||||
case PhotoSizeSourceStickerSetThumbnailVersion:
|
||||
p.writeStickerSet(b)
|
||||
b.PutInt32(p.StickerVersion)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package fileid
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/bin"
|
||||
)
|
||||
|
||||
func TestPhotoSizeSourceEncodeDecode(t *testing.T) {
|
||||
tests := []PhotoSizeSource{
|
||||
{
|
||||
Type: PhotoSizeSourceLegacy,
|
||||
Secret: 10,
|
||||
},
|
||||
{
|
||||
Type: PhotoSizeSourceStickerSetThumbnail,
|
||||
StickerSetID: 12,
|
||||
StickerSetAccessHash: 13,
|
||||
},
|
||||
{
|
||||
Type: PhotoSizeSourceFullLegacy,
|
||||
VolumeID: 13,
|
||||
LocalID: 14,
|
||||
Secret: 15,
|
||||
},
|
||||
{
|
||||
Type: PhotoSizeSourceDialogPhotoBigLegacy,
|
||||
VolumeID: 13,
|
||||
LocalID: 14,
|
||||
DialogID: -1001228418968,
|
||||
DialogAccessHash: 15,
|
||||
},
|
||||
{
|
||||
Type: PhotoSizeSourceStickerSetThumbnailLegacy,
|
||||
VolumeID: 10,
|
||||
LocalID: 11,
|
||||
StickerSetID: 12,
|
||||
StickerSetAccessHash: 13,
|
||||
},
|
||||
{
|
||||
Type: PhotoSizeSourceStickerSetThumbnailVersion,
|
||||
StickerSetID: 12,
|
||||
StickerSetAccessHash: 13,
|
||||
StickerVersion: 1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.Type.String(), func(t *testing.T) {
|
||||
a := require.New(t)
|
||||
var b bin.Buffer
|
||||
|
||||
tt.encode(&b)
|
||||
var got PhotoSizeSource
|
||||
a.NoError(got.decode(&b, latestSubVersion))
|
||||
a.Equal(tt, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package fileid
|
||||
|
||||
//go:generate go run -modfile=../_tools/go.mod golang.org/x/tools/cmd/stringer -type=PhotoSizeSourceType
|
||||
|
||||
// PhotoSizeSourceType represents photo_size_source type.
|
||||
type PhotoSizeSourceType int
|
||||
|
||||
const (
|
||||
// PhotoSizeSourceLegacy is Legacy type.
|
||||
PhotoSizeSourceLegacy PhotoSizeSourceType = iota
|
||||
// PhotoSizeSourceThumbnail is Thumbnail type.
|
||||
PhotoSizeSourceThumbnail
|
||||
// PhotoSizeSourceDialogPhotoSmall is DialogPhotoSmall type.
|
||||
PhotoSizeSourceDialogPhotoSmall
|
||||
// PhotoSizeSourceDialogPhotoBig is DialogPhotoBig type.
|
||||
PhotoSizeSourceDialogPhotoBig
|
||||
// PhotoSizeSourceStickerSetThumbnail is StickerSetThumbnail type.
|
||||
PhotoSizeSourceStickerSetThumbnail
|
||||
// PhotoSizeSourceFullLegacy is FullLegacy type.
|
||||
PhotoSizeSourceFullLegacy
|
||||
// PhotoSizeSourceDialogPhotoSmallLegacy is DialogPhotoSmallLegacy type.
|
||||
PhotoSizeSourceDialogPhotoSmallLegacy
|
||||
// PhotoSizeSourceDialogPhotoBigLegacy is DialogPhotoBigLegacy type.
|
||||
PhotoSizeSourceDialogPhotoBigLegacy
|
||||
// PhotoSizeSourceStickerSetThumbnailLegacy is StickerSetThumbnailLegacy type.
|
||||
PhotoSizeSourceStickerSetThumbnailLegacy
|
||||
// PhotoSizeSourceStickerSetThumbnailVersion is StickerSetThumbnailVersion type.
|
||||
PhotoSizeSourceStickerSetThumbnailVersion
|
||||
lastPhotoSizeSourceType
|
||||
)
|
||||
@@ -0,0 +1,13 @@
|
||||
package fileid
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPhotoSizeSourceType_String(t *testing.T) {
|
||||
for i := PhotoSizeSourceLegacy; i <= lastPhotoSizeSourceType+1; i++ {
|
||||
require.NotEmpty(t, i.String())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// Code generated by "stringer -type=PhotoSizeSourceType"; DO NOT EDIT.
|
||||
|
||||
package fileid
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[PhotoSizeSourceLegacy-0]
|
||||
_ = x[PhotoSizeSourceThumbnail-1]
|
||||
_ = x[PhotoSizeSourceDialogPhotoSmall-2]
|
||||
_ = x[PhotoSizeSourceDialogPhotoBig-3]
|
||||
_ = x[PhotoSizeSourceStickerSetThumbnail-4]
|
||||
_ = x[PhotoSizeSourceFullLegacy-5]
|
||||
_ = x[PhotoSizeSourceDialogPhotoSmallLegacy-6]
|
||||
_ = x[PhotoSizeSourceDialogPhotoBigLegacy-7]
|
||||
_ = x[PhotoSizeSourceStickerSetThumbnailLegacy-8]
|
||||
_ = x[PhotoSizeSourceStickerSetThumbnailVersion-9]
|
||||
_ = x[lastPhotoSizeSourceType-10]
|
||||
}
|
||||
|
||||
const _PhotoSizeSourceType_name = "PhotoSizeSourceLegacyPhotoSizeSourceThumbnailPhotoSizeSourceDialogPhotoSmallPhotoSizeSourceDialogPhotoBigPhotoSizeSourceStickerSetThumbnailPhotoSizeSourceFullLegacyPhotoSizeSourceDialogPhotoSmallLegacyPhotoSizeSourceDialogPhotoBigLegacyPhotoSizeSourceStickerSetThumbnailLegacyPhotoSizeSourceStickerSetThumbnailVersionlastPhotoSizeSourceType"
|
||||
|
||||
var _PhotoSizeSourceType_index = [...]uint16{0, 21, 45, 76, 105, 139, 164, 201, 236, 276, 317, 340}
|
||||
|
||||
func (i PhotoSizeSourceType) String() string {
|
||||
if i < 0 || i >= PhotoSizeSourceType(len(_PhotoSizeSourceType_index)-1) {
|
||||
return "PhotoSizeSourceType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _PhotoSizeSourceType_name[_PhotoSizeSourceType_index[i]:_PhotoSizeSourceType_index[i+1]]
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package fileid
|
||||
|
||||
import "bytes"
|
||||
|
||||
func rleEncode(s []byte) (r []byte) {
|
||||
var count byte
|
||||
for _, cur := range s {
|
||||
if cur == 0 {
|
||||
count++
|
||||
continue
|
||||
}
|
||||
|
||||
if count > 0 {
|
||||
r = append(r, 0, count)
|
||||
count = 0
|
||||
}
|
||||
r = append(r, cur)
|
||||
}
|
||||
if count > 0 {
|
||||
r = append(r, 0, count)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func rleDecode(s []byte) (r []byte) {
|
||||
var last []byte
|
||||
for _, cur := range s {
|
||||
if string(last) == string(rune(0)) {
|
||||
r = append(r, bytes.Repeat(last, int(cur))...)
|
||||
last = nil
|
||||
} else {
|
||||
r = append(r, last...)
|
||||
last = []byte{cur}
|
||||
}
|
||||
}
|
||||
r = append(r, last...)
|
||||
return r
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package fileid
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_rleEncode(t *testing.T) {
|
||||
for name, input := range testData {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
a := require.New(t)
|
||||
original, err := base64Decode(input)
|
||||
a.NoError(err)
|
||||
|
||||
decoded := rleDecode(original)
|
||||
a.Equal(original, rleEncode(decoded))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_rleDecode(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want []byte
|
||||
}{
|
||||
{
|
||||
"Valid",
|
||||
testData["Sticker"],
|
||||
[]uint8{
|
||||
0x08, 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0x00, 0x19, 0x01, 0x00, 0x00, 0x00, 0x3a, 0x61, 0x99,
|
||||
0x43, 0x10, 0x70, 0xa6, 0x69, 0x32, 0xab, 0x52, 0x10, 0x88, 0x8f, 0x10, 0x0f, 0xb4, 0xfb, 0x63,
|
||||
0x57, 0x1e, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x87, 0x57, 0x8f, 0x1e, 0x9d, 0x18, 0xbc, 0x87,
|
||||
0xc1, 0x75, 0x4d, 0x70, 0x22, 0x04,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
a := require.New(t)
|
||||
decoded, err := base64.URLEncoding.DecodeString(tt.input)
|
||||
a.NoError(err)
|
||||
|
||||
a.Equal(tt.want, rleDecode(decoded))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package fileid
|
||||
|
||||
//go:generate go run -modfile=../_tools/go.mod golang.org/x/tools/cmd/stringer -type=Type
|
||||
|
||||
// Type represents file_id type.
|
||||
type Type int
|
||||
|
||||
const (
|
||||
// Thumbnail is Thumbnail file type.
|
||||
Thumbnail Type = iota
|
||||
// ProfilePhoto is ProfilePhoto file type.
|
||||
ProfilePhoto
|
||||
// Photo is Photo file type.
|
||||
Photo
|
||||
// Voice is Voice file type.
|
||||
Voice
|
||||
// Video is Video file type.
|
||||
Video
|
||||
// Document is Document file type.
|
||||
Document
|
||||
// Encrypted is Encrypted file type.
|
||||
Encrypted
|
||||
// Temp is Temp file type.
|
||||
Temp
|
||||
// Sticker is Sticker file type.
|
||||
Sticker
|
||||
// Audio is Audio file type.
|
||||
Audio
|
||||
// Animation is Animation file type.
|
||||
Animation
|
||||
// EncryptedThumbnail is EncryptedThumbnail file type.
|
||||
EncryptedThumbnail
|
||||
// Wallpaper is Wallpaper file type.
|
||||
Wallpaper
|
||||
// VideoNote is VideoNote file type.
|
||||
VideoNote
|
||||
// SecureRaw is SecureRaw file type.
|
||||
SecureRaw
|
||||
// Secure is Secure file type.
|
||||
Secure
|
||||
// Background is Background file type.
|
||||
Background
|
||||
// DocumentAsFile is DocumentAsFile file type.
|
||||
DocumentAsFile
|
||||
lastType
|
||||
)
|
||||
@@ -0,0 +1,41 @@
|
||||
// Code generated by "stringer -type=Type"; DO NOT EDIT.
|
||||
|
||||
package fileid
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[Thumbnail-0]
|
||||
_ = x[ProfilePhoto-1]
|
||||
_ = x[Photo-2]
|
||||
_ = x[Voice-3]
|
||||
_ = x[Video-4]
|
||||
_ = x[Document-5]
|
||||
_ = x[Encrypted-6]
|
||||
_ = x[Temp-7]
|
||||
_ = x[Sticker-8]
|
||||
_ = x[Audio-9]
|
||||
_ = x[Animation-10]
|
||||
_ = x[EncryptedThumbnail-11]
|
||||
_ = x[Wallpaper-12]
|
||||
_ = x[VideoNote-13]
|
||||
_ = x[SecureRaw-14]
|
||||
_ = x[Secure-15]
|
||||
_ = x[Background-16]
|
||||
_ = x[DocumentAsFile-17]
|
||||
_ = x[lastType-18]
|
||||
}
|
||||
|
||||
const _Type_name = "ThumbnailProfilePhotoPhotoVoiceVideoDocumentEncryptedTempStickerAudioAnimationEncryptedThumbnailWallpaperVideoNoteSecureRawSecureBackgroundDocumentAsFilelastType"
|
||||
|
||||
var _Type_index = [...]uint8{0, 9, 21, 26, 31, 36, 44, 53, 57, 64, 69, 78, 96, 105, 114, 123, 129, 139, 153, 161}
|
||||
|
||||
func (i Type) String() string {
|
||||
if i < 0 || i >= Type(len(_Type_index)-1) {
|
||||
return "Type(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _Type_name[_Type_index[i]:_Type_index[i+1]]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package fileid
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestType_String(t *testing.T) {
|
||||
for i := Thumbnail; i <= lastType+1; i++ {
|
||||
require.NotEmpty(t, i.String())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user