form.go 3.0 KB

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