form.go 2.4 KB

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