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
+45
View File
@@ -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)
}
+20
View File
@@ -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)
}
})
}
+181
View File
@@ -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)
}
})
}
}
+87
View File
@@ -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)
}
}
+20
View File
@@ -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)
}
+24
View File
@@ -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)
})
}
}
+131
View File
@@ -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)
}
+47
View File
@@ -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)
})
}
+2
View File
@@ -0,0 +1,2 @@
// Package fileid contains BotAPI and tdlib file_id decoder.
package fileid
+81
View File
@@ -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,
},
}
}
+86
View File
@@ -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))
})
}
}
+105
View File
@@ -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
}
+291
View File
@@ -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)
}
+288
View File
@@ -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)
}
}
+61
View File
@@ -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)
})
}
}
+30
View File
@@ -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]]
}
+39
View File
@@ -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
}
+50
View File
@@ -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))
})
}
}
+46
View File
@@ -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
)
+41
View File
@@ -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]]
}
+13
View File
@@ -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())
}
}