From a92f9ec1ac4cf641004607c671d44518d4ef0279 Mon Sep 17 00:00:00 2001 From: Rustam Gilyazov <16064414+rusq@users.noreply.github.com> Date: Wed, 3 Aug 2022 21:31:17 +1000 Subject: [PATCH] interactive mode --- auth/auth_ui/survey.go | 42 +++++++++ auth/browser.go | 2 +- cmd/slackdump/interactive.go | 159 +++++++++++++++++++++++++++++++++++ cmd/slackdump/main.go | 14 ++- go.mod | 3 + go.sum | 15 ++++ internal/app/config.go | 2 +- internal/app/dump.go | 2 +- 8 files changed, 235 insertions(+), 4 deletions(-) create mode 100644 auth/auth_ui/survey.go create mode 100644 cmd/slackdump/interactive.go diff --git a/auth/auth_ui/survey.go b/auth/auth_ui/survey.go new file mode 100644 index 00000000..4c0a2719 --- /dev/null +++ b/auth/auth_ui/survey.go @@ -0,0 +1,42 @@ +package auth_ui + +import ( + "io" + + "github.com/AlecAivazis/survey/v2" +) + +type Survey struct{} + +func (*Survey) RequestWorkspace(w io.Writer) (string, error) { + return surveyInput( + "Enter Slack Workspace Name: ", + "HELP:\n1. Enter the slack workspace name or paste the URL of your slack workspace.\n"+ + "2. Browser will open, login as usual.\n"+ + "3. Browser will close and slackdump will be authenticated.\n\n"+ + "This must be done only once. The credentials are saved in an encrypted\nfile, and can be used only on this device.", + survey.Required, + ) +} + +func (*Survey) Stop() {} + +func surveyInput(msg, help string, validator survey.Validator) (string, error) { + qs := []*survey.Question{ + { + Name: "value", + Validate: validator, + Prompt: &survey.Input{ + Message: msg, + Help: help, + }, + }, + } + var m = struct { + Value string + }{} + if err := survey.Ask(qs, &m); err != nil { + return "", err + } + return m.Value, nil +} diff --git a/auth/browser.go b/auth/browser.go index 62fa79f6..a5a43ce6 100644 --- a/auth/browser.go +++ b/auth/browser.go @@ -14,7 +14,7 @@ import ( ) var _ Provider = BrowserAuth{} -var defaultFlow = &auth_ui.CLI{} +var defaultFlow = &auth_ui.Survey{} type BrowserAuth struct { simpleProvider diff --git a/cmd/slackdump/interactive.go b/cmd/slackdump/interactive.go new file mode 100644 index 00000000..04375bc4 --- /dev/null +++ b/cmd/slackdump/interactive.go @@ -0,0 +1,159 @@ +package main + +import ( + "errors" + "fmt" + "strings" + + "github.com/AlecAivazis/survey/v2" + "github.com/rusq/slackdump/v2/internal/app" + "github.com/rusq/slackdump/v2/internal/structures" +) + +var errExit = errors.New("exit") + +func Interactive(p *params) error { + mode := &survey.Select{ + Message: "Choose Slackdump Mode: ", + Options: []string{"Dump", "Export", "List", "- Options", "Exit"}, + } + var resp string + if err := survey.AskOne(mode, &resp); err != nil { + return err + } + var err error + switch resp { + case "Exit": + err = errExit + case "- Options": + // + case "Dump": + // + err = surveyDump(p) + case "Export": + // + err = surveyExport(p) + case "List": + err = surveyList(p) + } + return err +} + +func surveyList(p *params) error { + qs := []*survey.Question{ + { + Name: "entity", + Validate: survey.Required, + Prompt: &survey.Select{ + Message: "List: ", + Options: []string{"Channels", "Users"}, + Description: func(value string, index int) string { + return "List Slack " + value + }, + }, + }, + { + Name: "format", + Validate: survey.Required, + Prompt: &survey.Select{ + Message: "Report format: ", + Options: []string{app.OutputTypeText, app.OutputTypeJSON}, + Description: func(value string, index int) string { + return "generate report in " + value + " format" + }, + }, + }, + } + + mode := struct { + Entity string + Format string + }{} + + if err := survey.Ask(qs, &mode); err != nil { + return err + } + + switch mode.Entity { + case "Channels": + p.appCfg.ListFlags.Channels = true + case "Users": + p.appCfg.ListFlags.Users = true + } + p.appCfg.Output.Format = mode.Format + + return nil +} + +func surveyExport(p *params) error { + var err error + p.appCfg.ExportName, err = svMustInputString( + "Output directory or ZIP file: ", + "Enter the output directory or ZIP file name. Add \".zip\" to save to zip file", + ) + if err != nil { + return err + } + p.appCfg.Input.List, err = surveyChanList() + if err != nil { + return err + } + return nil +} + +func surveyDump(p *params) error { + var err error + p.appCfg.Input.List, err = surveyChanList() + return err +} + +func surveyInput(msg, help string, validator survey.Validator) (string, error) { + qs := []*survey.Question{ + { + Name: "value", + Validate: validator, + Prompt: &survey.Input{ + Message: msg, + Help: help, + }, + }, + } + var m = struct { + Value string + }{} + if err := survey.Ask(qs, &m); err != nil { + return "", err + } + return m.Value, nil +} + +func svMustInputString(msg, help string) (string, error) { + return surveyInput(msg, help, survey.Required) +} + +func svInputString(msg, help string) (string, error) { + return surveyInput(msg, help, nil) +} + +func surveyChanList() (*structures.EntityList, error) { + for { + chanStr, err := svInputString( + "List of channels: ", + "Enter whitespace separated channel IDs or URLs to export.\n"+ + " - prefix with ^ (carret) to exclude the channel\n"+ + " - prefix with @ to read the list of channels from the file.\n\n"+ + "For more details, see https://github.com/rusq/slackdump/blob/master/doc/usage-export.rst#providing-the-list-in-a-file", + ) + if err != nil { + return nil, err + } + if chanStr == "" { + return new(structures.EntityList), nil + } + if el, err := structures.MakeEntityList(strings.Split(chanStr, " ")); err != nil { + fmt.Println(err) + } else { + return el, nil + } + } +} diff --git a/cmd/slackdump/main.go b/cmd/slackdump/main.go index d0ab2c02..ae77bbad 100644 --- a/cmd/slackdump/main.go +++ b/cmd/slackdump/main.go @@ -66,7 +66,19 @@ func main() { params, err := parseCmdLine(os.Args[1:]) if err != nil { - log.Fatal(err) + if err == app.ErrNothingToDo { + if err := Interactive(¶ms); err != nil { + if err == errExit { + return + } + log.Fatal(err) + } + if err := params.validate(); err != nil { + log.Fatal(err) + } + } else { + log.Fatal(err) + } } if params.printVersion { diff --git a/go.mod b/go.mod index 8476e6c0..5192a25e 100644 --- a/go.mod +++ b/go.mod @@ -22,15 +22,18 @@ require ( ) require ( + github.com/AlecAivazis/survey/v2 v2.3.5 // indirect github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/gdamore/encoding v1.0.0 // indirect github.com/go-stack/stack v1.8.1 // indirect github.com/gorilla/websocket v1.5.0 // indirect + github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect + github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // 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.3.1 // indirect diff --git a/go.sum b/go.sum index fb57d77f..f8cc3ae5 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,9 @@ +github.com/AlecAivazis/survey/v2 v2.3.5 h1:A8cYupsAZkjaUmhtTYv3sSqc7LO5mp1XDfqe5E/9wRQ= +github.com/AlecAivazis/survey/v2 v2.3.5/go.mod h1:4AuI9b7RjAR+G7v9+C4YSlX/YL3K3cWNXgWXOhllqvI= github.com/MercuryEngineering/CookieMonster v0.0.0-20180304172713-1584578b3403 h1:EtZwYyLbkEcIt+B//6sujwRCnHuTEK3qiSypAX5aJeM= github.com/MercuryEngineering/CookieMonster v0.0.0-20180304172713-1584578b3403/go.mod h1:mM6WvakkX2m+NgMiPCfFFjwfH4KzENC07zeGEqq9U7s= +github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= +github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ= github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -26,19 +30,26 @@ github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/h2non/filetype v1.1.1/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= +github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= 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/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= github.com/playwright-community/playwright-go v0.2000.1 h1:2JViSHpJQ/UL/PO1Gg6gXV5IcXAAsoBJ3KG9L3wKXto= @@ -65,6 +76,7 @@ github.com/schollz/progressbar/v3 v3.8.6/go.mod h1:W5IEwbJecncFGBvuEh4A7HT1nZZ6W github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -82,6 +94,7 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -95,11 +108,13 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220318055525-2edf467146b5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220730100132-1609e554cd39 h1:aNCnH+Fiqs7ZDTFH6oEFjIfbX2HvgQXJ6uQuUbTobjk= golang.org/x/sys v0.0.0-20220730100132-1609e554cd39/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 h1:Q5284mrmYTpACcm+eAKjKJH48BBwSyfJqmmGDTtT8Vc= golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= diff --git a/internal/app/config.go b/internal/app/config.go index 9434b09e..f908eb64 100644 --- a/internal/app/config.go +++ b/internal/app/config.go @@ -117,7 +117,7 @@ func (p *Config) Validate() error { } if !p.ListFlags.FlagsPresent() && !p.Output.FormatValid() { - return fmt.Errorf("invalid Output type: %q, must use one of %v", p.Output.Format, []string{OutputTypeJSON, OutputTypeText}) + return fmt.Errorf("invalid output type: %q, must use one of %v", p.Output.Format, []string{OutputTypeJSON, OutputTypeText}) } // validate file naming template diff --git a/internal/app/dump.go b/internal/app/dump.go index 5d6fc825..55d217a2 100644 --- a/internal/app/dump.go +++ b/internal/app/dump.go @@ -229,5 +229,5 @@ func (app *dump) formatEntity(w io.Writer, rep reporter, output Output) error { enc := json.NewEncoder(w) return enc.Encode(rep) } - return errors.New("invalid Output format") + return errors.New("invalid output format") }