diff --git a/cmd/tg/app.go b/cmd/tg/app.go index 895e7a1..4936236 100644 --- a/cmd/tg/app.go +++ b/cmd/tg/app.go @@ -14,6 +14,7 @@ import ( "golang.org/x/xerrors" "gopkg.in/yaml.v3" + "github.com/gotd/cli/internal/pretty" "github.com/gotd/td/session" "github.com/gotd/td/telegram" "github.com/gotd/td/telegram/dcs" @@ -25,6 +26,8 @@ type app struct { cfg Config log *zap.Logger opts telegram.Options + + debugInvoker bool } func newApp() *app { @@ -59,17 +62,20 @@ func (p *app) run(ctx context.Context, f func(ctx context.Context, api *tg.Clien } } - invoker := invokers.NewWaiter(client) - raw := tg.NewClient(invoker) - ctx, cancel := context.WithCancel(ctx) g, ctx := errgroup.WithContext(ctx) + + invoker := invokers.NewWaiter(client) g.Go(func() error { return invoker.Run(ctx) }) g.Go(func() error { defer cancel() - return f(ctx, raw) + var i tg.Invoker = invoker + if p.debugInvoker { + i = pretty.Invoker{Next: i} + } + return f(ctx, tg.NewClient(i)) }) if err := g.Wait(); !errors.Is(err, context.Canceled) { @@ -113,6 +119,9 @@ func (p *app) Before(c *cli.Context) error { if c.Bool("test") { p.opts.DCList = dcs.StagingDCs() } + if c.Bool("debug-invoker") { + p.debugInvoker = true + } return nil } diff --git a/cmd/tg/main.go b/cmd/tg/main.go index 422f367..381a0dc 100644 --- a/cmd/tg/main.go +++ b/cmd/tg/main.go @@ -37,6 +37,10 @@ func main() { Value: defaultConfigPath(), Usage: "config to use", }, + &cli.BoolFlag{ + Name: "debug-invoker", + Usage: "use pretty-printing debug invoker", + }, &cli.BoolFlag{ Name: "test", Aliases: []string{"staging"}, diff --git a/internal/pretty/pretty.go b/internal/pretty/pretty.go new file mode 100644 index 0000000..aabf736 --- /dev/null +++ b/internal/pretty/pretty.go @@ -0,0 +1,46 @@ +// Package pretty implements pretty-print facilities. +package pretty + +import ( + "context" + "fmt" + "reflect" + "time" + + "github.com/gotd/td/bin" + "github.com/gotd/td/tdp" + "github.com/gotd/td/tg" +) + +type Invoker struct { + Next tg.Invoker +} + +func formatObject(input interface{}) string { + o, ok := input.(tdp.Object) + if !ok { + // Handle tg.*Box values. + rv := reflect.Indirect(reflect.ValueOf(input)) + for i := 0; i < rv.NumField(); i++ { + if v, ok := rv.Field(i).Interface().(tdp.Object); ok { + return formatObject(v) + } + } + + return fmt.Sprintf("%T (not object)", input) + } + return tdp.Format(o) +} + +func (i Invoker) InvokeRaw(ctx context.Context, input bin.Encoder, output bin.Decoder) error { + fmt.Println("→", formatObject(input)) + start := time.Now() + if err := i.Next.InvokeRaw(ctx, input, output); err != nil { + fmt.Println("←", err) + return err + } + + fmt.Printf("← (%s) %s\n", time.Since(start).Round(time.Millisecond), formatObject(output)) + + return nil +}