form.go 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. package login
  2. import (
  3. "errors"
  4. "log/slog"
  5. "github.com/ayn2op/discordo/internal/config"
  6. "github.com/diamondburned/arikawa/v3/api"
  7. "github.com/gdamore/tcell/v2"
  8. "github.com/rivo/tview"
  9. "github.com/zalando/go-keyring"
  10. )
  11. type DoneFn = func(token string)
  12. type Form struct {
  13. *tview.Flex
  14. app *tview.Application
  15. errorTextView *tview.TextView
  16. inputs []*tview.InputField
  17. active int
  18. done DoneFn
  19. }
  20. func NewForm(app *tview.Application, done DoneFn) *Form {
  21. self := &Form{
  22. Flex: tview.NewFlex().SetDirection(tview.FlexRow),
  23. app: app,
  24. errorTextView: tview.NewTextView(),
  25. done: done,
  26. }
  27. emailInput := tview.NewInputField()
  28. emailInput.
  29. SetBorder(true).
  30. SetTitle("Email").
  31. SetTitleAlign(tview.AlignLeft)
  32. passwordInput := tview.NewInputField()
  33. passwordInput.
  34. SetMaskCharacter('*').
  35. SetBorder(true).
  36. SetTitle("Password").
  37. SetTitleAlign(tview.AlignLeft)
  38. codeInput := tview.NewInputField()
  39. codeInput.
  40. SetMaskCharacter('*').
  41. SetBorder(true).
  42. SetTitle("Code (optional)").
  43. SetTitleAlign(tview.AlignLeft)
  44. self.inputs = []*tview.InputField{emailInput, passwordInput, codeInput}
  45. for i, input := range self.inputs {
  46. var focus bool
  47. if i == 0 {
  48. focus = true
  49. }
  50. self.AddItem(input, 3, 1, focus)
  51. }
  52. self.
  53. AddItem(self.errorTextView, 0, 1, false).
  54. SetBorderPadding(0, 0, 1, 1).
  55. SetInputCapture(self.onInputCapture)
  56. return self
  57. }
  58. func (self *Form) onInputCapture(event *tcell.EventKey) *tcell.EventKey {
  59. switch event.Key() {
  60. case tcell.KeyBacktab:
  61. if self.active == 0 {
  62. self.active = len(self.inputs) - 1
  63. } else {
  64. self.active--
  65. }
  66. self.app.SetFocus(self.inputs[self.active])
  67. return nil
  68. case tcell.KeyTab:
  69. if self.active == len(self.inputs)-1 {
  70. self.active = 0
  71. } else {
  72. self.active++
  73. }
  74. self.app.SetFocus(self.inputs[self.active])
  75. return nil
  76. case tcell.KeyEnter:
  77. // If the currently active input is not the email input, proceed to login with the provided details.
  78. if self.active != 0 {
  79. self.login()
  80. }
  81. }
  82. return event
  83. }
  84. func (self *Form) login() {
  85. email := self.inputs[0].GetText()
  86. password := self.inputs[1].GetText()
  87. if email == "" || password == "" {
  88. return
  89. }
  90. // Create an API client without an authentication token.
  91. client := api.NewClient("")
  92. // Spoof the user agent of a web browser.
  93. client.UserAgent = config.UserAgent
  94. // Attempt to login using the email and password.
  95. resp, err := client.Login(email, password)
  96. if err != nil {
  97. self.onError(err)
  98. return
  99. }
  100. if resp.Token == "" && resp.MFA {
  101. code := self.inputs[2].GetText()
  102. if code == "" {
  103. self.onError(errors.New("code required"))
  104. return
  105. }
  106. // Attempt to login using the code.
  107. resp, err = client.TOTP(code, resp.Ticket)
  108. if err != nil {
  109. self.onError(err)
  110. return
  111. }
  112. }
  113. if resp.Token == "" {
  114. self.onError(errors.New("missing token"))
  115. return
  116. }
  117. go keyring.Set(config.Name, "token", resp.Token)
  118. if self.done != nil {
  119. self.done(resp.Token)
  120. }
  121. }
  122. func (self *Form) onError(err error) {
  123. slog.Error("failed to login", "err", err)
  124. self.errorTextView.SetText(err.Error())
  125. }