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. // The email view is displayed on the screen first since it is the recommended method to login.
  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. }
  36. return event
  37. })
  38. return v
  39. }
  40. type EmailView struct {
  41. *tview.Form
  42. core *Core
  43. }
  44. func newEmailView(c *Core) *EmailView {
  45. v := &EmailView{
  46. Form: tview.NewForm(),
  47. core: c,
  48. }
  49. v.AddInputField("Email", "", 0, nil, nil)
  50. v.AddPasswordField("Password", "", 0, 0, nil)
  51. v.AddPasswordField("Code (optional)", "", 0, 0, nil)
  52. v.AddButton("Login", v.onLoginButtonSelected)
  53. return v
  54. }
  55. func (v *EmailView) onLoginButtonSelected() {
  56. email := v.GetFormItem(0).(*tview.InputField).GetText()
  57. password := v.GetFormItem(1).(*tview.InputField).GetText()
  58. if email == "" || password == "" {
  59. return
  60. }
  61. // Make a scratch HTTP client without a token
  62. client := api.NewClient("").WithContext(context.Background())
  63. // Try to login without TOTP
  64. l, err := client.Login(email, password)
  65. if err != nil {
  66. log.Fatal(err)
  67. }
  68. // If the token is not dispatched in the response and the "mfa" field is set as true, login using MFA instead.
  69. if l.Token == "" && l.MFA {
  70. code := v.GetFormItem(2).(*tview.InputField).GetText()
  71. if code == "" {
  72. return
  73. }
  74. // Retry logging in with a 2FA token
  75. l, err = client.TOTP(code, l.Ticket)
  76. if err != nil {
  77. log.Fatal(err)
  78. }
  79. }
  80. err = v.core.Run(l.Token)
  81. if err != nil {
  82. log.Fatal(err)
  83. }
  84. v.core.Draw()
  85. go keyring.Set(config.Name, "token", l.Token)
  86. }
  87. type TokenView struct {
  88. *tview.Form
  89. core *Core
  90. }
  91. func newTokenView(c *Core) *TokenView {
  92. v := &TokenView{
  93. Form: tview.NewForm(),
  94. core: c,
  95. }
  96. v.AddPasswordField("Token", "", 0, 0, nil)
  97. v.AddButton("Login", v.onLoginButtonSelected)
  98. return v
  99. }
  100. func (v *TokenView) onLoginButtonSelected() {
  101. token := v.GetFormItem(0).(*tview.InputField).GetText()
  102. if token == "" {
  103. return
  104. }
  105. err := v.core.Run(token)
  106. if err != nil {
  107. log.Fatal(err)
  108. }
  109. v.core.Draw()
  110. go keyring.Set(config.Name, "token", token)
  111. }