diff --git a/.github/workflows/tlparser.yml b/.github/workflows/tlparser.yml new file mode 100644 index 00000000..c85db585 --- /dev/null +++ b/.github/workflows/tlparser.yml @@ -0,0 +1,32 @@ +name: TLParser Workflow + +on: + push: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: 1.22 + + - name: Build and run tlgen + run: | + go build -o tlgen ./internal/cmd/tlgen + ./tlgen + + - name: Create Pull Request + uses: peter-evans/create-pull-request@v3 + with: + token: ${{ secrets.GITHUB_TOKEN }} + commit-message: Auto LTL + title: Auto LTL + body: Automatically generated pull request from TLParser workflow \ No newline at end of file diff --git a/internal/cmd/tlgen/main.go b/internal/cmd/tlgen/main.go index f524ec72..2b5f74d6 100644 --- a/internal/cmd/tlgen/main.go +++ b/internal/cmd/tlgen/main.go @@ -2,24 +2,78 @@ package main import ( "fmt" + "io" + "net/http" "os" + "path/filepath" + "regexp" + "strings" + "time" "github.com/amarnathcjd/gogram/internal/cmd/tlgen/gen" "github.com/amarnathcjd/gogram/internal/cmd/tlgen/tlparser" ) -const helpMsg = `tlgen -usage: tlgen input_file.tl output_dir/ -THIS TOOL IS USING ONLY FOR AUTOMATIC CODE -GENERATION, DO NOT GENERATE FILES BY HAND! -No, seriously. Don't. go generate is amazing. You -are amazing too, but lesser 😏 -` -const license = `-` +const ( + API_SOURCE = "https://raw.githubusercontent.com/telegramdesktop/tdesktop/dev/Telegram/SourceFiles/mtproto/scheme/api.tl" + tlLOC = "../../../schemes/api.tl" + desLOC = "../../../telegram/" +) + +const helpMsg = `welcome to gogram's TL generator (c) @amarnathcjd` func main() { + if len(os.Args) == 0 || len(os.Args) == 1 { + currentLocalAPIVersionFile := filepath.Join(desLOC, "const.go") + currentLocalAPIVersion, err := os.ReadFile(currentLocalAPIVersionFile) + if err != nil { + panic(err) + } + + reg := regexp.MustCompile(`ApiVersion = \d+`) + str := string(currentLocalAPIVersion) + llayer := reg.FindString(str) + llayer = strings.TrimPrefix(llayer, "ApiVersion = ") + + currentRemoteAPIVersion, err := http.Get(API_SOURCE) + if err != nil { + panic(err) + } + + remoteAPIVersion, err := io.ReadAll(currentRemoteAPIVersion.Body) + if err != nil { + panic(err) + } + + reg = regexp.MustCompile(`// LAYER \d+`) + str = string(remoteAPIVersion) + rlayer := reg.FindString(str) + rlayer = strings.TrimPrefix(rlayer, "// LAYER ") + + if !strings.EqualFold(llayer, rlayer) { + fmt.Println("Local API version is", llayer, "and remote API version is", rlayer) + fmt.Println("Performing update") + + file, err := os.OpenFile(tlLOC, os.O_RDWR|os.O_CREATE, 0644) + if err != nil { + panic(err) + } + + file.Truncate(0) + file.Seek(0, 0) + file.WriteString(string(remoteAPIVersion)) + + root(tlLOC, desLOC) + } else { + fmt.Println("Local API version is", llayer, "and remote API version is", rlayer) + fmt.Println("No update required") + } + + return + } + if len(os.Args) != 3 { - fmt.Println(helpMsg) + fmt.Print(helpMsg) return } @@ -30,6 +84,7 @@ func main() { } func root(tlfile, outdir string) error { + startTime := time.Now() b, err := os.ReadFile(tlfile) if err != nil { return fmt.Errorf("read schema file: %w", err) @@ -40,10 +95,145 @@ func root(tlfile, outdir string) error { return fmt.Errorf("parse schema file: %w", err) } - g, err := gen.NewGenerator(schema, license, outdir) + g, err := gen.NewGenerator(schema, "(c) @amarnathcjd", outdir) if err != nil { return err } - return g.Generate() + err = g.Generate() + if err != nil { + return fmt.Errorf("generate code: %w", err) + } + + fmt.Println("Generated code in", outdir, "in", time.Since(startTime)) + minorFixes(outdir, getAPILayerFromFile(tlfile)) + return nil +} + +func getAPILayerFromFile(tlfile string) string { + b, err := os.ReadFile(tlfile) + if err != nil { + return "0" + } + + // last line:: // LAYER 176 + lines := strings.Split(string(b), "\n") + if len(lines) < 2 { + return "0" + } + + lastLine := lines[len(lines)-1] + if !strings.HasPrefix(lastLine, "// LAYER") { + return "0" + } + + return strings.TrimSpace(strings.TrimPrefix(lastLine, "// LAYER")) +} + +func minorFixes(outdir string, layer string) { + execWorkDir, err := os.Getwd() + if err != nil { + panic(err) + } + + execWorkDir = filepath.Join(execWorkDir, outdir) + fmt.Println("Applying minor fixes to generated code in", execWorkDir) + + replace(filepath.Join(execWorkDir, "methods_gen.go"), "return bool", "return false") + replace(filepath.Join(execWorkDir, "methods_gen.go"), `if err != nil { + return nil, errors.Wrap(err, "sending UsersGetUsers") + } + + resp, ok := responseData.([]User) + if !ok { + panic("got invalid response type: " + reflect.TypeOf(responseData).String()) + }`, `if err != nil { + return nil, errors.Wrap(err, "sending UsersGetUsers") + } + + resp, ok := responseData.([]User) + if !ok { + if _, ok := responseData.([]*UserObj); ok { // Temp Fix till Problem is Identified + var users []User = make([]User, len(responseData.([]*UserObj))) + for i, user := range responseData.([]*UserObj) { + users[i] = user + } + + return users, nil + } + + panic("got invalid response type: " + reflect.TypeOf(responseData).String()) + }`) + + replace(filepath.Join(execWorkDir, "methods_gen.go"), `errors []SecureValueError`, `errorsw []SecureValueError`) + replace(filepath.Join(execWorkDir, "methods_gen.go"), `responseData, err := c.MakeRequest(&UsersSetSecureValueErrorsParams{ + Errors: errors, + ID: id, + })`, `responseData, err := c.MakeRequest(&UsersSetSecureValueErrorsParams{ + Errors: errorsw, + ID: id, + })`) + + replace(filepath.Join(execWorkDir, "enums_gen.go"), `Null Null`, `NullCrc Null`) + + replace(filepath.Join(execWorkDir, "init_gen.go"), `Null,`, `NullCrc,`) + if layer != "0" { + // replace ApiVersion = 174 + fmt.Println("Updating ApiVersion to", layer) + file, err := os.OpenFile(filepath.Join(execWorkDir, "const.go"), os.O_RDWR|os.O_CREATE, 0644) + if err != nil { + panic(err) + } + + content, err := io.ReadAll(file) + if err != nil { + panic(err) + } + + reg := regexp.MustCompile(`ApiVersion = \d+`) + str := string(content) + + str = reg.ReplaceAllString(str, "ApiVersion = "+layer) + + file.Truncate(0) + file.Seek(0, 0) + + _, err = file.Write([]byte(str)) + if err != nil { + panic(err) + } + } +} + +func replace(filename, old, new string) { + f, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0644) + if err != nil { + panic(err) + } + + content, err := io.ReadAll(f) + if err != nil { + panic(err) + } + + //fmt.Println("Replacing", old, "with", new, "in", filename) + + str := string(content) + str = strings.Replace(str, old, new, -1) + + // truncate the file before writing + err = f.Truncate(0) + if err != nil { + panic(err) + } + + _, err = f.Seek(0, 0) + if err != nil { + panic(err) + } + + _, err = f.Write([]byte(str)) + if err != nil { + panic(err) + } } diff --git a/mtproto.go b/mtproto.go index 2995a490..46237f88 100755 --- a/mtproto.go +++ b/mtproto.go @@ -401,6 +401,7 @@ func (m *MTProto) Ping() time.Duration { func (m *MTProto) startReadingResponses(ctx context.Context) { m.routineswg.Add(1) + go func() { defer m.routineswg.Done() for { @@ -413,7 +414,6 @@ func (m *MTProto) startReadingResponses(ctx context.Context) { return } err := m.readMsg() - //errors.Is(err, io.EOF) if err != nil { if strings.Contains(err.Error(), "unexpected error: unexpected EOF") { @@ -442,16 +442,18 @@ func (m *MTProto) startReadingResponses(ctx context.Context) { } return default: - if e, ok := err.(transport.ErrCode); ok && e != 4294966892 { - err = m.makeAuthKey() - if err != nil { - m.Logger.Error(errors.Wrap(err, "making auth key")) - } - } else { - err = m.Reconnect(false) - if err != nil { - m.Logger.Error(errors.Wrap(err, "reconnecting error")) + switch e := err.(type) { + case *ErrResponseCode: + if e.Code == 4294966892 { + m.Logger.Error(errors.New("[AUTH_KEY_INVALID] the auth key is invalid and needs to be reauthenticated (code -404)")) + panic("[AUTH_KEY_INVALID] the auth key is invalid and needs to be reauthenticated (code -404)") } + case *transport.ErrCode: + m.Logger.Error(errors.New("[TRANSPORT_ERROR] - " + e.Error())) + } + + if err := m.Reconnect(false); err != nil { + m.Logger.Error(errors.Wrap(err, "reconnecting")) } } } @@ -620,12 +622,12 @@ func (m *MTProto) offsetTime() { } if err := json.NewDecoder(resp.Body).Decode(&timeResponse); err != nil { - m.Logger.Error(errors.Wrap(err, "offsetting time")) + m.Logger.Error(errors.Wrap(err, "off-setting time")) return } m.timeOffset = timeResponse.Unixtime - currentLocalTime - m.Logger.Info("SystemTime is out of sync, offsetting time by " + strconv.FormatInt(m.timeOffset, 10) + " seconds") + m.Logger.Info("system time is out of sync, off-setting time by " + strconv.FormatInt(m.timeOffset, 10) + " seconds") } func closeOnCancel(ctx context.Context, c io.Closer) {