login_view.go 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. package ui
  2. import (
  3. "context"
  4. "log"
  5. "github.com/ayntgl/discordo/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. const (
  12. emailViewPageName = "email"
  13. tokenViewPageName = "token"
  14. )
  15. func NewLoginView(c *Core) *tview.Pages {
  16. v := tview.NewPages()
  17. v.AddPage(emailViewPageName, newEmailView(c), true, true)
  18. v.AddPage(tokenViewPageName, newTokenView(c), true, true)
  19. // Since the recommended method to login is using the email and password, it is displayed on the screen first.
  20. v.SwitchToPage(emailViewPageName)
  21. v.SetTitle("Login")
  22. v.SetTitleAlign(tview.AlignLeft)
  23. v.SetBorder(true)
  24. v.SetBorderPadding(0, 0, 1, 1)
  25. v.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
  26. if event.Key() == tcell.KeyCtrlSpace {
  27. name, _ := v.GetFrontPage()
  28. switch name {
  29. case emailViewPageName:
  30. name = tokenViewPageName
  31. case tokenViewPageName:
  32. name = emailViewPageName
  33. }
  34. v.SwitchToPage(name)
  35. return nil
  36. }
  37. return event
  38. })
  39. return v
  40. }
  41. type EmailView struct {
  42. *tview.Form
  43. core *Core
  44. }
  45. func newEmailView(c *Core) *EmailView {
  46. v := &EmailView{
  47. Form: tview.NewForm(),
  48. core: c,
  49. }
  50. v.AddInputField("Email", "", 0, nil, nil)
  51. v.AddPasswordField("Password", "", 0, 0, nil)
  52. v.AddPasswordField("Code (optional)", "", 0, 0, nil)
  53. v.AddButton("Login", v.onLoginButtonSelected)
  54. return v
  55. }
  56. func (v *EmailView) onLoginButtonSelected() {
  57. email := v.GetFormItem(0).(*tview.InputField).GetText()
  58. password := v.GetFormItem(1).(*tview.InputField).GetText()
  59. if email == "" || password == "" {
  60. return
  61. }
  62. // Make a scratch HTTP client without a token
  63. client := api.NewClient("").WithContext(context.Background())
  64. // Try to login without TOTP
  65. l, err := client.Login(email, password)
  66. if err != nil {
  67. log.Fatal(err)
  68. }
  69. // If the token is not dispatched in the response and the "mfa" field is set as true, login using MFA instead.
  70. if l.Token == "" && l.MFA {
  71. code := v.GetFormItem(2).(*tview.InputField).GetText()
  72. if code == "" {
  73. return
  74. }
  75. // Retry logging in with a 2FA token
  76. l, err = client.TOTP(code, l.Ticket)
  77. if err != nil {
  78. log.Fatal(err)
  79. }
  80. }
  81. err = v.core.Run(l.Token)
  82. if err != nil {
  83. log.Fatal(err)
  84. }
  85. v.core.Draw()
  86. go keyring.Set(config.Name, "token", l.Token)
  87. }
  88. type TokenView struct {
  89. *tview.Form
  90. core *Core
  91. }
  92. func newTokenView(c *Core) *TokenView {
  93. v := &TokenView{
  94. Form: tview.NewForm(),
  95. core: c,
  96. }
  97. v.AddPasswordField("Token", "", 0, 0, nil)
  98. v.AddButton("Login", v.onLoginButtonSelected)
  99. return v
  100. }
  101. func (v *TokenView) onLoginButtonSelected() {
  102. token := v.GetFormItem(0).(*tview.InputField).GetText()
  103. if token == "" {
  104. return
  105. }
  106. err := v.core.Run(token)
  107. if err != nil {
  108. log.Fatal(err)
  109. }
  110. v.core.Draw()
  111. go keyring.Set(config.Name, "token", token)
  112. }