// Copyright (c) 2025 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. //go:build ignore package main import ( "fmt" "go/ast" "go/parser" "go/token" "os" "strings" "go.mau.fi/util/exerrors" ) const header = `// GENERATED BY internals_generate.go; DO NOT EDIT //go:generate go run internals_generate.go //go:generate goimports -local git.bobomao.top/joey/testwh -w internals.go package whatsmeow ` const postImportHeader = ` type DangerousInternalClient struct { c *Client } // DangerousInternals allows access to all unexported methods in Client. // // Deprecated: dangerous func (cli *Client) DangerousInternals() *DangerousInternalClient { return &DangerousInternalClient{cli} } type DangerousInfoQuery = infoQuery type DangerousInfoQueryType = infoQueryType ` func getTypeName(expr ast.Expr) string { switch e := expr.(type) { case *ast.Ident: return e.Name case *ast.StarExpr: return "*" + getTypeName(e.X) case *ast.ArrayType: if e.Len != nil { return fmt.Sprintf("[%s]%s", getTypeName(e.Len), getTypeName(e.Elt)) } return "[]" + getTypeName(e.Elt) case *ast.MapType: return fmt.Sprintf("map[%s]%s", getTypeName(e.Key), getTypeName(e.Value)) case *ast.ChanType: if e.Dir == ast.SEND { return fmt.Sprintf("chan<- %s", getTypeName(e.Value)) } else if e.Dir == ast.RECV { return fmt.Sprintf("<-chan %s", getTypeName(e.Value)) } return fmt.Sprintf("chan %s", getTypeName(e.Value)) case *ast.FuncType: var params []string for _, param := range e.Params.List { params = append(params, getTypeName(param.Type)) } var results []string if e.Results != nil { for _, result := range e.Results.List { results = append(results, getTypeName(result.Type)) } } retVals := strings.Join(results, ", ") if len(results) > 1 { retVals = fmt.Sprintf("(%s)", retVals) } return fmt.Sprintf("func(%s) %s", strings.Join(params, ", "), retVals) case *ast.SelectorExpr: return fmt.Sprintf("%s.%s", getTypeName(e.X), e.Sel.Name) case *ast.StructType: // This isn't technically correct, but struct literals shouldn't be used for anything else return "struct{}" case *ast.Ellipsis: return fmt.Sprintf("...%s", getTypeName(e.Elt)) case *ast.BasicLit: return e.Value default: panic(fmt.Errorf("unknown type %T", e)) } } var write func(str string) var writef func(format string, args ...any) func main() { fset := token.NewFileSet() fileNames := []string{ "appstate.go", "armadillomessage.go", "broadcast.go", "call.go", "client.go", "connectionevents.go", "download.go", "download-to-file.go", "group.go", "handshake.go", "keepalive.go", "mediaconn.go", "mediaretry.go", "message.go", "msgsecret.go", "newsletter.go", "notification.go", "pair-code.go", "pair.go", "prekeys.go", "presence.go", "privacysettings.go", "push.go", "qrchan.go", "receipt.go", "request.go", "retry.go", "sendfb.go", "send.go", "upload.go", "user.go", "reportingtoken.go", } files := make([]*ast.File, len(fileNames)) for i, name := range fileNames { files[i] = exerrors.Must(parser.ParseFile(fset, name, nil, parser.SkipObjectResolution)) } file := exerrors.Must(os.OpenFile("internals.go", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)) write = func(str string) { exerrors.Must(file.WriteString(str)) } writef = func(format string, args ...any) { exerrors.Must(fmt.Fprintf(file, format, args...)) } write(header) write("import (\n") for _, i := range files[0].Imports { write("\t") if i.Name != nil { writef("%s ", i.Name.Name) } writef("%s\n", i.Path.Value) } write(")\n") write(postImportHeader) for _, f := range files { processFile(f) } exerrors.PanicIfNotNil(file.Close()) } func processFile(f *ast.File) { ast.Inspect(f, func(node ast.Node) (retVal bool) { retVal = true funcDecl, ok := node.(*ast.FuncDecl) if !ok || funcDecl.Name.IsExported() { return } if funcDecl.Recv == nil || len(funcDecl.Recv.List) == 0 || len(funcDecl.Recv.List[0].Names) == 0 || funcDecl.Recv.List[0].Names[0].Name != "cli" { return } writef("\nfunc (int *DangerousInternalClient) %s%s(", strings.ToUpper(funcDecl.Name.Name[0:1]), funcDecl.Name.Name[1:]) for i, param := range funcDecl.Type.Params.List { if i != 0 { write(", ") } for j, name := range param.Names { if j != 0 { write(", ") } write(name.Name) } if len(param.Names) > 0 { write(" ") } write(getTypeName(param.Type)) } write(") ") if funcDecl.Type.Results != nil && len(funcDecl.Type.Results.List) > 0 { needsParentheses := len(funcDecl.Type.Results.List) > 1 || len(funcDecl.Type.Results.List[0].Names) > 0 if needsParentheses { write("(") } for i, result := range funcDecl.Type.Results.List { if i != 0 { write(", ") } for j, name := range result.Names { if j != 0 { write(", ") } write(name.Name) } if len(result.Names) > 0 { write(" ") } write(getTypeName(result.Type)) } if needsParentheses { write(")") } write(" ") } write("{\n\t") if funcDecl.Type.Results != nil { write("return ") } writef("int.c.%s(", funcDecl.Name.Name) for i, param := range funcDecl.Type.Params.List { for j, name := range param.Names { if i != 0 || j != 0 { write(", ") } write(name.Name) _, isEllipsis := param.Type.(*ast.Ellipsis) if isEllipsis { write("...") } } } write(")\n}\n") return }) }