// Binary dltl fetches .tl schema from remote repo. package main import ( "bufio" "bytes" "crypto/sha256" "flag" "fmt" "io" "net/http" "net/url" "os" "path" "strconv" "strings" "github.com/gotd/tl" ) func mergeSchema(target, s *tl.Schema) { Definitions: for _, d := range s.Definitions { for _, targetD := range target.Definitions { if targetD.Definition.ID == d.Definition.ID { continue Definitions } } target.Definitions = append(target.Definitions, d) } Classes: for _, c := range s.Classes { for _, targetC := range target.Classes { if targetC.Name == c.Name { continue Classes } } target.Classes = append(target.Classes, c) } if target.Layer == 0 { target.Layer = s.Layer } } func main() { var ( name = flag.String("f", "telegram_api.tl", "file name to download; api.tl or mtproto.tl") base = flag.String("base", "https://raw.githubusercontent.com/tdlib/td", "base url") branch = flag.String("branch", "master", "branch to use") dir = flag.String("dir", "td/generate/scheme", "directory of schemas") out = flag.String("o", "", "output file name (blank to stdout)") merge = flag.String("merge", "", "path to schema(s) to merge with, comma-separated") ) flag.Parse() u, err := url.Parse(*base) if err != nil { panic(err) } u.Path = path.Join(u.Path, *branch, *dir, *name) res, err := http.Get(u.String()) if err != nil { panic(err) } defer func() { _ = res.Body.Close() }() if res.StatusCode/100 != 2 { panic(fmt.Sprintf("%s %d", u.String(), res.StatusCode)) } // Parsing in-place. h := sha256.New() s, err := tl.Parse(io.TeeReader(res.Body, h)) if err != nil { panic(err) } if *merge != "" { for _, mergeName := range strings.Split(*merge, ",") { data, err := os.ReadFile(mergeName) if err != nil { panic(err) } m, err := tl.Parse(bytes.NewReader(data)) if err != nil { panic(err) } mergeSchema(s, m) } } // Trying to detect layer of tdlib schema. if s.Layer == 0 && *name == "telegram_api.tl" { layerURL, err := url.Parse(*base) if err != nil { panic(err) } layerURL.Path = path.Join(layerURL.Path, *branch, "td/telegram/Version.h") res, err := http.Get(layerURL.String()) if err != nil { panic(err) } if res.StatusCode == http.StatusOK { scanner := bufio.NewScanner(res.Body) for scanner.Scan() { t := strings.TrimSpace(scanner.Text()) // constexpr int32 MTPROTO_LAYER = 131; const ( prefix = `constexpr int32 MTPROTO_LAYER = ` suffix = `;` ) if !strings.HasPrefix(t, prefix) { continue } t = strings.TrimPrefix(t, prefix) t = strings.TrimSuffix(t, suffix) t = strings.TrimSpace(t) if layer, err := strconv.Atoi(t); err == nil { s.Layer = layer } } } } if s.Layer == 0 && *name == "telegram_api.tl" { // Still failed. panic("failed to detect layer") } var outWriter io.Writer = os.Stdout if *out != "" { w, err := os.Create(*out) if err != nil { panic(err) } defer func() { if err := w.Close(); err != nil { panic(err) } }() outWriter = w } // Writing header to avoid manual edit. var b strings.Builder b.WriteString("// Code generated by ./cmd/dltl, DO NOT EDIT.\n") b.WriteString("//\n") b.WriteString(fmt.Sprintf("// Source: %s\n", u)) if *merge != "" { b.WriteString(fmt.Sprintf("// Merge: %s\n", *merge)) } if s.Layer > 0 { b.WriteString(fmt.Sprintf("// Layer: %d\n", s.Layer)) } b.WriteString(fmt.Sprintf("// SHA256: %x\n", h.Sum(nil))) b.WriteRune('\n') if _, err := io.WriteString(outWriter, b.String()); err != nil { panic(err) } if _, err := s.WriteTo(outWriter); err != nil { panic(err) } }