From 4d3f6b69e800cf96671b3c809eab2b4ecd8af97e Mon Sep 17 00:00:00 2001 From: Rustam Gilyazov <16064414+rusq@users.noreply.github.com> Date: Fri, 23 Dec 2022 08:36:31 +1000 Subject: [PATCH 1/2] externalise internal/mtp to github.com/rusq/mtpwrap --- .gitignore | 4 + cmd/testui/main.go | 2 +- go.mod | 13 +- go.sum | 13 +- internal/mtp/authflow/auth_term.go | 197 -------------------------- internal/mtp/authflow/util_unix.go | 9 -- internal/mtp/authflow/util_windows.go | 9 -- internal/mtp/bg/bg.go | 82 ----------- internal/mtp/creds_storage.go | 64 --------- internal/mtp/creds_storage_test.go | 50 ------- internal/mtp/dialogs.go | 110 -------------- internal/mtp/dialogs_filters.go | 25 ---- internal/mtp/messages.go | 134 ------------------ internal/mtp/messages_test.go | 87 ------------ internal/mtp/mtp.go | 4 +- internal/mtp/peer_storage.go | 129 ----------------- internal/tui/app.go | 2 +- internal/tui/chatlist.go | 2 +- internal/tui/confirm.go | 2 +- internal/waipu/batch.go | 2 +- internal/waipu/cli.go | 2 +- main.go | 4 +- 22 files changed, 23 insertions(+), 923 deletions(-) delete mode 100644 internal/mtp/authflow/auth_term.go delete mode 100644 internal/mtp/authflow/util_unix.go delete mode 100644 internal/mtp/authflow/util_windows.go delete mode 100644 internal/mtp/bg/bg.go delete mode 100644 internal/mtp/creds_storage.go delete mode 100644 internal/mtp/creds_storage_test.go delete mode 100644 internal/mtp/dialogs.go delete mode 100644 internal/mtp/dialogs_filters.go delete mode 100644 internal/mtp/messages.go delete mode 100644 internal/mtp/messages_test.go delete mode 100644 internal/mtp/peer_storage.go diff --git a/.gitignore b/.gitignore index d57302b..58b430a 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,7 @@ .vscode .idea *~ + +#executable +wipemychat +wipemychat.exe diff --git a/cmd/testui/main.go b/cmd/testui/main.go index a4ac227..2b6e44b 100644 --- a/cmd/testui/main.go +++ b/cmd/testui/main.go @@ -11,7 +11,7 @@ import ( "github.com/gotd/td/telegram/query/messages" "github.com/rusq/dlog" - "github.com/rusq/wipemychat/internal/mtp" + mtp "github.com/rusq/mtpwrap" "github.com/rusq/wipemychat/internal/tui" ) diff --git a/go.mod b/go.mod index 7cddfb2..af640d1 100644 --- a/go.mod +++ b/go.mod @@ -7,25 +7,21 @@ require ( github.com/fatih/color v1.13.0 github.com/gdamore/tcell/v2 v2.5.3 github.com/gotd/contrib v0.13.0 - github.com/gotd/td v0.72.0 + github.com/gotd/td v0.73.0 github.com/joho/godotenv v1.4.0 github.com/looplab/fsm v0.3.0 github.com/mattn/go-colorable v0.1.13 github.com/rivo/tview v0.0.0-20221128165837-db36428c92d9 github.com/rusq/dlog v1.3.3 - github.com/rusq/encio v0.1.0 + github.com/rusq/mtpwrap v0.0.1 github.com/rusq/osenv/v2 v2.0.1 - github.com/rusq/secure v0.0.4 github.com/rusq/tracer v1.0.1 github.com/schollz/progressbar/v3 v3.12.2 - github.com/stretchr/testify v1.8.1 go.uber.org/zap v1.24.0 - golang.org/x/term v0.3.0 ) require ( github.com/cenkalti/backoff/v4 v4.2.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect github.com/denisbrodbeck/machineid v1.0.1 // indirect github.com/gdamore/encoding v1.0.0 // indirect github.com/go-faster/errors v0.6.1 // indirect @@ -38,8 +34,9 @@ require ( github.com/mattn/go-isatty v0.0.16 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rivo/uniseg v0.4.3 // indirect + github.com/rusq/encio v0.1.0 // indirect + github.com/rusq/secure v0.0.4 // indirect github.com/segmentio/asm v1.2.0 // indirect go.opentelemetry.io/otel v1.11.2 // indirect go.opentelemetry.io/otel/trace v1.11.2 // indirect @@ -49,8 +46,8 @@ require ( golang.org/x/net v0.3.0 // indirect golang.org/x/sync v0.1.0 // indirect golang.org/x/sys v0.3.0 // indirect + golang.org/x/term v0.3.0 // indirect golang.org/x/text v0.5.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect nhooyr.io/websocket v1.8.7 // indirect rsc.io/qr v0.2.0 // indirect ) diff --git a/go.sum b/go.sum index 2803266..b327164 100644 --- a/go.sum +++ b/go.sum @@ -60,8 +60,8 @@ github.com/gotd/ige v0.2.2 h1:XQ9dJZwBfDnOGSTxKXBGP4gMud3Qku2ekScRjDWWfEk= github.com/gotd/ige v0.2.2/go.mod h1:tuCRb+Y5Y3eNTo3ypIfNpQ4MFjrnONiL2jN2AKZXmb0= github.com/gotd/neo v0.1.5 h1:oj0iQfMbGClP8xI59x7fE/uHoTJD7NZH9oV1WNuPukQ= github.com/gotd/neo v0.1.5/go.mod h1:9A2a4bn9zL6FADufBdt7tZt+WMhvZoc5gWXihOPoiBQ= -github.com/gotd/td v0.72.0 h1:JO+iF2cyoUENp7lkdCdByZ6HZXjHNKmISBFAy6Awb8E= -github.com/gotd/td v0.72.0/go.mod h1:3i+HP0uaCObPT47dfIKb4DIO3X56aeWa8EJZmy0EIPE= +github.com/gotd/td v0.73.0 h1:JetBRT7TAbp0u5piM3Kvy2dtEbVZ5tKanlVXo46vydw= +github.com/gotd/td v0.73.0/go.mod h1:3n+OfSs304SUDxXpk+06qi7vn/O6xgFAcYZLxM8FveU= github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -106,6 +106,8 @@ github.com/rusq/dlog v1.3.3 h1:Q9fZW1H/YEnlDg3Ph1k/BRSBfi/q5ezI+8Metws9tTI= github.com/rusq/dlog v1.3.3/go.mod h1:kjZAEvBu7m3+mnJQKoIeLul1YB3kJq/6lZBdDTZmpzA= github.com/rusq/encio v0.1.0 h1:DauNaVtIf79kILExhMGIsE5svYwPnDSksdYP0oVVcr8= github.com/rusq/encio v0.1.0/go.mod h1:AP3lDpo/BkcHcOMNduBlZdd0sbwhruq6+NZtYm5Mxb0= +github.com/rusq/mtpwrap v0.0.1 h1:6yNA7yrRyDTQzlZi2qc5WZDHY6DEIa7/xTuSfNa+PI4= +github.com/rusq/mtpwrap v0.0.1/go.mod h1:O8lUqdPC/8NtsXYKpfYYeL1dCTkSVRTdOVHrBWvU4Zg= github.com/rusq/osenv/v2 v2.0.1 h1:1LtNt8VNV/W86wb38Hyu5W3Rwqt/F1JNRGE+8GRu09o= github.com/rusq/osenv/v2 v2.0.1/go.mod h1:+wJBSisjNZpfoD961JzqjaM+PtaqSusO3b4oVJi7TFY= github.com/rusq/secure v0.0.4 h1:svpiZHfHnx89eEDCCFI9OXG1Y8hL9kUWUG6fJbrWUOI= @@ -117,15 +119,10 @@ github.com/schollz/progressbar/v3 v3.12.2/go.mod h1:HFJYIYQQJX32UJdyoigUl19xoV6a github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= @@ -171,14 +168,12 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g= nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= rsc.io/qr v0.2.0 h1:6vBLea5/NRMVTz8V66gipeLycZMl/+UlFmk8DvqQ6WY= diff --git a/internal/mtp/authflow/auth_term.go b/internal/mtp/authflow/auth_term.go deleted file mode 100644 index e876d17..0000000 --- a/internal/mtp/authflow/auth_term.go +++ /dev/null @@ -1,197 +0,0 @@ -package authflow - -import ( - "bufio" - "context" - "errors" - "fmt" - "io" - "os" - "strconv" - "strings" - "time" - - "github.com/fatih/color" - "github.com/gotd/td/telegram/auth" - "github.com/gotd/td/tg" - "golang.org/x/term" -) - -type FullAuthFlow interface { - auth.UserAuthenticator - - GetAPICredentials(ctx context.Context) (int, string, error) -} - -var ( - blink = color.New(color.BlinkSlow) - italic = color.New(color.Italic) - param = color.New(color.Italic, color.FgBlue, color.BgHiWhite) - warn = color.New(color.FgHiRed) - underline = color.New(color.Underline) - - line = strings.Repeat("-=", 40) -) - -// noSignUp can be embedded to prevent signing up. -type noSignUp struct{} - -func (c noSignUp) SignUp(ctx context.Context) (auth.UserInfo, error) { - return auth.UserInfo{}, errors.New("not implemented") -} - -func (c noSignUp) AcceptTermsOfService(ctx context.Context, tos tg.HelpTermsOfService) error { - return &auth.SignUpRequired{TermsOfService: tos} -} - -// TermAuth implements authentication via terminal. -type TermAuth struct { - noSignUp - - phone string -} - -func NewTermAuth(phone string) TermAuth { - return TermAuth{phone: phone} -} - -func (a TermAuth) Phone(_ context.Context) (string, error) { - clrscr(os.Stdout) - if a.phone != "" { - return a.phone, nil - } - fmt.Printf("Connected, please login to Telegram.\n\n") - fmt.Print("Enter phone: ") - return readln(os.Stdin) -} - -func (a TermAuth) Password(ctx context.Context) (string, error) { - defer fmt.Println() - fmt.Print("Enter 2FA password (won't be shown): ") - return readpass(ctx) -} - -func getCodeSpecifics(code *tg.AuthSentCode) (string, int) { - digits := func(where string, n int) string { - return fmt.Sprintf("The code %s.\nEnter exactly %d digits.", where, n) - } - - switch val := code.Type.(type) { - case *tg.AuthSentCodeTypeApp: - return digits("was sent through the telegram app", val.GetLength()), val.GetLength() - case *tg.AuthSentCodeTypeSMS: - return digits("will be sent via a text message (SMS)", val.GetLength()), val.GetLength() - case *tg.AuthSentCodeTypeCall: - return digits("will be sent via a phone call, and a synthesized voice will tell you what to input", val.GetLength()), val.GetLength() - case *tg.AuthSentCodeTypeFlashCall: - return fmt.Sprintf("The code will be sent via a flash phone call, that will be closed immediately.\nThe phone code will then be the phone number itself, just make sure that the\nphone number matches the specified pattern: %q (%d characters)", val.GetPattern(), len(val.GetPattern())), len(val.GetPattern()) - case *tg.AuthSentCodeTypeMissedCall: - - return fmt.Sprintf("The code will be sent via a flash phone call, that will be closed immediately.\nThe last digits of the phone number that calls are the code that must be entered.\nThe phone call prefix will be: %s and the length of the code is %d", val.GetPrefix(), val.GetLength()), val.GetLength() - default: - return "UNSUPPORTED AUTH TYPE", 0 - } -} - -func getCodeTimeout(code *tg.AuthSentCode) (string, time.Duration) { - timeout, ok := code.GetTimeout() - if !ok { - return "", 30 * time.Minute - } - ret := time.Duration(timeout) * time.Second - return fmt.Sprintf("(enter code within %s)", ret), ret -} - -func (a TermAuth) Code(_ context.Context, code *tg.AuthSentCode) (string, error) { - codeHelp, length := getCodeSpecifics(code) - timeoutHelp, timeoutIn := getCodeTimeout(code) - timeout := time.Now().Add(timeoutIn) - - var input string - var err error - for { - if time.Now().After(timeout) { - return "", errors.New("operation timed out") - } - fmt.Printf("(i) TIP: %s\nEnter code%s: ", codeHelp, timeoutHelp) - input, err = readln(os.Stdin) - if err != nil { - if errors.Is(err, io.EOF) { - return "", errors.New("login aborted") - } - } - if len(input) == length || length == 0 { - break - } - fmt.Println("*** Invalid code, try again [Press Ctrl+C to abort] ***") - } - return input, nil -} - -func (a TermAuth) GetAPICredentials(ctx context.Context) (int, string, error) { - instructions() - var id int - for { - fmt.Printf("Enter App '%s': ", param.Sprint(" api_id ")) - sID, err := readln(os.Stdin) - if err != nil { - return 0, "", err - } - id, err = strconv.Atoi(sID) - if err == nil { - break - } - fmt.Println("*** Input error: api_id should be an integer") - } - fmt.Printf("Enter App '%s' (won't be shown): ", param.Sprint(" api_hash ")) - hash, err := readpass(ctx) - fmt.Println() - if err != nil { - return 0, "", err - } - return id, hash, nil -} - -func instructions() { - - fmt.Println(line) - fmt.Printf("To get the API ID and API Hash, follow the instructions:\n\n") - fmt.Printf("\t1. Login to telegram \"API Development tools\":\n") - fmt.Printf("\t\t%s %s %s\n", blink.Sprint("->"), italic.Sprint("https://my.telegram.org/apps"), blink.Sprint("<-")) - fmt.Printf("\t2. Fill in the form: %s, %s and %s can be any values\n\t you like;\n"+ - "\t3. Choose \"%s\" platform\n"+ - "\t4. Click button.\n\n", - underline.Sprint("App title"), underline.Sprint("Short Name"), underline.Sprint("URL"), - underline.Sprint("Desktop")) - fmt.Printf("You will see the App '%s' and App '%s' values that you will need to\n"+ - "enter shortly. This application will encrypt and save the credentials on your\ndevice. You can delete them any time starting with -reset flag.\n\n", - param.Sprint(" api_id "), param.Sprint(" api_hash ")) - warn.Printf("VERY IMPORTANT: This is the key to your account, keep it secret, never share\n" + - "it with anyone, never publish it online.\n") - fmt.Println(line) - fmt.Println() -} - -func readln(r io.Reader) (string, error) { - line, err := bufio.NewReader(r).ReadString('\n') - if err != nil { - return "", err - } - return strings.TrimSpace(line), nil -} - -func readpass(_ context.Context) (string, error) { - stdin := int(os.Stdin.Fd()) - - oldState, err := term.MakeRaw(stdin) - if err != nil { - return "", err - } - defer term.Restore(stdin, oldState) - - bytePwd, err := term.ReadPassword(stdin) - if err != nil { - return "", err - } - return strings.TrimSpace(string(bytePwd)), nil -} diff --git a/internal/mtp/authflow/util_unix.go b/internal/mtp/authflow/util_unix.go deleted file mode 100644 index 6cc15e0..0000000 --- a/internal/mtp/authflow/util_unix.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build !windows - -package authflow - -import "io" - -func clrscr(w io.Writer) { - w.Write([]byte("\033c")) -} diff --git a/internal/mtp/authflow/util_windows.go b/internal/mtp/authflow/util_windows.go deleted file mode 100644 index 9ea09c3..0000000 --- a/internal/mtp/authflow/util_windows.go +++ /dev/null @@ -1,9 +0,0 @@ -// go:build windows - -package authflow - -import "io" - -func clrscr(w io.Writer) { - return -} diff --git a/internal/mtp/bg/bg.go b/internal/mtp/bg/bg.go deleted file mode 100644 index 123d7ae..0000000 --- a/internal/mtp/bg/bg.go +++ /dev/null @@ -1,82 +0,0 @@ -// Package bg implements wrapper for running client in background. -// -// TODO: Once https://github.com/gotd/contrib/pull/216 is merged can be removed. -package bg - -import ( - "context" - "errors" -) - -// Client abstracts telegram client. -type Client interface { - Run(ctx context.Context, f func(ctx context.Context) error) error -} - -// StopFunc closes Client and waits until Run returns. -type StopFunc func() error - -type connectOptions struct { - ctx context.Context -} - -// Option for Connect. -type Option interface { - apply(o *connectOptions) -} - -type fnOption func(o *connectOptions) - -func (f fnOption) apply(o *connectOptions) { - f(o) -} - -// WithContext sets base context for client. -func WithContext(ctx context.Context) Option { - return fnOption(func(o *connectOptions) { - o.ctx = ctx - }) -} - -// Connect blocks until client is connected, calling Run internally in -// background. -func Connect(client Client, options ...Option) (StopFunc, error) { - opt := &connectOptions{ - ctx: context.Background(), - } - for _, o := range options { - o.apply(opt) - } - - ctx, cancel := context.WithCancel(opt.ctx) - - initDone := make(chan struct{}) - errC := make(chan error, 1) - go func() { - defer close(errC) - errC <- client.Run(ctx, func(ctx context.Context) error { - close(initDone) - <-ctx.Done() - if errors.Is(ctx.Err(), context.Canceled) { - return nil - } - return ctx.Err() - }) - }() - - select { - case <-ctx.Done(): // context cancelled - cancel() - return func() error { return nil }, ctx.Err() - case err := <-errC: // startup timeout - cancel() - return func() error { return nil }, err - case <-initDone: // init done - } - - stopFn := func() error { - cancel() - return <-errC - } - return stopFn, nil -} diff --git a/internal/mtp/creds_storage.go b/internal/mtp/creds_storage.go deleted file mode 100644 index 74fea5b..0000000 --- a/internal/mtp/creds_storage.go +++ /dev/null @@ -1,64 +0,0 @@ -package mtp - -import ( - "encoding/json" - "io" - - "github.com/rusq/encio" -) - -type credsStorage struct { - filename string -} - -// creds is the structure of data in the storage. -type creds struct { - ApiID int `json:"api_id,omitempty"` - ApiHash string `json:"api_hash,omitempty"` -} - -func (cs credsStorage) IsAvailable() bool { - return cs.filename != "" -} - -func (cs credsStorage) Save(apiID int, apiHash string) error { - f, err := encio.Create(cs.filename) - if err != nil { - return err - } - defer f.Close() - - return cs.write(f, apiID, apiHash) -} - -func (cs credsStorage) write(f io.Writer, apiID int, apiHash string) error { - creds := creds{ - ApiID: apiID, - ApiHash: apiHash, - } - - enc := json.NewEncoder(f) - if err := enc.Encode(creds); err != nil { - return err - } - return nil -} - -func (cs credsStorage) Load() (int, string, error) { - f, err := encio.Open(cs.filename) - if err != nil { - return 0, "", err - } - defer f.Close() - - return cs.read(f) -} - -func (cs credsStorage) read(r io.Reader) (int, string, error) { - var creds creds - dec := json.NewDecoder(r) - if err := dec.Decode(&creds); err != nil { - return 0, "", err - } - return creds.ApiID, creds.ApiHash, nil -} diff --git a/internal/mtp/creds_storage_test.go b/internal/mtp/creds_storage_test.go deleted file mode 100644 index f28556c..0000000 --- a/internal/mtp/creds_storage_test.go +++ /dev/null @@ -1,50 +0,0 @@ -package mtp - -import ( - "bytes" - "testing" - - "github.com/stretchr/testify/assert" -) - -func Test_encryptDecrypt(t *testing.T) { - var ( - ApiID = 12345 - ApiHash = "very secure" - ) - var buf bytes.Buffer - cs := credsStorage{} - err := cs.write(&buf, ApiID, ApiHash) - assert.NoError(t, err) - - gotID, gotHash, gotErr := cs.read(&buf) - assert.NoError(t, gotErr) - assert.Equal(t, ApiID, gotID) - assert.Equal(t, ApiHash, gotHash) - -} - -func FuzzWriteRead(f *testing.F) { - type testcase struct { - id int - hash string - } - var testcases = []testcase{{12345, "very secure"}, {0, "12345"}, {42, ""}, {-100, "blah"}} - for _, tc := range testcases { - f.Add(tc.id, tc.hash) - } - cs := credsStorage{} - f.Fuzz(func(t *testing.T, id int, hash string) { - var buf bytes.Buffer - err := cs.write(&buf, id, hash) - if err != nil { - return - } - gotID, gotHash, gotErr := cs.read(&buf) - if gotErr != nil { - return - } - assert.Equal(t, id, gotID) - assert.Equal(t, hash, gotHash) - }) -} diff --git a/internal/mtp/dialogs.go b/internal/mtp/dialogs.go deleted file mode 100644 index ab30014..0000000 --- a/internal/mtp/dialogs.go +++ /dev/null @@ -1,110 +0,0 @@ -package mtp - -import ( - "context" - "errors" - "runtime/trace" - - "github.com/gotd/contrib/storage" - - "github.com/gotd/td/telegram/query/dialogs" - "github.com/gotd/td/tg" -) - -// GetChats retrieves the account chats. -func (c *Client) GetChats(ctx context.Context) ([]Entity, error) { - return c.GetEntities(ctx, FilterChat()) -} - -// GetChannels retrieves the account channels. -func (c *Client) GetChannels(ctx context.Context) ([]Entity, error) { - return c.GetEntities(ctx, FilterChannel()) -} - -// GetEntities ensures that storage is populated, then iterates through storage -// peers calling filterFn for each peer. The filterFn should return Entity and -// true, if the peer satisfies the criteria, or nil and false, otherwise. -func (c *Client) GetEntities(ctx context.Context, filterFn FilterFunc) ([]Entity, error) { - ctx, task := trace.NewTask(ctx, "GetEntities") - defer task.End() - - if err := c.ensureStoragePopulated(ctx); err != nil { - return nil, err - } - - peerIter, err := c.storage.Iterate(ctx) - if err != nil { - return nil, err - } - defer peerIter.Close() - - var ee []Entity - - for peerIter.Next(ctx) { - ent, ok := filterFn(peerIter.Value()) - if !ok { - continue - } - ee = append(ee, ent) - } - if err := peerIter.Err(); err != nil { - return nil, err - } - return ee, nil -} - -// ensureStoragePopulated ensures that the peer storage has been populated within -// defCacheEvict duration. -func (c *Client) ensureStoragePopulated(ctx context.Context) error { - if cached, err := c.cache.Get(cacheDlgStorage); err == nil && cached.(bool) { - trace.Log(ctx, "cache", "hit") - return nil - } - // populating the storage - trace.Log(ctx, "cache", "miss") - - dlgIter := dialogs.NewQueryBuilder(c.cl.API()). - GetDialogs(). - BatchSize(defBatchSize). - Iter() - if err := storage.CollectPeers(c.storage).Dialogs(ctx, dlgIter); err != nil { - return err - } - if err := c.cache.SetWithExpire(cacheDlgStorage, true, defCacheEvict); err != nil { - return err - } - - return nil -} - -// CreateChat creates a Chat (not a Mega- or Gigagroup). -// -// Example -// -// if err := cl.CreateChat(ctx, "mtproto-test",123455678, 312849128); err != nil { -// return err -// } -func (c *Client) CreateChat(ctx context.Context, title string, userIDs ...int64) error { - if len(userIDs) == 0 { - return errors.New("at least one user is required") - } - - var others = make([]tg.InputUserClass, len(userIDs)) - for i := range userIDs { - others[i] = &tg.InputUser{UserID: userIDs[i]} - } - - var users = append([]tg.InputUserClass{&tg.InputUserSelf{}}, others...) - - var resp tg.Updates - if err := c.cl.Invoke(ctx, - &tg.MessagesCreateChatRequest{ - Users: users, - Title: title, - }, - &resp, - ); err != nil { - return err - } - return nil -} diff --git a/internal/mtp/dialogs_filters.go b/internal/mtp/dialogs_filters.go deleted file mode 100644 index a612773..0000000 --- a/internal/mtp/dialogs_filters.go +++ /dev/null @@ -1,25 +0,0 @@ -package mtp - -import "github.com/gotd/contrib/storage" - -type FilterFunc func(storage.Peer) (ent Entity, ok bool) - -func FilterChat() FilterFunc { - return func(peer storage.Peer) (Entity, bool) { - if peer.Chat != nil { - return peer.Chat, true - } else if peer.Channel != nil && !peer.Channel.Broadcast { - return peer.Channel, true - } - return nil, false - } -} - -func FilterChannel() FilterFunc { - return func(peer storage.Peer) (Entity, bool) { - if peer.Channel != nil && peer.Channel.Broadcast { - return peer.Channel, true - } - return nil, false - } -} diff --git a/internal/mtp/messages.go b/internal/mtp/messages.go deleted file mode 100644 index 332ab83..0000000 --- a/internal/mtp/messages.go +++ /dev/null @@ -1,134 +0,0 @@ -package mtp - -import ( - "context" - "fmt" - "runtime/trace" - - "github.com/gotd/td/telegram/message" - "github.com/gotd/td/telegram/query" - "github.com/gotd/td/telegram/query/messages" - "github.com/gotd/td/tg" -) - -// SearchAllMyMessages returns the current authorized user messages from chat or -// channel `dlg`. For each API call, the callback function will be invoked, if -// not nil. -func (c *Client) SearchAllMyMessages(ctx context.Context, dlg Entity, cb func(n int)) ([]messages.Elem, error) { - return c.SearchAllMessages(ctx, dlg, &tg.InputPeerSelf{}, cb) -} - -// SearchAllMessages search messages in the chat or channel `dlg`. It finds ALL -// messages from the person `who`. returns a slice of message.Elem. For each API -// call, the callback function will be invoked, if not nil. -func (c *Client) SearchAllMessages(ctx context.Context, dlg Entity, who tg.InputPeerClass, cb func(n int)) ([]messages.Elem, error) { - if cached, err := c.cache.Get(cacheKey(dlg.GetID())); err == nil { - msgs := cached.([]messages.Elem) - if cb != nil { - cb(len(msgs)) - } - return msgs, nil - } - - ip, err := asInputPeer(dlg) - if err != nil { - return nil, err - } - - bld := query.Messages(c.cl.API()). - Search(ip). - BatchSize(defBatchSize). - FromID(who). - Filter(&tg.InputMessagesFilterEmpty{}) - elems, err := collectMessages(ctx, bld, cb) - if err != nil { - return nil, err - } - - if err := c.cache.Set(cacheKey(dlg.GetID()), elems); err != nil { - return nil, err - } - return elems, err -} - -func (c *Client) DeleteMessages(ctx context.Context, dlg Entity, messages []messages.Elem) (int, error) { - ctx, task := trace.NewTask(ctx, "DeleteMessages") - defer task.End() - - ip, err := asInputPeer(dlg) - if err != nil { - trace.Log(ctx, "logic", err.Error()) - return 0, err - } - ids := splitBy(defBatchSize, messages, func(i int) int { return messages[i].Msg.GetID() }) - trace.Logf(ctx, "logic", "split chunks: %d", len(ids)) - - // clearing cache. - if c.cache.Remove(cacheKey(dlg.GetID())) { - trace.Log(ctx, "logic", "cache cleared") - } - - total := 0 - for _, chunk := range ids { - resp, err := message.NewSender(c.cl.API()).To(ip).Revoke().Messages(ctx, chunk...) - if err != nil { - trace.Logf(ctx, "api", "revoke error: %s", err) - return 0, fmt.Errorf("failed to delete: %w", err) - } - total += resp.GetPtsCount() - } - trace.Log(ctx, "logic", "ok") - return total, nil -} - -func asInputPeer(ent Entity) (tg.InputPeerClass, error) { - switch peer := ent.(type) { - case *tg.Chat: - return peer.AsInputPeer(), nil - case *tg.Channel: - return peer.AsInputPeer(), nil - default: - return nil, fmt.Errorf("unsupported input peer type: %T", peer) - } - // unreachable -} - -// splitBy splits the chunk input of M items to X chunks of `n` items. -// For each element of input, the fn is called, that should return -// the value. -func splitBy[T, S any](n int, input []S, fn func(i int) T) [][]T { - var out [][]T = make([][]T, 0, len(input)/n) - var chunk []T - for i := range input { - if i > 0 && i%n == 0 { - out = append(out, chunk) - chunk = make([]T, 0, n) - } - chunk = append(chunk, fn(i)) - } - if len(chunk) > 0 { - out = append(out, chunk) - } - return out -} - -// collectMessages is the copy/pasta from the td/telegram/message package with added -// optional callback function. It creates iterator and collects all elements to -// slice, calling callback function for each iteration, if it's not nil. -func collectMessages(ctx context.Context, b *messages.SearchQueryBuilder, cb func(n int)) ([]messages.Elem, error) { - iter := b.Iter() - c, err := iter.Total(ctx) - if err != nil { - return nil, fmt.Errorf("get total: %w", err) - } - - r := make([]messages.Elem, 0, c) - for iter.Next(ctx) { - r = append(r, iter.Value()) - if cb != nil { - cb(1) - } - } - - return r, iter.Err() -} diff --git a/internal/mtp/messages_test.go b/internal/mtp/messages_test.go deleted file mode 100644 index a8332c7..0000000 --- a/internal/mtp/messages_test.go +++ /dev/null @@ -1,87 +0,0 @@ -package mtp - -import ( - "reflect" - "testing" -) - -func Test_splitBy(t *testing.T) { - var ( - testInputEven = []int{1, 2, 3, 4, 5} - testInputOdd = []int{1, 2, 3, 4, 5, 6} - testInputSngl = []int{42} - ) - type args struct { - n int - input []int - fn func(i int) int8 - } - tests := []struct { - name string - args args - want [][]int8 - }{ - { - "splits as expected (even)", - args{ - n: 2, - input: testInputEven, - fn: func(i int) int8 { - return int8(testInputEven[i]) - }, - }, - [][]int8{{1, 2}, {3, 4}, {5}}, - }, - { - "splits as expected (odd)", - args{ - n: 2, - input: testInputOdd, - fn: func(i int) int8 { - return int8(testInputOdd[i]) - }, - }, - [][]int8{{1, 2}, {3, 4}, {5, 6}}, - }, - { - "splits as expected (odd)", - args{ - n: 3, - input: testInputOdd, - fn: func(i int) int8 { - return int8(testInputOdd[i]) - }, - }, - [][]int8{{1, 2, 3}, {4, 5, 6}}, - }, - { - "splits as expected (empty)", - args{ - n: 2, - input: []int{}, - fn: func(i int) int8 { - return 0 - }, - }, - [][]int8{}, - }, - { - "splits as expected (one)", - args{ - n: 2, - input: testInputSngl, - fn: func(i int) int8 { - return int8(testInputSngl[i]) - }, - }, - [][]int8{{42}}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := splitBy(tt.args.n, tt.args.input, tt.args.fn); !reflect.DeepEqual(got, tt.want) { - t.Errorf("splitBy() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/internal/mtp/mtp.go b/internal/mtp/mtp.go index 63c8fa3..95a54a1 100644 --- a/internal/mtp/mtp.go +++ b/internal/mtp/mtp.go @@ -17,11 +17,11 @@ import ( "github.com/gotd/td/telegram/auth" "github.com/mattn/go-colorable" "github.com/rusq/dlog" + "github.com/rusq/mtpwrap/authflow" "go.uber.org/zap" "go.uber.org/zap/zapcore" - "github.com/rusq/wipemychat/internal/mtp/authflow" - "github.com/rusq/wipemychat/internal/mtp/bg" + "github.com/gotd/contrib/bg" ) const ( diff --git a/internal/mtp/peer_storage.go b/internal/mtp/peer_storage.go deleted file mode 100644 index 972f0e8..0000000 --- a/internal/mtp/peer_storage.go +++ /dev/null @@ -1,129 +0,0 @@ -package mtp - -import ( - "context" - "errors" - "sort" - "sync" - - "github.com/gotd/contrib/storage" -) - -// MemStorage is the default peer storage for MTP. It uses a map to store all -// peers, hence, it's not a persistent store. -type MemStorage struct { - s map[string]storage.Peer - - mu sync.RWMutex - iterating bool - - // iterator - keys []string - keyIdx int - - iterErr error -} - -func NewMemStorage() *MemStorage { - return &MemStorage{ - s: make(map[string]storage.Peer, 0), - } -} - -func (ms *MemStorage) Add(_ context.Context, value storage.Peer) error { - ms.mu.Lock() - defer ms.mu.Unlock() - - key := storage.KeyFromPeer(value).String() - ms.s[key] = value - return nil -} - -func (ms *MemStorage) Find(ctx context.Context, key storage.PeerKey) (storage.Peer, error) { - return ms.Resolve(ctx, key.String()) -} - -func (ms *MemStorage) Assign(_ context.Context, key string, value storage.Peer) error { - ms.mu.Lock() - defer ms.mu.Unlock() - - ms.s[key] = value - - return nil -} - -func (ms *MemStorage) Resolve(_ context.Context, key string) (storage.Peer, error) { - ms.mu.RLock() - defer ms.mu.RUnlock() - - peer, ok := ms.s[key] - if !ok { - return storage.Peer{}, storage.ErrPeerNotFound - } - return peer, nil -} - -func (ms *MemStorage) Iterate(ctx context.Context) (storage.PeerIterator, error) { - select { - case <-ctx.Done(): - return nil, ctx.Err() - default: - } - if ms.IsIterating() { - return nil, errors.New("already iterating") - } - - // preparing the iterator - ms.mu.Lock() - ms.keys = make([]string, 0, len(ms.s)) - for k := range ms.s { - ms.keys = append(ms.keys, k) - } - sort.Strings(ms.keys) - ms.keyIdx = -1 // set the passphrase start value - - ms.iterating = true - ms.iterErr = nil - ms.mu.Unlock() - - // locking for iteration - ms.mu.RLock() - return ms, nil -} - -func (ms *MemStorage) Next(ctx context.Context) bool { - select { - case <-ctx.Done(): - ms.iterErr = ctx.Err() - return false - default: - } - ms.keyIdx++ - return ms.keyIdx < len(ms.keys) -} - -func (ms *MemStorage) Err() error { - return ms.iterErr -} - -func (ms *MemStorage) Value() storage.Peer { - if !ms.IsIterating() { - return storage.Peer{} - } - return ms.s[ms.keys[ms.keyIdx]] -} - -func (ms *MemStorage) Close() error { - if !ms.IsIterating() { - return nil - } - ms.mu.RUnlock() - ms.mu.Lock() - ms.iterating = false - ms.mu.Unlock() - return nil -} - -func (ms *MemStorage) IsIterating() bool { - return ms.iterating -} diff --git a/internal/tui/app.go b/internal/tui/app.go index 62c7a54..ebd9f0f 100644 --- a/internal/tui/app.go +++ b/internal/tui/app.go @@ -10,7 +10,7 @@ import ( "github.com/rusq/dlog" "github.com/rusq/osenv/v2" - "github.com/rusq/wipemychat/internal/mtp" + mtp "github.com/rusq/mtpwrap" "github.com/rusq/wipemychat/internal/waipu" ) diff --git a/internal/tui/chatlist.go b/internal/tui/chatlist.go index 2b17816..330132c 100644 --- a/internal/tui/chatlist.go +++ b/internal/tui/chatlist.go @@ -7,7 +7,7 @@ import ( "github.com/gdamore/tcell/v2" "github.com/rivo/tview" - "github.com/rusq/wipemychat/internal/mtp" + mtp "github.com/rusq/mtpwrap" ) const infoText = "Press [Ctrl+Q] or [F10] to quit, [Ctrl+F] or [/] to search chats" diff --git a/internal/tui/confirm.go b/internal/tui/confirm.go index 5589889..adb1e23 100644 --- a/internal/tui/confirm.go +++ b/internal/tui/confirm.go @@ -7,7 +7,7 @@ import ( "github.com/gdamore/tcell/v2" "github.com/gotd/td/telegram/query/messages" - "github.com/rusq/wipemychat/internal/mtp" + mtp "github.com/rusq/mtpwrap" ) func (app *App) initConfirm() { diff --git a/internal/waipu/batch.go b/internal/waipu/batch.go index 0fc4666..4d74244 100644 --- a/internal/waipu/batch.go +++ b/internal/waipu/batch.go @@ -6,7 +6,7 @@ import ( "fmt" "github.com/rusq/dlog" - "github.com/rusq/wipemychat/internal/mtp" + mtp "github.com/rusq/mtpwrap" "github.com/schollz/progressbar/v3" ) diff --git a/internal/waipu/cli.go b/internal/waipu/cli.go index 8aca309..f9e363d 100644 --- a/internal/waipu/cli.go +++ b/internal/waipu/cli.go @@ -4,7 +4,7 @@ import ( "context" "github.com/gotd/td/telegram/query/messages" - "github.com/rusq/wipemychat/internal/mtp" + mtp "github.com/rusq/mtpwrap" ) type Telegramer interface { diff --git a/main.go b/main.go index 08940d6..db47f5b 100644 --- a/main.go +++ b/main.go @@ -23,8 +23,8 @@ import ( "github.com/rusq/tracer" "github.com/schollz/progressbar/v3" - "github.com/rusq/wipemychat/internal/mtp" - "github.com/rusq/wipemychat/internal/mtp/authflow" + mtp "github.com/rusq/mtpwrap" + "github.com/rusq/mtpwrap/authflow" "github.com/rusq/wipemychat/internal/tui" "github.com/rusq/wipemychat/internal/waipu" ) From 616105ea2e1502c28801301c8e7868beae7f9f8b Mon Sep 17 00:00:00 2001 From: Rustam Gilyazov <16064414+rusq@users.noreply.github.com> Date: Fri, 23 Dec 2022 08:47:49 +1000 Subject: [PATCH 2/2] remove a stray file --- go.mod | 8 +- internal/mtp/mtp.go | 220 -------------------------------------------- 2 files changed, 4 insertions(+), 224 deletions(-) delete mode 100644 internal/mtp/mtp.go diff --git a/go.mod b/go.mod index af640d1..685f384 100644 --- a/go.mod +++ b/go.mod @@ -3,34 +3,33 @@ module github.com/rusq/wipemychat go 1.18 require ( - github.com/bluele/gcache v0.0.2 github.com/fatih/color v1.13.0 github.com/gdamore/tcell/v2 v2.5.3 - github.com/gotd/contrib v0.13.0 github.com/gotd/td v0.73.0 github.com/joho/godotenv v1.4.0 github.com/looplab/fsm v0.3.0 - github.com/mattn/go-colorable v0.1.13 github.com/rivo/tview v0.0.0-20221128165837-db36428c92d9 github.com/rusq/dlog v1.3.3 github.com/rusq/mtpwrap v0.0.1 github.com/rusq/osenv/v2 v2.0.1 github.com/rusq/tracer v1.0.1 github.com/schollz/progressbar/v3 v3.12.2 - go.uber.org/zap v1.24.0 ) require ( + github.com/bluele/gcache v0.0.2 // indirect github.com/cenkalti/backoff/v4 v4.2.0 // indirect github.com/denisbrodbeck/machineid v1.0.1 // indirect github.com/gdamore/encoding v1.0.0 // indirect github.com/go-faster/errors v0.6.1 // indirect github.com/go-faster/jx v0.40.0 // indirect github.com/go-faster/xor v0.3.0 // indirect + github.com/gotd/contrib v0.13.0 // indirect github.com/gotd/ige v0.2.2 // indirect github.com/gotd/neo v0.1.5 // indirect github.com/klauspost/compress v1.15.12 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.16 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect @@ -42,6 +41,7 @@ require ( go.opentelemetry.io/otel/trace v1.11.2 // indirect go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.8.0 // indirect + go.uber.org/zap v1.24.0 // indirect golang.org/x/crypto v0.3.0 // indirect golang.org/x/net v0.3.0 // indirect golang.org/x/sync v0.1.0 // indirect diff --git a/internal/mtp/mtp.go b/internal/mtp/mtp.go deleted file mode 100644 index 95a54a1..0000000 --- a/internal/mtp/mtp.go +++ /dev/null @@ -1,220 +0,0 @@ -// Package mtp provides some functions for the gotd/td functions -package mtp - -import ( - "context" - "errors" - "fmt" - "io" - "time" - - "github.com/bluele/gcache" - "github.com/gotd/contrib/middleware/floodwait" - "github.com/gotd/contrib/storage" - "github.com/gotd/td/session" - "github.com/gotd/td/tdp" - "github.com/gotd/td/telegram" - "github.com/gotd/td/telegram/auth" - "github.com/mattn/go-colorable" - "github.com/rusq/dlog" - "github.com/rusq/mtpwrap/authflow" - "go.uber.org/zap" - "go.uber.org/zap/zapcore" - - "github.com/gotd/contrib/bg" -) - -const ( - defBatchSize = 100 - defCacheEvict = 10 * time.Minute - defCacheSz = 20 -) - -var ( - // ErrAlreadyRunning is returned if the attempt is made to start the client, - // while there's another instance running asynchronously. - ErrAlreadyRunning = errors.New("already running asynchronously, stop the running instance first") -) - -type Client struct { - cl *telegram.Client - - cache gcache.Cache - storage storage.PeerStorage - creds credsStorage - - waiter *floodwait.SimpleWaiter - waiterStop func() - - stop bg.StopFunc - - auth authflow.FullAuthFlow - sendcodeOpts auth.SendCodeOptions - telegramOpts telegram.Options -} - -// Entity interface is the subset of functions that are commonly defined on most -// entities in telegram lib. It can be a user, a chat or channel, or any other -// telegram Entity. -type Entity interface { - GetID() int64 - GetTitle() string - TypeInfo() tdp.Type - Zero() bool -} - -type cacheKey int64 - -const ( - cacheDlgStorage cacheKey = iota -) - -type Option func(c *Client) - -func WithMTPOptions(opts telegram.Options) Option { - return func(c *Client) { - c.telegramOpts = opts - } -} - -// WithStorage allows to specify custom session storage. -func WithStorage(path string) Option { - return func(c *Client) { - c.telegramOpts.SessionStorage = &session.FileStorage{Path: path} - } -} - -// WithPeerStorage allows to specify a custom storage for peer data. -func WithPeerStorage(s storage.PeerStorage) Option { - return func(c *Client) { - if s == nil { - return - } - c.storage = s - } -} - -// WithAuth allows to override the authorization flow -func WithAuth(flow authflow.FullAuthFlow) Option { - return func(c *Client) { - c.auth = flow - } -} - -func WithApiCredsFile(path string) Option { - return func(c *Client) { - c.creds = credsStorage{filename: path} - } -} - -func WithDebug(enable bool) Option { - return func(c *Client) { - if !enable { - c.telegramOpts.Logger = nil - return - } - cfg := zap.NewDevelopmentEncoderConfig() - cfg.EncodeLevel = zapcore.CapitalColorLevelEncoder - c.telegramOpts.Logger = zap.New(zapcore.NewCore( - zapcore.NewConsoleEncoder(cfg), - zapcore.AddSync(colorable.NewColorableStdout()), - zapcore.DebugLevel, - )) - } -} - -func New(appID int, appHash string, opts ...Option) (*Client, error) { - // Client with the default parameters - var c = Client{ - cache: gcache.New(defCacheSz).LFU().Expiration(defCacheEvict).Build(), - storage: NewMemStorage(), - - auth: authflow.TermAuth{}, // default is the terminal authentication - waiter: floodwait.NewSimpleWaiter(), - - telegramOpts: telegram.Options{}, - } - - for _, opt := range opts { - opt(&c) - } - - c.telegramOpts.Middlewares = append(c.telegramOpts.Middlewares, c.waiter) - if (appID == 0 || appHash == "") && c.creds.IsAvailable() { - var err error - appID, appHash, err = c.loadCredentials() - if err != nil { - return nil, err - } - } - - c.cl = telegram.NewClient(appID, appHash, c.telegramOpts) - - return &c, nil -} - -func (c *Client) loadCredentials() (int, string, error) { - var err error - apiID, apiHash, err := c.creds.Load() - if err == nil && apiID > 0 && apiHash != "" { - return apiID, apiHash, nil - } - dlog.Debugf("warning: error loading credentials file, requesting manual input: %s", err) - apiID, apiHash, err = c.auth.GetAPICredentials(context.Background()) - if err != nil { - fmt.Println() - if errors.Is(io.EOF, err) { - return 0, "", errors.New("exit") - } - return 0, "", err - } - if err := c.creds.Save(apiID, apiHash); err != nil { - // not a fatal error - dlog.Debugf("failed to save credentials: %s", err) - } - return apiID, apiHash, nil -} - -// Start starts the telegram session in goroutine -func (c *Client) Start(ctx context.Context) error { - if c.stop != nil { - return ErrAlreadyRunning - } - - stop, err := bg.Connect(c.cl) - if err != nil { - return err - } - c.stop = stop - - flow := auth.NewFlow(c.auth, c.sendcodeOpts) - if err := c.cl.Auth().IfNecessary(ctx, flow); err != nil { - if err := c.Stop(); err != nil { - dlog.Debugf("error stopping: %s", err) - } - return err - } - dlog.Debug("auth success") - - return nil -} - -func (c *Client) Stop() error { - if c.stop != nil { - if c.waiterStop != nil { - defer c.waiterStop() - } - return c.stop() - } - return nil -} - -// Run runs an arbitrary telegram session. -func (c *Client) Run(ctx context.Context, fn func(context.Context, *telegram.Client) error) error { - if c.stop != nil { - return ErrAlreadyRunning - } - return c.cl.Run(ctx, func(ctx context.Context) error { - return fn(ctx, c.cl) - }) -}