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
+401
View File
@@ -0,0 +1,401 @@
package tljson
import (
"encoding/base64"
"strconv"
"github.com/go-faster/errors"
"github.com/go-faster/jx"
"go.mau.fi/mautrix-telegram/pkg/gotd/tg"
)
// EmojiSound represents emoji sound file location.
//
// See https://core.telegram.org/api/animated-emojis#emojis-with-sounds.
type EmojiSound struct {
ID int64 `json:"id"`
AccessHash int64 `json:"access_hash"`
FileReference []byte `json:"file_reference_base64"`
}
// DecodeJSON decodes EmojiSound.
func (e *EmojiSound) DecodeJSON(d *jx.Decoder) error {
return d.ObjBytes(func(d *jx.Decoder, key []byte) error {
switch string(key) {
case "id":
v, err := d.Str()
if err != nil {
return errors.Wrap(err, "decode id")
}
id, err := strconv.ParseInt(v, 10, 64)
if err != nil {
return errors.Wrap(err, "parse id")
}
e.ID = id
case "access_hash":
v, err := d.Str()
if err != nil {
return errors.Wrap(err, "decode access_hash")
}
accessHash, err := strconv.ParseInt(v, 10, 64)
if err != nil {
return errors.Wrap(err, "parse access_hash")
}
e.AccessHash = accessHash
case "file_reference_base64":
encoded, err := d.StrBytes()
if err != nil {
return errors.Wrap(err, "decode file_reference_base64")
}
encoding := base64.RawURLEncoding
e.FileReference = make([]byte, encoding.DecodedLen(len(encoded)))
n, err := encoding.Decode(e.FileReference, encoded)
if err != nil {
return errors.Wrap(err, "un-base64 file_reference_base64")
}
e.FileReference = e.FileReference[:n]
default:
return d.Skip()
}
return nil
})
}
// EmojiSendDiceSuccess represents the winning dice value and the final frame of the animated sticker.
//
// See https://core.telegram.org/api/dice.
type EmojiSendDiceSuccess struct {
Value int `json:"value"`
FrameStart int `json:"frame_start"`
}
// DecodeJSON decodes EmojiSendDiceSuccess.
func (e *EmojiSendDiceSuccess) DecodeJSON(d *jx.Decoder) error {
return d.ObjBytes(func(d *jx.Decoder, key []byte) error {
switch string(key) {
case "value":
v, err := d.Int()
if err != nil {
return errors.Wrap(err, "decode value")
}
e.Value = v
case "frame_start":
v, err := d.Int()
if err != nil {
return errors.Wrap(err, "decode frame_start")
}
e.FrameStart = v
default:
return d.Skip()
}
return nil
})
}
// RoundVideoEncoding represents a set of recommended codec parameters for round videos.
type RoundVideoEncoding struct {
Diameter int `json:"diameter"`
VideoBitrate int `json:"video_bitrate"`
AudioBitrate int `json:"audio_bitrate"`
MaxSize int `json:"max_size"`
}
// DecodeJSON decodes RoundVideoEncoding.
func (e *RoundVideoEncoding) DecodeJSON(d *jx.Decoder) error {
return d.ObjBytes(func(d *jx.Decoder, key []byte) error {
switch string(key) {
case "diameter":
v, err := d.Int()
if err != nil {
return errors.Wrap(err, "decode diameter")
}
e.Diameter = v
case "video_bitrate":
v, err := d.Int()
if err != nil {
return errors.Wrap(err, "decode video_bitrate")
}
e.VideoBitrate = v
case "audio_bitrate":
v, err := d.Int()
if err != nil {
return errors.Wrap(err, "decode audio_bitrate")
}
e.AudioBitrate = v
case "max_size":
v, err := d.Int()
if err != nil {
return errors.Wrap(err, "decode max_size")
}
e.MaxSize = v
default:
return d.Skip()
}
return nil
})
}
// AppConfig represents app config structure.
//
// See https://core.telegram.org/api/config#client-configuration.
type AppConfig struct {
Test int `json:"test"`
// Animated emojis and animated dice should be scaled by this factor before being shown to the user.
EmojiesAnimatedZoom float64 `json:"emojies_animated_zoom"`
// A list of supported animated dice stickers.
EmojiesSendDice []string `json:"emojies_send_dice"`
// For animated dice emojis other than the basic 🎲, indicates the winning dice value
// and the final frame of the animated sticker, at which to show the fireworks.
EmojiesSendDiceSuccess map[string]EmojiSendDiceSuccess `json:"emojies_send_dice_success"`
// A map of soundbites to be played when the user clicks on the specified animated emoji.
//
// The file reference field should be base64-decoded before downloading the file.
EmojiesSounds map[string]EmojiSound `json:"emojies_sounds"`
// Specifies the name of the service providing GIF search through gif_search_username.
GIFSearchBranding string `json:"gif_search_branding"`
// Specifies a list of emojies that should be suggested as search term in a bar above the GIF search box.
GIFSearchEmojies []string `json:"gif_search_emojies"`
// Specifies that the app should not display local sticker suggestions
// for emojis at all and just use the result of messages.getStickers.
StickersEmojiSuggestOnlyAPI bool `json:"stickers_emoji_suggest_only_api"`
// Specifies the validity period of the local cache of messages.getStickers,
// also relevant when generating the pagination hash when invoking the method.
StickersEmojiCacheTime int `json:"stickers_emoji_cache_time"`
GroupCallVideoParticipantsMax int `json:"groupcall_video_participants_max"`
YoutubePIP string `json:"youtube_pip"`
// Whether the Settings->Devices menu should show an option to scan a QR login code.
QRLoginCamera bool `json:"qr_login_camera"`
// Whether the login screen should show a QR code login option, possibly
// as default login method ("disabled", "primary" or "secondary")
QRLoginCode string `json:"qr_login_code"`
// Whether clients should show an option for managing dialog filters AKA folders.
DialogFiltersEnabled bool `json:"dialog_filters_enabled"`
// Whether clients should actively show a tooltip, inviting the user to configure dialog filters AKA folders.
//
// Typically, this happens when the chat list is long enough to start getting cluttered.
DialogFiltersTooltip bool `json:"dialog_filters_tooltip"`
// Whether clients can invoke account.setGlobalPrivacySettings
// with globalPrivacySettings.archive_and_mute_new_noncontact_peers = boolTrue,
// to automatically archive and mute new incoming chats from non-contacts.
AutoArchiveSettingAvailable bool `json:"autoarchive_setting_available"`
// Contains a list of suggestions that should be actively shown as a tooltip to the user.
PendingSuggestions []string `json:"pending_suggestions"`
// Autologin token.
//
// See https://core.telegram.org/api/url-authorization#link-url-authorization.
AutologinToken string `json:"autologin_token"`
// A list of Telegram domains that support automatic login with no user confirmation.
//
// See https://core.telegram.org/api/url-authorization#link-url-authorization.
AutologinDomains []string `json:"autologin_domains"`
// A list of domains that support automatic login with manual user confirmation.
//
// See https://core.telegram.org/api/url-authorization#link-url-authorization.
URLAuthDomains []string `json:"url_auth_domains"`
// Contains a set of recommended codec parameters for round videos.
RoundVideoEncoding RoundVideoEncoding `json:"round_video_encoding"`
// To protect user privacy, read receipts are only stored for
// chat_read_mark_expire_period seconds after the message was sent.
ChatReadMarkExpirePeriod int `json:"chat_read_mark_expire_period"`
// Per-user read receipts, fetchable using messages.getMessageReadParticipants
// will be available in groups with less than chat_read_mark_size_threshold participants.
ChatReadMarkSizeThreshold int `json:"chat_read_mark_size_threshold"`
// Unparsed is map of unknown unparsed fields.
Unparsed map[string]tg.JSONValueClass
}
func decodeStringArray(d *jx.Decoder, array *[]string) error {
return d.Arr(func(d *jx.Decoder) error {
v, err := d.Str()
if err != nil {
return errors.Wrapf(err, "decode %d", len(*array))
}
*array = append(*array, v)
return nil
})
}
// DecodeJSON decodes AppConfig.
func (e *AppConfig) DecodeJSON(d *jx.Decoder) error {
// TODO(tdakkota): use schema-based parser
return d.ObjBytes(func(d *jx.Decoder, key []byte) error {
switch string(key) {
case "test":
v, err := d.Int()
if err != nil {
return errors.Wrap(err, "decode test")
}
e.Test = v
case "emojies_animated_zoom":
v, err := d.Float64()
if err != nil {
return errors.Wrap(err, "decode emojies_animated_zoom")
}
e.EmojiesAnimatedZoom = v
case "emojies_send_dice":
if err := decodeStringArray(d, &e.EmojiesSendDice); err != nil {
return errors.Wrap(err, "decode emojies_send_dice")
}
case "emojies_send_dice_success":
if e.EmojiesSendDiceSuccess == nil {
e.EmojiesSendDiceSuccess = map[string]EmojiSendDiceSuccess{}
}
if err := d.Obj(func(d *jx.Decoder, key string) error {
var v EmojiSendDiceSuccess
if err := v.DecodeJSON(d); err != nil {
return errors.Wrapf(err, "decode %q", key)
}
e.EmojiesSendDiceSuccess[key] = v
return nil
}); err != nil {
return errors.Wrap(err, "decode emojies_send_dice_success")
}
case "emojies_sounds":
if e.EmojiesSounds == nil {
e.EmojiesSounds = map[string]EmojiSound{}
}
if err := d.Obj(func(d *jx.Decoder, key string) error {
var v EmojiSound
if err := v.DecodeJSON(d); err != nil {
return errors.Wrapf(err, "decode %q", key)
}
e.EmojiesSounds[key] = v
return nil
}); err != nil {
return errors.Wrap(err, "decode emojies_sounds")
}
case "gif_search_branding":
v, err := d.Str()
if err != nil {
return errors.Wrap(err, "decode gif_search_branding")
}
e.GIFSearchBranding = v
case "gif_search_emojies":
if err := decodeStringArray(d, &e.GIFSearchEmojies); err != nil {
return errors.Wrap(err, "decode gif_search_emojies")
}
case "stickers_emoji_suggest_only_api":
v, err := d.Bool()
if err != nil {
return errors.Wrap(err, "decode stickers_emoji_suggest_only_api")
}
e.StickersEmojiSuggestOnlyAPI = v
case "stickers_emoji_cache_time":
v, err := d.Int()
if err != nil {
return errors.Wrap(err, "decode stickers_emoji_cache_time")
}
e.StickersEmojiCacheTime = v
case "groupcall_video_participants_max":
v, err := d.Int()
if err != nil {
return errors.Wrap(err, "decode groupcall_video_participants_max")
}
e.GroupCallVideoParticipantsMax = v
case "youtube_pip":
v, err := d.Str()
if err != nil {
return errors.Wrap(err, "decode youtube_pip")
}
e.YoutubePIP = v
case "qr_login_camera":
v, err := d.Bool()
if err != nil {
return errors.Wrap(err, "decode qr_login_camera")
}
e.QRLoginCamera = v
case "qr_login_code":
v, err := d.Str()
if err != nil {
return errors.Wrap(err, "decode qr_login_code")
}
e.QRLoginCode = v
case "dialog_filters_enabled":
v, err := d.Bool()
if err != nil {
return errors.Wrap(err, "decode dialog_filters_enabled")
}
e.DialogFiltersEnabled = v
case "dialog_filters_tooltip":
v, err := d.Bool()
if err != nil {
return errors.Wrap(err, "decode dialog_filters_tooltip")
}
e.DialogFiltersTooltip = v
case "autoarchive_setting_available":
v, err := d.Bool()
if err != nil {
return errors.Wrap(err, "decode autoarchive_setting_available")
}
e.AutoArchiveSettingAvailable = v
case "pending_suggestions":
if err := decodeStringArray(d, &e.PendingSuggestions); err != nil {
return errors.Wrap(err, "decode pending_suggestions")
}
case "autologin_token":
v, err := d.Str()
if err != nil {
return errors.Wrap(err, "decode autologin_token")
}
e.AutologinToken = v
case "autologin_domains":
if err := decodeStringArray(d, &e.AutologinDomains); err != nil {
return errors.Wrap(err, "decode autologin_domains")
}
case "url_auth_domains":
if err := decodeStringArray(d, &e.URLAuthDomains); err != nil {
return errors.Wrap(err, "decode url_auth_domains")
}
case "round_video_encoding":
if err := e.RoundVideoEncoding.DecodeJSON(d); err != nil {
return errors.Wrap(err, "decode round_video_encoding")
}
case "chat_read_mark_expire_period":
v, err := d.Int()
if err != nil {
return errors.Wrap(err, "decode chat_read_mark_expire_period")
}
e.ChatReadMarkExpirePeriod = v
case "chat_read_mark_size_threshold":
v, err := d.Int()
if err != nil {
return errors.Wrap(err, "decode chat_read_mark_size_threshold")
}
e.ChatReadMarkSizeThreshold = v
default:
if e.Unparsed == nil {
e.Unparsed = map[string]tg.JSONValueClass{}
}
v, err := Decode(d)
if err != nil {
return errors.Wrapf(err, "decode %q", key)
}
e.Unparsed[string(key)] = v
return nil
}
return nil
})
}
// DecodeJSONValue decodes AppConfig from tg.JSONValueClass.
func (e *AppConfig) DecodeJSONValue(val tg.JSONValueClass) error {
v, ok := val.(*tg.JSONObject)
if !ok {
return errors.Errorf("unexpected type %T", val)
}
// TODO(tdakkota): decode directly
encoder := jx.GetEncoder()
Encode(v, encoder)
return e.DecodeJSON(jx.DecodeBytes(encoder.Bytes()))
}
+322
View File
@@ -0,0 +1,322 @@
package tljson
import (
"testing"
"github.com/go-faster/jx"
"github.com/stretchr/testify/require"
"go.mau.fi/mautrix-telegram/pkg/gotd/tg"
)
const example = `{
"test": 1,
"emojies_animated_zoom": 0.625,
"emojies_send_dice": [
"\ud83c\udfb2",
"\ud83c\udfaf",
"\ud83c\udfc0",
"\u26bd",
"\u26bd\ufe0f",
"\ud83c\udfb0",
"\ud83c\udfb3"
],
"emojies_send_dice_success": {
"\ud83c\udfaf": {
"value": 6,
"frame_start": 62
},
"\ud83c\udfc0": {
"value": 5,
"frame_start": 110
},
"\u26bd": {
"value": 5,
"frame_start": 110
},
"\u26bd\ufe0f": {
"value": 5,
"frame_start": 110
},
"\ud83c\udfb0": {
"value": 64,
"frame_start": 110
},
"\ud83c\udfb3": {
"value": 6,
"frame_start": 110
}
},
"emojies_sounds": {
"\ud83c\udf83": {
"id": "4956223179606458539",
"access_hash": "-2107001400913062971",
"file_reference_base64": "AGFhvoKbftK5O9K9RpgN1ZtgSzWy"
},
"\u26b0": {
"id": "4956223179606458540",
"access_hash": "-1498869544183595185",
"file_reference_base64": "AGFhvoJIm8Uz0qSMIdm3AsKlK7wJ"
},
"\ud83e\udddf\u200d\u2642": {
"id": "4960929110848176331",
"access_hash": "3986395821757915468",
"file_reference_base64": "AGFhvoLtXSSIclmvfg6ePz3KsHQF"
},
"\ud83e\udddf": {
"id": "4960929110848176332",
"access_hash": "-8929417974289765626",
"file_reference_base64": "AGFhvoImaz5Umt4GvMUD5nocIu0W"
},
"\ud83e\udddf\u200d\u2640": {
"id": "4960929110848176333",
"access_hash": "9161696144162881753",
"file_reference_base64": "AGFhvoIm1QZsb48xlpRfh4Mq7EMG"
},
"\ud83c\udf51": {
"id": "4963180910661861548",
"access_hash": "-7431729439735063448",
"file_reference_base64": "AGFhvoKLrwl_WKr5LR0Jjs7o3RyT"
},
"\ud83c\udf8a": {
"id": "5094064004578410732",
"access_hash": "8518192996098758509",
"file_reference_base64": "AGFhvoKMNffRV2J3vKED0O6d8e42"
},
"\ud83c\udf84": {
"id": "5094064004578410733",
"access_hash": "-4142643820629256996",
"file_reference_base64": "AGFhvoJ1ulPBbXEURlTZWwJFx6xZ"
},
"\ud83e\uddbe": {
"id": "5094064004578410734",
"access_hash": "-8934384022571962340",
"file_reference_base64": "AGFhvoL4zdMRmYv9z3L8KPaX4JQL"
}
},
"gif_search_branding": "tenor",
"gif_search_emojies": [
"\ud83d\udc4d",
"\ud83d\ude18",
"\ud83d\ude0d",
"\ud83d\ude21",
"\ud83e\udd73",
"\ud83d\ude02",
"\ud83d\ude2e",
"\ud83d\ude44",
"\ud83d\ude0e",
"\ud83d\udc4e"
],
"stickers_emoji_suggest_only_api": false,
"stickers_emoji_cache_time": 86400,
"qr_login_camera": false,
"qr_login_code": "disabled",
"dialog_filters_enabled": true,
"dialog_filters_tooltip": false,
"autoarchive_setting_available": false,
"pending_suggestions": [
"AUTOARCHIVE_POPULAR",
"VALIDATE_PASSWORD",
"VALIDATE_PHONE_NUMBER",
"NEWCOMER_TICKS"
],
"autologin_token": "string",
"autologin_domains": [
"instantview.telegram.org",
"translations.telegram.org",
"contest.dev",
"contest.com",
"bugs.telegram.org",
"suggestions.telegram.org",
"themes.telegram.org"
],
"youtube_pip": "abc",
"groupcall_video_participants_max": 1234,
"url_auth_domains": [
"somedomain.telegram.org"
],
"round_video_encoding": {
"diameter": 384,
"video_bitrate": 1000,
"audio_bitrate": 64,
"max_size": 12582912
},
"chat_read_mark_size_threshold": 50,
"chat_read_mark_expire_period": 604800,
"unknown": null
}`
func TestDecodeAppConfig(t *testing.T) {
a := require.New(t)
var appConfig AppConfig
obj, err := Decode(jx.DecodeStr(example))
a.NoError(err)
a.NoError(appConfig.DecodeJSONValue(obj))
a.Equal(AppConfig{
Test: 1,
EmojiesAnimatedZoom: 0.625000,
EmojiesSendDice: []string{
"🎲",
"🎯",
"🏀",
"⚽",
"⚽️",
"🎰",
"🎳",
},
EmojiesSendDiceSuccess: map[string]EmojiSendDiceSuccess{
"⚽": {
Value: 5,
FrameStart: 110,
},
"⚽️": {
Value: 5,
FrameStart: 110,
},
"🎯": {
Value: 6,
FrameStart: 62,
},
"🎰": {
Value: 64,
FrameStart: 110,
},
"🎳": {
Value: 6,
FrameStart: 110,
},
"🏀": {
Value: 5,
FrameStart: 110,
},
},
EmojiesSounds: map[string]EmojiSound{
"⚰": {
ID: 4956223179606458540,
AccessHash: -1498869544183595185,
FileReference: []uint8{
0x00, 0x61, 0x61, 0xbe, 0x82, 0x48, 0x9b, 0xc5, 0x33, 0xd2, 0xa4, 0x8c, 0x21, 0xd9, 0xb7, 0x02,
0xc2, 0xa5, 0x2b, 0xbc, 0x09,
},
},
"🍑": {
ID: 4963180910661861548,
AccessHash: -7431729439735063448,
FileReference: []uint8{
0x00, 0x61, 0x61, 0xbe, 0x82, 0x8b, 0xaf, 0x09, 0x7f, 0x58, 0xaa, 0xf9, 0x2d, 0x1d, 0x09, 0x8e,
0xce, 0xe8, 0xdd, 0x1c, 0x93,
},
},
"🎃": {
ID: 4956223179606458539,
AccessHash: -2107001400913062971,
FileReference: []uint8{
0x00, 0x61, 0x61, 0xbe, 0x82, 0x9b, 0x7e, 0xd2, 0xb9, 0x3b, 0xd2, 0xbd, 0x46, 0x98, 0x0d, 0xd5,
0x9b, 0x60, 0x4b, 0x35, 0xb2,
},
},
"🎄": {
ID: 5094064004578410733,
AccessHash: -4142643820629256996,
FileReference: []uint8{
0x00, 0x61, 0x61, 0xbe, 0x82, 0x75, 0xba, 0x53, 0xc1, 0x6d, 0x71, 0x14, 0x46, 0x54, 0xd9, 0x5b,
0x02, 0x45, 0xc7, 0xac, 0x59,
},
},
"🎊": {
ID: 5094064004578410732,
AccessHash: 8518192996098758509,
FileReference: []uint8{
0x00, 0x61, 0x61, 0xbe, 0x82, 0x8c, 0x35, 0xf7, 0xd1, 0x57, 0x62, 0x77, 0xbc, 0xa1, 0x03, 0xd0,
0xee, 0x9d, 0xf1, 0xee, 0x36,
},
},
"🦾": {
ID: 5094064004578410734,
AccessHash: -8934384022571962340,
FileReference: []uint8{
0x00, 0x61, 0x61, 0xbe, 0x82, 0xf8, 0xcd, 0xd3, 0x11, 0x99, 0x8b, 0xfd, 0xcf, 0x72, 0xfc, 0x28,
0xf6, 0x97, 0xe0, 0x94, 0x0b,
},
},
"🧟": {
ID: 4960929110848176332,
AccessHash: -8929417974289765626,
FileReference: []uint8{
0x00, 0x61, 0x61, 0xbe, 0x82, 0x26, 0x6b, 0x3e, 0x54, 0x9a, 0xde, 0x06, 0xbc, 0xc5, 0x03, 0xe6,
0x7a, 0x1c, 0x22, 0xed, 0x16,
},
},
"🧟\u200d♀": {
ID: 4960929110848176333,
AccessHash: 9161696144162881753,
FileReference: []uint8{
0x00, 0x61, 0x61, 0xbe, 0x82, 0x26, 0xd5, 0x06, 0x6c, 0x6f, 0x8f, 0x31, 0x96, 0x94, 0x5f, 0x87,
0x83, 0x2a, 0xec, 0x43, 0x06,
},
},
"🧟\u200d♂": {
ID: 4960929110848176331,
AccessHash: 3986395821757915468,
FileReference: []uint8{
0x00, 0x61, 0x61, 0xbe, 0x82, 0xed, 0x5d, 0x24, 0x88, 0x72, 0x59, 0xaf, 0x7e, 0x0e, 0x9e, 0x3f,
0x3d, 0xca, 0xb0, 0x74, 0x05,
},
},
},
GIFSearchBranding: "tenor",
GIFSearchEmojies: []string{
"👍",
"😘",
"😍",
"😡",
"🥳",
"😂",
"😮",
"🙄",
"😎",
"👎",
},
StickersEmojiSuggestOnlyAPI: false,
StickersEmojiCacheTime: 86400,
GroupCallVideoParticipantsMax: 1234,
YoutubePIP: "abc",
QRLoginCamera: false,
QRLoginCode: "disabled",
DialogFiltersEnabled: true,
DialogFiltersTooltip: false,
AutoArchiveSettingAvailable: false,
PendingSuggestions: []string{
"AUTOARCHIVE_POPULAR",
"VALIDATE_PASSWORD",
"VALIDATE_PHONE_NUMBER",
"NEWCOMER_TICKS",
},
AutologinToken: "string",
AutologinDomains: []string{
"instantview.telegram.org",
"translations.telegram.org",
"contest.dev",
"contest.com",
"bugs.telegram.org",
"suggestions.telegram.org",
"themes.telegram.org",
},
URLAuthDomains: []string{
"somedomain.telegram.org",
},
RoundVideoEncoding: RoundVideoEncoding{
Diameter: 384,
VideoBitrate: 1000,
AudioBitrate: 64,
MaxSize: 12582912,
},
ChatReadMarkExpirePeriod: 604800,
ChatReadMarkSizeThreshold: 50,
Unparsed: map[string]tg.JSONValueClass{
"unknown": &tg.JSONNull{},
},
}, appConfig)
}
+101
View File
@@ -0,0 +1,101 @@
package tljson
import (
"github.com/go-faster/errors"
"github.com/go-faster/jx"
"go.mau.fi/mautrix-telegram/pkg/gotd/tg"
)
// Decode decodes JSON and converts it to tg.JSONValueClass.
func Decode(d *jx.Decoder) (tg.JSONValueClass, error) {
switch tt := d.Next(); tt {
case jx.String:
s, err := d.Str()
if err != nil {
return nil, err
}
return &tg.JSONString{Value: s}, nil
case jx.Number:
f, err := d.Float64()
if err != nil {
return nil, err
}
return &tg.JSONNumber{Value: f}, nil
case jx.Null:
if err := d.Null(); err != nil {
return nil, err
}
return &tg.JSONNull{}, nil
case jx.Bool:
b, err := d.Bool()
if err != nil {
return nil, err
}
return &tg.JSONBool{Value: b}, nil
case jx.Array:
var r []tg.JSONValueClass
if err := d.Arr(func(d *jx.Decoder) error {
obj, err := Decode(d)
if err != nil {
return errors.Wrapf(err, "decode %d element", len(r))
}
r = append(r, obj)
return nil
}); err != nil {
return nil, err
}
return &tg.JSONArray{Value: r}, nil
case jx.Object:
var r []tg.JSONObjectValue
if err := d.Obj(func(d *jx.Decoder, key string) error {
obj, err := Decode(d)
if err != nil {
return errors.Wrapf(err, "decode %q", key)
}
r = append(r, tg.JSONObjectValue{
Key: key,
Value: obj,
})
return nil
}); err != nil {
return nil, err
}
return &tg.JSONObject{Value: r}, nil
default:
return nil, errors.Errorf("unexpected type %v", tt)
}
}
// Encode writes given value to Encoder.
func Encode(obj tg.JSONValueClass, e *jx.Encoder) {
switch obj := obj.(type) {
case *tg.JSONNull:
e.Null()
case *tg.JSONBool:
e.Bool(obj.Value)
case *tg.JSONNumber:
if v := int64(obj.Value); float64(v) == obj.Value {
e.Int64(v)
} else {
e.Float64(obj.Value)
}
case *tg.JSONString:
e.Str(obj.Value)
case *tg.JSONArray:
e.ArrStart()
for _, v := range obj.Value {
Encode(v, e)
}
e.ArrEnd()
case *tg.JSONObject:
e.ObjStart()
for _, pair := range obj.Value {
e.FieldStart(pair.Key)
Encode(pair.Value, e)
}
e.ObjEnd()
}
}
+67
View File
@@ -0,0 +1,67 @@
package tljson
import (
"testing"
"github.com/go-faster/jx"
"github.com/stretchr/testify/require"
"go.mau.fi/mautrix-telegram/pkg/gotd/tg"
)
func TestDecodeEncodeDecode(t *testing.T) {
tests := []struct {
name string
input string
wantErr bool
expect tg.JSONValueClass
}{
{"Empty", "", true, nil},
{"InvalidNull", "nul", true, nil},
{"InvalidTrue", "tru", true, nil},
{"InvalidFalse", "falsy", true, nil},
{"InvalidInt", `[1a]"`, true, nil},
{"InvalidFloat", "1.", true, nil},
{"InvalidString", `"hello`, true, nil},
{"InvalidArray", "[1, 2, 3.]", true, nil},
{"InvalidObject", `{"abc":"def}`, true, nil},
{"Null", "null", false, nil},
{"True", "true", false, nil},
{"False", "false", false, nil},
{"Int", "10", false, nil},
{"Float", "1.1", false, nil},
{"String", `"hello"`, false, nil},
{"EmptyArray", "[]", false, nil},
{"Array", "[1, 2, 3]", false, nil},
{"EmptyObject", `{}`, false, nil},
{"Object", `{"abc":"def"}`, false, nil},
{"Tree", `{"a":1,"b":true,"c":null,"sub":{"abc":"def"}}`, false, nil},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
a := require.New(t)
// Decode.
d := jx.DecodeStr(tt.input)
obj, err := Decode(d)
if tt.wantErr {
a.Error(err)
return
}
a.NoError(err)
// Encode.
e := jx.GetEncoder()
Encode(obj, e)
// Decode.
d.ResetBytes(e.Bytes())
obj2, err := Decode(d)
a.NoError(err)
a.Equal(obj, obj2)
})
}
}
+2
View File
@@ -0,0 +1,2 @@
// Package tljson contains some helpers to work with JSONValue class.
package tljson