model.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. package qr
  2. import (
  3. "crypto/rsa"
  4. "fmt"
  5. "strings"
  6. "time"
  7. "github.com/ayn2op/tview"
  8. "github.com/ayn2op/tview/tabs"
  9. "github.com/gdamore/tcell/v3"
  10. "github.com/gorilla/websocket"
  11. "github.com/skip2/go-qrcode"
  12. )
  13. type Model struct {
  14. *tview.TextView
  15. conn *websocket.Conn
  16. heartbeatInterval time.Duration
  17. privateKey *rsa.PrivateKey
  18. fingerprint string
  19. qrCode *qrcode.QRCode
  20. msg string
  21. }
  22. func NewModel() *Model {
  23. m := &Model{
  24. TextView: tview.NewTextView(),
  25. }
  26. m.
  27. SetScrollable(true).
  28. SetWrap(false).
  29. SetTextAlign(tview.AlignmentCenter)
  30. m.msg = "Press Ctrl+N to open QR login"
  31. return m
  32. }
  33. var _ tabs.Tab = (*Model)(nil)
  34. func (m *Model) Label() string {
  35. return "QR"
  36. }
  37. func (m *Model) HandleEvent(event tcell.Event) tview.Command {
  38. switch event := event.(type) {
  39. case *tview.InitEvent:
  40. m.msg = "Connecting to Remote Auth Gateway..."
  41. return m.connect()
  42. case *tview.KeyEvent:
  43. if event.Key() == tcell.KeyEsc {
  44. m.msg = "Canceled"
  45. return tview.Batch(m.close(), nil)
  46. }
  47. return m.TextView.HandleEvent(event)
  48. case *connCreateEvent:
  49. m.conn = event.conn
  50. m.msg = "Connected. Handshaking..."
  51. return m.listen()
  52. case *connCloseEvent:
  53. m.conn = nil
  54. return nil
  55. case *helloEvent:
  56. m.heartbeatInterval = time.Duration(event.heartbeatInterval) * time.Millisecond
  57. return tview.Batch(m.listen(), m.heartbeat(), m.generatePrivateKey())
  58. case *privateKeyEvent:
  59. m.privateKey = event.privateKey
  60. return tview.Batch(m.listen(), m.sendInit())
  61. case *nonceProofEvent:
  62. return tview.Batch(m.listen(), m.sendNonceProof(event.encryptedNonce))
  63. case *pendingRemoteInitEvent:
  64. m.fingerprint = event.fingerprint
  65. return tview.Batch(m.listen(), m.generateQRCode(event.fingerprint))
  66. case *qrCodeEvent:
  67. m.qrCode = event.qrCode
  68. m.msg = "Scan this with the Discord mobile app to log in instantly."
  69. return m.listen()
  70. case *pendingTicketEvent:
  71. return tview.Batch(m.listen(), m.decryptUserPayload(event.encryptedUserPayload))
  72. case *userEvent:
  73. name := event.username
  74. if event.discriminator != "0" {
  75. name += "#" + event.discriminator
  76. }
  77. m.msg = fmt.Sprintf("Check your phone! Logging in as %s", name)
  78. return m.listen()
  79. case *pendingLoginEvent:
  80. m.msg = "Authenticating..."
  81. return tview.Batch(m.close(), m.exchangeTicket(event.ticket))
  82. case *cancelEvent:
  83. m.msg = "Login canceled on mobile"
  84. return m.close()
  85. case *heartbeatTickEvent:
  86. if m.conn == nil {
  87. return nil
  88. }
  89. return tview.Batch(m.heartbeat(), m.sendHeartbeat())
  90. case *tcell.EventError:
  91. m.msg = event.Error()
  92. return tview.Batch(m.close(), tview.Command(func() tcell.Event { return event }))
  93. }
  94. return nil
  95. }
  96. func (m *Model) Draw(screen tcell.Screen) {
  97. var contents []string
  98. if m.qrCode != nil {
  99. bitmap := m.qrCode.Bitmap()
  100. var b strings.Builder
  101. for y := 0; y < len(bitmap); y += 2 {
  102. for x := range bitmap[y] {
  103. top := bitmap[y][x]
  104. bottom := false
  105. if y+1 < len(bitmap) {
  106. bottom = bitmap[y+1][x]
  107. }
  108. if top && bottom {
  109. b.WriteString("█")
  110. } else if top && !bottom {
  111. b.WriteString("▀")
  112. } else if !top && bottom {
  113. b.WriteString("▄")
  114. } else {
  115. b.WriteByte(' ')
  116. }
  117. }
  118. b.WriteByte('\n')
  119. }
  120. contents = append(contents, b.String())
  121. }
  122. if m.msg != "" {
  123. contents = append(contents, m.msg)
  124. }
  125. builder := tview.NewLineBuilder()
  126. builder.Write(strings.Join(contents, "\n"), tcell.StyleDefault)
  127. m.SetLines(m.centerLines(builder.Finish()))
  128. m.TextView.Draw(screen)
  129. }
  130. func (m *Model) centerLines(lines []tview.Line) []tview.Line {
  131. _, _, _, height := m.GetInnerRect()
  132. if height == 0 {
  133. height = 40
  134. }
  135. padding := (height - len(lines)) / 2
  136. if padding < 0 {
  137. padding = 0
  138. } else if padding < 1 && height > len(lines) {
  139. padding = 1
  140. }
  141. if padding == 0 {
  142. return lines
  143. }
  144. centered := make([]tview.Line, 0, padding+len(lines))
  145. centered = append(centered, make([]tview.Line, padding)...)
  146. centered = append(centered, lines...)
  147. return centered
  148. }