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())) }