Files
mautrix-telegram/pkg/gotd/telegram/tljson/app_config.go
T
2025-06-27 20:03:37 -07:00

402 lines
13 KiB
Go

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