// Code generated manually for JSON invocation support.// This file provides InvokeJSON method for calling MTProto methods via JSON.package tgimport ()// InvokeJSON invokes an MTProto method using JSON input.// The JSON input should contain an "@type" field with the method name (e.g., "messages.sendMessage").// All method parameters should be included in the same JSON object.//// The JSON format follows MTProto JSON API specification where:// - "@type" field contains the method or type name// - All fields use snake_case naming// - Nested objects also include "@type" field//// useSnakeCase controls the output JSON field naming convention:// - true: Use snake_case (MTProto convention, e.g., "user_id", "access_hash")// - false: Use PascalCase (Go struct field names, e.g., "UserID", "AccessHash")//// Example://// jsonInput := `{// "@type": "messages.sendMessage",// "peer": {"@type": "inputPeerUser", "user_id": 123, "access_hash": 456},// "message": "Hello",// "random_id": 123456789// }`// result, err := client.InvokeJSON(ctx, jsonInput, true) // true = snake_case outputfunc ( *Client) ( context.Context, string, bool) (string, error) {// Parse JSON to extract @type field := jx.DecodeStr()varstringif := .ObjBytes(func( *jx.Decoder, []byte) error {ifstring() == tdjson.TypeField { , := .Str()if != nil {return } = returnnil }return .Skip() }); != nil {return"", errors.Wrap(, "extract @type") }if == "" {return"", errors.New("@type field is required") }// Get type ID from method name := NamesMap() , := []if ! {return"", errors.Errorf("unknown method or type: %q", ) }// Create request object using constructor (no reflection) := TypesConstructorMap() , := []if ! {return"", errors.Errorf("no constructor for type ID: 0x%x (%s)", , ) } := ()if == nil {return"", errors.Errorf("constructor returned nil for type ID: 0x%x (%s)", , ) }// Convert JSON to binary format using jx for parsing (no encoding/json) // Create a fresh decoder since the previous one was consumedif := jsonUnmarshalToObject(, ); != nil {return"", errors.Wrap(, "unmarshal JSON to object") }// For result, we use a generic decoder that can handle any result type := &genericResultDecoder{constructors: , }// Invoke methodif := .rpc.Invoke(, .(bin.Encoder), ); != nil {return"", }if .result == nil {return"", errors.New("no result from invocation") }// Convert binary directly to pure MTProto JSON // Note: For output, we still use encoding/json since manual encoding requires reflection // The key improvement is that input parsing uses jx, not encoding/jsonreturnbinaryToMTProtoJSON(.buffer, .typeID, )}// genericResultDecoder is a decoder that can decode any response type// and store it for later JSON encoding.typegenericResultDecoderstruct {constructorsmap[uint32]func() bin.Objectresultbin.Objectbuffer *bin.BuffertypeIDuint32}func ( *genericResultDecoder) ( *bin.Buffer) error {// Peek at the type ID to determine what to decode , := .PeekID()if != nil {returnerrors.Wrap(, "peek response type ID") } .typeID = // Save the buffer for later JSON encoding .buffer = &bin.Buffer{Buf: make([]byte, len(.Buf))}copy(.buffer.Buf, .Buf)// Use TypesConstructorMap to create appropriate instance , := .constructors[]if ! {returnerrors.Errorf("unknown response type: 0x%x", ) } := ()if == nil {returnerrors.Errorf("failed to create response instance for type: 0x%x", ) }// Decode into instance , := .(bin.Decoder)if ! {returnerrors.Errorf("response type does not implement bin.Decoder") }if := .Decode(); != nil {returnerrors.Wrap(, "decode response") }// Store as bin.Objectif , := .(bin.Object); { .result = } else {returnerrors.New("response is not a bin.Object") }returnnil}// jsonValue represents an intermediate JSON value during parsingtypejsonValuestruct {typjx.Typestrstringnumjx.Numbooleanboolarr []jsonValueobjmap[string]jsonValue}// jsonUnmarshalToObject unmarshals JSON into an MTProto object using jx.// Parses JSON with jx into intermediate structure, then encodes to binary.func ( string, bin.Object) error {// Get TypeInfo to know field structure , := .(interface{ () tdp.Type })if ! {returnerrors.New("object does not implement TypeInfo()") } := .()// Parse JSON with jx into intermediate structure := jx.DecodeStr() , := parseJSONObject()if != nil {returnerrors.Wrap(, "parse JSON object") }// Build binary buffer := &bin.Buffer{} .PutID(.ID)// Encode each field in order based on TypeInfofor , := range .Fields {// Skip flags fields - they will be set by SetFlags() after decodingif .SchemaName == "flags" || .SchemaName == "flags2" {continue }// Skip null/optional fields that aren't presentif .Null {if , := [.SchemaName]; ! {continue } } , := [.SchemaName]if ! {continue }// Encode field value to binaryif := encodeJSONValueToBinary(, ); != nil {returnerrors.Wrapf(, "encode field %s", .SchemaName) } }// Decode binary back to objectif := .Decode(); != nil {returnerrors.Wrap(, "decode binary to object") }// Set flags if the object supports itif , := .(interface{ () }); { .() }returnnil}// parseJSONObject parses a JSON object into a map of field values// includeTypeField controls whether to include @type field (needed for nested objects)// The decoder should be positioned at the start (ObjBytes will call Next() internally)func ( *jx.Decoder) (map[string]jsonValue, error) {returnparseJSONObjectWithType(, false)}// parseJSONObjectWithType parses a JSON object, optionally including @type field// ObjBytes will internally call Next() to check if it's an objectfunc ( *jx.Decoder, bool) (map[string]jsonValue, error) { := make(map[string]jsonValue)if := .ObjBytes(func( *jx.Decoder, []byte) error { := string()// Skip @type field if includeType is false (for root object)if == "@type" && ! {return .Skip() } , := parseJSONValue()if != nil {returnerrors.Wrapf(, "parse field %s", ) } [] = returnnil }); != nil {returnnil, }return , nil}// parseJSONValue parses a single JSON valuefunc ( *jx.Decoder) (jsonValue, error) {varjsonValue// Peek at the next token to determine type := .Next()switch {casejx.Object:// Parse object recursively (include @type for nested objects) // Next() has already consumed the object token, so we're now inside the object // We can call ObjBytes directly := make(map[string]jsonValue)if := .ObjBytes(func( *jx.Decoder, []byte) error { := string()// Always include @type for nested objects , := ()if != nil {returnerrors.Wrapf(, "parse field %s", ) } [] = returnnil }); != nil {returnjsonValue{}, errors.Wrap(, "parse nested object") } .typ = jx.Object .obj = casejx.Array:// Parse array := []jsonValue{}if := .Arr(func( *jx.Decoder) error { , := ()if != nil {returnerrors.Wrap(, "parse array item") } = append(, )returnnil }); != nil {returnjsonValue{}, errors.Wrap(, "parse array") } .typ = jx.Array .arr = casejx.String: , := .Str()if != nil {returnjsonValue{}, errors.Wrap(, "parse string") } .typ = jx.String .str = casejx.Number: , := .Num()if != nil {returnjsonValue{}, errors.Wrap(, "parse number") } .typ = jx.Number .num = casejx.Bool: , := .Bool()if != nil {returnjsonValue{}, errors.Wrap(, "parse bool") } .typ = jx.Bool .boolean = casejx.Null:if := .Null(); != nil {returnjsonValue{}, errors.Wrap(, "parse null") } .typ = jx.Nulldefault:returnjsonValue{}, errors.Errorf("unexpected JSON token: %v", ) }return , nil}// encodeJSONValueToBinary encodes a jsonValue to binary formatfunc ( *bin.Buffer, jsonValue) error {returnencodeJSONValueToBinaryWithContext(, , "")}// encodeJSONValueToBinaryWithContext encodes a jsonValue to binary format with field contextfunc ( *bin.Buffer, jsonValue, string) error {switch .typ {casejx.Object:// This is an interface type (object with @type)returnencodeInterfaceObjectToBinary(, .obj)casejx.Array:// This is an array .PutVectorHeader(len(.arr))for , := range .arr {if := encodeJSONValueToBinary(, ); != nil {returnerrors.Wrap(, "encode array item") } }returnnilcasejx.Bool: .PutBool(.boolean)casejx.Number:if .num.IsInt() { , := .num.Int64()if != nil {returnerrors.Wrap(, "parse integer") } .PutLong() } else { , := .num.Float64()if != nil {returnerrors.Wrap(, "parse float") } .PutDouble() }casejx.String:// Check if this might be Int128 or Int256 (hex-encoded) // Int128 = 16 bytes = 32 hex chars, Int256 = 32 bytes = 64 hex charsifisHexString(.str) { := len(.str)// Check field name hints (common names for Int128/Int256 fields) // Also check length - if it's exactly 32 or 64 hex chars, it's likely Int128/Int256 := == "public_key" || == "key" || == "nonce" || == "secret" || == 32 || == 64if {switch {case32: // Int128varbin.Int128if , := hex.Decode([:], []byte(.str)); == nil { .PutInt128()returnnil }case64: // Int256varbin.Int256if , := hex.Decode([:], []byte(.str)); == nil { .PutInt256()returnnil } } } }// Regular string .PutString(.str)casejx.Null:// Nil values are typically not encoded (handled by optional fields)returnnildefault:returnerrors.Errorf("unsupported JSON value type: %v", .typ) }returnnil}// encodeInterfaceObjectToBinary encodes an interface object (with @type) to binaryfunc ( *bin.Buffer, map[string]jsonValue) error {// Extract @type , := [tdjson.TypeField]if ! {returnerrors.New("interface object missing @type field") }if .typ != jx.String {returnerrors.New("@type field must be a string") } := .str// Get type ID and constructor := NamesMap() , := []if ! {returnerrors.Errorf("unknown type: %q", ) } := TypesConstructorMap() , := []if ! {returnerrors.Errorf("no constructor for type: %q", ) } := ()if == nil {returnerrors.Errorf("constructor returned nil for type: %q", ) }// Get TypeInfo for the concrete object , := .(interface{ () tdp.Type })if ! {returnerrors.New("concrete object does not implement TypeInfo()") } := .()// Build binary buffer for concrete object := &bin.Buffer{} .PutID(.ID)// Encode each fieldfor , := range .Fields {if .SchemaName == "flags" || .SchemaName == "flags2" {continue }if .Null {if , := [.SchemaName]; ! {continue } } , := [.SchemaName]if ! {continue }// Encode field value, with context about field name for better type detectionif := encodeJSONValueToBinaryWithContext(, , .SchemaName); != nil {returnerrors.Wrapf(, "encode field %s", .SchemaName) } }// Try to decode to concrete object to validate encoding // If it fails, it might be because we encoded a hex string incorrectly := &bin.Buffer{Buf: make([]byte, len(.Buf))}copy(.Buf, .Buf)if := .Decode(); != nil {// Decode failed - might be because we encoded a hex string as regular string // Try re-encoding with Int128/Int256 detection for hex stringsreturnretryEncodeWithInt128Int256(, , , , ) }// Decode succeeded, use the decoded object // Set flagsif , := .(interface{ () }); { .() }// Encode the concrete object to the output bufferreturn .(bin.Encoder).Encode()}// retryEncodeWithInt128Int256 retries encoding with Int128/Int256 detection for hex stringsfunc ( *bin.Buffer, map[string]jsonValue, string, bin.Object, tdp.Type) error {// Rebuild binary buffer, this time being more aggressive about Int128/Int256 detection := &bin.Buffer{} .PutID(.ID)// Encode each field with enhanced Int128/Int256 detectionfor , := range .Fields {if .SchemaName == "flags" || .SchemaName == "flags2" {continue }if .Null {if , := [.SchemaName]; ! {continue } } , := [.SchemaName]if ! {continue }// Encode field value with field name context for Int128/Int256 detectionif := encodeJSONValueToBinaryWithContext(, , .SchemaName); != nil {returnerrors.Wrapf(, "encode field %s", .SchemaName) } }// Try to decode again := &bin.Buffer{Buf: make([]byte, len(.Buf))}copy(.Buf, .Buf)if := .Decode(); != nil {returnerrors.Wrapf(, "retry decode failed for type %q", ) }// Set flagsif , := .(interface{ () }); { .() }// Encode to output bufferreturn .(bin.Encoder).Encode()}// isHexString checks if a string is a valid hex stringfunc ( string) bool {iflen() == 0 {returnfalse }for , := range {if !(( >= '0' && <= '9') || ( >= 'a' && <= 'f') || ( >= 'A' && <= 'F')) {returnfalse } }returntrue}// binaryToMTProtoJSON converts binary MTProto buffer to JSON.// Note: For output marshaling, we use encoding/json since manual encoding// would require reflection or code generation. The key improvement is that// input parsing uses jx, not encoding/json.// useSnakeCase controls whether to convert field names to snake_case (true) or keep PascalCase (false).func ( *bin.Buffer, uint32, bool) (string, error) {// Get type name from registry := NamesMap() := ""for , := range {if == { = break } }if == "" {return"", errors.Errorf("type name not found for ID: 0x%x", ) }// Decode binary to Go struct := TypesConstructorMap() , := []if ! {return"", errors.Errorf("no constructor for type ID: 0x%x", ) } := ()if == nil {return"", errors.New("constructor returned nil") }// Decode from bufferif != nil { := &bin.Buffer{Buf: make([]byte, len(.Buf))}copy(.Buf, .Buf)if := .Decode(); != nil {return"", errors.Wrap(, "decode binary to object") } }// Use jx.Encoder to build JSON manually // We'll encode using TypeInfo to iterate fieldsreturnencodeObjectToJSONWithJX(, , )}// encodeObjectToJSONWithJX encodes a bin.Object to JSON.// Note: We use encoding/json for output marshaling since:// 1. The object is already properly decoded from binary// 2. Structs have JSON tags for proper field names// 3. Manual encoding would require reflection or code generation// The key improvement is that input parsing uses jx, not encoding/json.// useSnakeCase controls whether to convert field names to snake_case (true) or keep PascalCase (false).func ( bin.Object, string, bool) (string, error) {// Marshal object to JSON (uses struct field names, which are PascalCase) , := json.Marshal()if != nil {return"", errors.Wrap(, "marshal object to JSON") }// Parse and ensure @type field is presentvarmap[string]interface{}if := json.Unmarshal(, &); != nil {returnstring(), nil// Return as-is if unmarshal fails } ["@type"] = // Convert field names to snake_case if requestedif { = convertKeysToSnakeCase() } , := json.Marshal()if != nil {return"", errors.Wrap(, "marshal result") }returnstring(), nil}// convertKeysToSnakeCase recursively converts map keys from PascalCase to snake_casefunc ( map[string]interface{}) map[string]interface{} { := make(map[string]interface{})for , := range { := pascalToSnakeCase()switch val := .(type) {casemap[string]interface{}: [] = ()case []interface{}: := make([]interface{}, len())for , := range {if , := .(map[string]interface{}); { [] = () } else { [] = } } [] = default: [] = } }return}// pascalToSnakeCase converts PascalCase to snake_casefunc ( string) string {if == "" {return }var []runefor , := range {if > 0 && >= 'A' && <= 'Z' { = append(, '_') }if >= 'A' && <= 'Z' { = append(, +'a'-'A') } else { = append(, ) } }returnstring()}
The pages are generated with Goldsv0.8.4. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.