form.go 2.3 KB

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