form.go 2.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. package login
  2. import (
  3. "errors"
  4. "log/slog"
  5. "github.com/ayn2op/discordo/internal/config"
  6. "github.com/ayn2op/discordo/internal/consts"
  7. "github.com/ayn2op/discordo/internal/ui"
  8. "github.com/ayn2op/tview"
  9. "github.com/diamondburned/arikawa/v3/api"
  10. "github.com/zalando/go-keyring"
  11. )
  12. type DoneFn = func(token string)
  13. type Form struct {
  14. *tview.Pages
  15. cfg *config.Config
  16. form *tview.Form
  17. done DoneFn
  18. formPage int
  19. errPage int
  20. }
  21. func NewForm(cfg *config.Config, done DoneFn) *Form {
  22. f := &Form{
  23. Pages: tview.NewPages(),
  24. cfg: cfg,
  25. form: tview.NewForm(),
  26. done: done,
  27. }
  28. f.form.
  29. AddInputField("Email", "", 0, nil, nil).
  30. AddPasswordField("Password", "", 0, 0, nil).
  31. AddPasswordField("Code (optional)", "", 0, 0, nil).
  32. AddButton("Login", f.login)
  33. f.formPage = f.AddAndSwitchToPage(f.form, true)
  34. return f
  35. }
  36. func (f *Form) login() {
  37. email := f.form.GetFormItem(0).(*tview.InputField).GetText()
  38. password := f.form.GetFormItem(1).(*tview.InputField).GetText()
  39. if email == "" || password == "" {
  40. return
  41. }
  42. // Create an API client without an authentication token.
  43. client := api.NewClient("")
  44. // Spoof the user agent of a web browser.
  45. client.UserAgent = f.cfg.Identify.UserAgent
  46. resp, err := client.Login(email, password)
  47. if err != nil {
  48. f.onError(err)
  49. return
  50. }
  51. if resp.Token == "" && resp.MFA {
  52. code := f.form.GetFormItem(2).(*tview.InputField).GetText()
  53. if code == "" {
  54. f.onError(errors.New("code required"))
  55. return
  56. }
  57. // Attempt to login using the code.
  58. resp, err = client.TOTP(code, resp.Ticket)
  59. if err != nil {
  60. f.onError(err)
  61. return
  62. }
  63. }
  64. if resp.Token == "" {
  65. f.onError(errors.New("missing token"))
  66. return
  67. }
  68. go keyring.Set(consts.Name, "token", resp.Token)
  69. if f.done != nil {
  70. f.done(resp.Token)
  71. }
  72. }
  73. func (f *Form) onError(err error) {
  74. slog.Error("failed to login", "err", err)
  75. modal := tview.NewModal().
  76. SetText(err.Error()).
  77. AddButtons([]string{"Close"}).
  78. SetDoneFunc(func(_ int, _ string) {
  79. f.RemovePage(f.errPage).SwitchToPage(f.formPage)
  80. })
  81. f.errPage = f.AddAndSwitchToPage(ui.Centered(modal, 0, 0), true)
  82. f.ShowPage(f.formPage)
  83. }