Skip to content

Commit

Permalink
Merge pull request #345 from rusq/ui
Browse files Browse the repository at this point in the history
ui
  • Loading branch information
rusq authored Nov 2, 2024
2 parents 8874833 + fb1db71 commit 2895662
Show file tree
Hide file tree
Showing 42 changed files with 1,347 additions and 258 deletions.
4 changes: 2 additions & 2 deletions auth/auth_ui/auth_ui.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ const (
LInteractive LoginType = iota
// LHeadless is the email/password login type.
LHeadless
// LGoogleAuth is the google auth option
LGoogleAuth
// LUserBrowser is the google auth option
LUserBrowser
// LCancel should be returned if the user cancels the login intent.
LCancel
)
Expand Down
2 changes: 1 addition & 1 deletion auth/auth_ui/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func (cl *CLI) RequestLoginType(w io.Writer) (LoginType, error) {
value LoginType
}{
{"Email", LHeadless},
{"Google", LGoogleAuth},
{"Google", LUserBrowser},
{"Apple", LInteractive},
{"Login with Single-Sign-On (SSO)", LInteractive},
{"Other/Manual", LInteractive},
Expand Down
98 changes: 85 additions & 13 deletions auth/auth_ui/huh.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"strconv"

"github.com/charmbracelet/huh"
"github.com/rusq/slackauth"
)

// Huh is the Auth UI that uses the huh library to provide a terminal UI.
Expand Down Expand Up @@ -48,20 +49,52 @@ func (*Huh) RequestCreds(w io.Writer, workspace string) (email string, passwd st
return
}

func (*Huh) RequestLoginType(w io.Writer) (LoginType, error) {
type methodMenuItem struct {
MenuItem string
ShortDesc string
Type LoginType
}

func (m methodMenuItem) String() string {
return fmt.Sprintf("%-20s - %s", m.MenuItem, m.ShortDesc)
}

var methods = []methodMenuItem{
{
"Manual",
"Works with most authentication schemes, except Google.",
LInteractive,
},
{
"Automatic",
"Only suitable for email/password auth",
LHeadless,
},
{
"User's Browser",
"Loads your user profile, works with Google Auth",
LUserBrowser,
},
}

type LoginOpts struct {
Type LoginType
BrowserPath string
}

func (*Huh) RequestLoginType(w io.Writer) (LoginOpts, error) {
var opts = make([]huh.Option[LoginType], 0, len(methods))
for _, m := range methods {
opts = append(opts, huh.NewOption(m.String(), m.Type))
}
opts = append(opts,
huh.NewOption("------", LoginType(-1)),
huh.NewOption("Cancel", LCancel),
)
var loginType LoginType
err := huh.NewForm(huh.NewGroup(
huh.NewSelect[LoginType]().Title("Select login type").
Options(
huh.NewOption("Email (manual)", LInteractive),
huh.NewOption("Email (automatic)", LHeadless),
huh.NewOption("Google", LGoogleAuth),
huh.NewOption("Apple", LInteractive),
huh.NewOption("Login with Single-Sign-On (SSO)", LInteractive),
huh.NewOption("Other/Manual", LInteractive),
huh.NewOption("", LoginType(-1)),
huh.NewOption("Cancel", LCancel),
).
Options(opts...).
Value(&loginType).
Validate(valSepEaster()).
DescriptionFunc(func() string {
Expand All @@ -70,7 +103,7 @@ func (*Huh) RequestLoginType(w io.Writer) (LoginType, error) {
return "Clean browser will open on a Slack Login page."
case LHeadless:
return "You will be prompted to enter your email and password, login is automated."
case LGoogleAuth:
case LUserBrowser:
return "System browser will open on a Slack Login page."
case LCancel:
return "Cancel the login process."
Expand All @@ -79,7 +112,46 @@ func (*Huh) RequestLoginType(w io.Writer) (LoginType, error) {
}
}, &loginType),
)).Run()
return loginType, err
if err != nil {
return LoginOpts{Type: LCancel}, err
}
if loginType == LUserBrowser {
path, err := chooseBrowser()
if err != nil {
return LoginOpts{Type: LCancel}, err
}
return LoginOpts{
Type: LUserBrowser,
BrowserPath: path,
}, err
}
return LoginOpts{Type: loginType}, nil
}

func chooseBrowser() (string, error) {
browsers, err := slackauth.ListBrowsers()
if err != nil {
return "", err
}
var opts = make([]huh.Option[int], 0, len(browsers))
for i, b := range browsers {
opts = append(opts, huh.NewOption(b.Name, i))
}

var selection int
err = huh.NewForm(huh.NewGroup(
huh.NewSelect[int]().
Title("Detected browsers on your system").
Options(opts...).
Value(&selection).
DescriptionFunc(func() string {
return browsers[selection].Path
}, &selection),
)).Run()
if err != nil {
return "", err
}
return browsers[selection].Path, nil
}

// ConfirmationCode asks the user to input the confirmation code, does some
Expand Down
10 changes: 5 additions & 5 deletions auth/rod.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ type browserAuthUIExt interface {
// RequestLoginType should request the login type from the user and return
// one of the [auth_ui.LoginType] constants. The implementation should
// provide a way to cancel the login flow, returning [auth_ui.LoginCancel].
RequestLoginType(w io.Writer) (auth_ui.LoginType, error)
RequestLoginType(w io.Writer) (auth_ui.LoginOpts, error)
// RequestCreds should request the user's email and password and return
// them.
RequestCreds(w io.Writer, workspace string) (email string, passwd string, err error)
Expand Down Expand Up @@ -110,10 +110,10 @@ func NewRODAuth(ctx context.Context, opts ...Option) (RodAuth, error) {
return r, err
}
sopts := r.opts.slackauthOpts()
if resp == auth_ui.LGoogleAuth {
if resp.Type == auth_ui.LUserBrowser {
// it doesn't need to know that this browser is just a puppet in the
// masterful hands.
sopts = append(sopts, slackauth.WithForceUser())
sopts = append(sopts, slackauth.WithForceUser(), slackauth.WithLocalBrowser(resp.BrowserPath))
}

cl, err := slackauth.New(
Expand All @@ -128,8 +128,8 @@ func NewRODAuth(ctx context.Context, opts ...Option) (RodAuth, error) {
lg := logger.FromContext(ctx)
t := time.Now()
var sp simpleProvider
switch resp {
case auth_ui.LInteractive, auth_ui.LGoogleAuth:
switch resp.Type {
case auth_ui.LInteractive, auth_ui.LUserBrowser:
lg.Printf("ℹ️ Initialising browser, once the browser appears, login as usual")
var err error
sp.Token, sp.Cookie, err = cl.Manual(ctx)
Expand Down
6 changes: 3 additions & 3 deletions cmd/slackdump/internal/archive/wizard.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import (

func archiveWizard(ctx context.Context, cmd *base.Command, args []string) error {
w := &dumpui.Wizard{
Title: "Archive Slack Workspace",
Particulars: "Archive",
Cmd: cmd,
Title: "Archive Slack Workspace",
Name: "Archive",
Cmd: cmd,
}
return w.Run(ctx)
}
1 change: 1 addition & 0 deletions cmd/slackdump/internal/diag/tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,6 @@ Tools command contains different tools, running which may be requested if you op
CmdRecord,
cmdSearch,
CmdThread,
CmdWizDebug,
},
}
93 changes: 93 additions & 0 deletions cmd/slackdump/internal/diag/wizdebug.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package diag

import (
"context"

tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/huh"
"github.com/rusq/slackdump/v3/cmd/slackdump/internal/golang/base"
"github.com/rusq/slackdump/v3/cmd/slackdump/internal/ui/cfgui"
"github.com/rusq/slackdump/v3/cmd/slackdump/internal/ui/dumpui"
)

var CmdWizDebug = &base.Command{
UsageLine: "slackdump tools wizdebug",
Short: "run the wizard debug command",
Run: runWizDebug,
PrintFlags: true,
}

type wdWhat int

const (
wdExit wdWhat = iota
wdDumpUI
wdConfigUI
)

func runWizDebug(ctx context.Context, cmd *base.Command, args []string) error {
var action wdWhat
for {
form := huh.NewForm(
huh.NewGroup(
huh.NewSelect[wdWhat]().Options(
huh.NewOption("Dump UI", wdDumpUI),
huh.NewOption("Global Config UI", wdConfigUI),
).Value(&action),
).WithHeight(10),
)

if err := form.RunWithContext(ctx); err != nil {
return err
}
switch action {
case wdDumpUI:
if err := debugDumpUI(ctx); err != nil {
return err
}
case wdConfigUI:
if err := debugConfigUI(ctx); err != nil {
return err
}
case wdExit:
return nil
}
}
}

func debugDumpUI(ctx context.Context) error {
menu := []dumpui.MenuItem{
{
ID: "run",
Name: "Run",
Help: "Run the command",
},
{
Name: "Global Configuration...",
Help: "Set global configuration options",
Model: cfgui.NewConfigUI(cfgui.DefaultStyle(), cfgui.GlobalConfig),
},
{
Name: "Local Configuration...",
Help: "Set command specific configuration options",
},
{
Separator: true,
},
{
Name: "Exit",
Help: "Exit to main menu",
},
}
w := dumpui.NewModel("Wizard Debug", menu, false)

if _, err := tea.NewProgram(w, tea.WithContext(ctx)).Run(); err != nil {
return err
}

return nil
}

func debugConfigUI(ctx context.Context) error {
return cfgui.Global(ctx)
}
3 changes: 1 addition & 2 deletions cmd/slackdump/internal/dump/dump.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,8 @@ import (
"time"

"github.com/rusq/fsadapter"
"github.com/rusq/slackdump/v3/cmd/slackdump/internal/bootstrap"

"github.com/rusq/slackdump/v3"
"github.com/rusq/slackdump/v3/cmd/slackdump/internal/bootstrap"
"github.com/rusq/slackdump/v3/cmd/slackdump/internal/cfg"
"github.com/rusq/slackdump/v3/cmd/slackdump/internal/golang/base"
"github.com/rusq/slackdump/v3/downloader"
Expand Down
59 changes: 57 additions & 2 deletions cmd/slackdump/internal/dump/wizard.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,71 @@ package dump

import (
"context"
"strings"

"github.com/rusq/slackdump/v3/cmd/slackdump/internal/golang/base"
"github.com/rusq/slackdump/v3/cmd/slackdump/internal/ui/cfgui"
"github.com/rusq/slackdump/v3/cmd/slackdump/internal/ui/dumpui"
"github.com/rusq/slackdump/v3/cmd/slackdump/internal/ui/updaters"
"github.com/rusq/slackdump/v3/internal/nametmpl"
"github.com/rusq/slackdump/v3/internal/structures"
)

func WizDump(ctx context.Context, cmd *base.Command, args []string) error {
w := dumpui.Wizard{
Title: "Dump Slackdump channels",
Particulars: "Dump",
Title: "Dump Slack Channels",
Name: "Dump",
LocalConfig: opts.configuration,
Cmd: cmd,
ArgsFn: func() []string {
return splitEntryList(entryList)
},
ValidateParamsFn: func() error {
return structures.ValidateEntityList(entryList)
},
}
return w.Run(ctx)
}

var entryList string

func splitEntryList(s string) []string {
return strings.Split(s, " ")
}

func (o *options) configuration() cfgui.Configuration {
return cfgui.Configuration{
{
Name: "Required",
Params: []cfgui.Parameter{
cfgui.ChannelIDs(&entryList),
},
}, {
Name: "Optional",
Params: []cfgui.Parameter{
{
Name: "File naming template",
Value: o.nameTemplate,
Description: "Output file naming template",
Inline: true,
Updater: updaters.NewString(&o.nameTemplate, nametmpl.Default, false, func(s string) error {
_, err := nametmpl.New(s)
return err
}),
},
{
Name: "V2 Compatibility mode",
Value: cfgui.Checkbox(o.compat),
Description: "Use V2 compatibility mode (slower)",
Updater: updaters.NewBool(&o.compat),
},
{
Name: "Update links",
Value: cfgui.Checkbox(o.updateLinks),
Description: "Update file links to point to the downloaded files",
Updater: updaters.NewBool(&o.updateLinks),
},
},
},
}
}
Loading

0 comments on commit 2895662

Please sign in to comment.