app.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. package ui
  2. import (
  3. "context"
  4. "sort"
  5. "strings"
  6. "github.com/ayntgl/discordo/config"
  7. "github.com/diamondburned/arikawa/v3/api"
  8. "github.com/diamondburned/arikawa/v3/discord"
  9. "github.com/diamondburned/arikawa/v3/gateway"
  10. "github.com/diamondburned/arikawa/v3/state"
  11. "github.com/gdamore/tcell/v2"
  12. "github.com/rivo/tview"
  13. )
  14. type App struct {
  15. *tview.Application
  16. MainFlex *tview.Flex
  17. GuildsTree *GuildsTree
  18. ChannelsTree *ChannelsTree
  19. MessagesTextView *MessagesTextView
  20. MessageInputField *MessageInput
  21. Config *config.Config
  22. State *state.State
  23. SelectedChannel *discord.Channel
  24. SelectedMessage int
  25. }
  26. func NewApp(token string, c *config.Config) *App {
  27. app := &App{
  28. Application: tview.NewApplication(),
  29. MainFlex: tview.NewFlex(),
  30. Config: c,
  31. State: state.NewWithIdentifier(gateway.NewIdentifier(gateway.IdentifyCommand{
  32. Token: token,
  33. Intents: nil,
  34. Properties: gateway.IdentifyProperties{
  35. OS: c.Identify.Os,
  36. Browser: c.Identify.Browser,
  37. },
  38. // The official client sets the compress field as false.
  39. Compress: false,
  40. })),
  41. SelectedMessage: -1,
  42. }
  43. app.GuildsTree = NewGuildsTree(app)
  44. app.ChannelsTree = NewChannelsTree(app)
  45. app.MessagesTextView = NewMessagesTextView(app)
  46. app.MessageInputField = NewMessageInput(app)
  47. app.EnableMouse(app.Config.Mouse)
  48. app.MainFlex.SetInputCapture(app.onInputCapture)
  49. return app
  50. }
  51. func (app *App) Connect() error {
  52. // For user accounts, all of the guilds, the user is in, are dispatched in the READY gateway event.
  53. // Whereas, for bot accounts, the guilds are dispatched discretely in the GUILD_CREATE gateway events.
  54. if !strings.HasPrefix(app.State.Token, "Bot") {
  55. api.UserAgent = app.Config.Identify.UserAgent
  56. app.State.AddHandler(app.onStateReady)
  57. }
  58. app.State.AddHandler(app.onStateGuildCreate)
  59. app.State.AddHandler(app.onStateGuildDelete)
  60. app.State.AddHandler(app.onStateMessageCreate)
  61. return app.State.Open(context.Background())
  62. }
  63. func (app *App) onInputCapture(e *tcell.EventKey) *tcell.EventKey {
  64. if app.MessageInputField.HasFocus() {
  65. return e
  66. }
  67. if app.MainFlex.GetItemCount() != 0 {
  68. switch e.Name() {
  69. case app.Config.Keys.ToggleGuildsTree:
  70. app.SetFocus(app.GuildsTree)
  71. return nil
  72. case app.Config.Keys.ToggleChannelsTree:
  73. app.SetFocus(app.ChannelsTree)
  74. return nil
  75. case app.Config.Keys.ToggleMessagesTextView:
  76. app.SetFocus(app.MessagesTextView)
  77. return nil
  78. case app.Config.Keys.ToggleMessageInput:
  79. app.SetFocus(app.MessageInputField)
  80. return nil
  81. }
  82. }
  83. return e
  84. }
  85. func (app *App) DrawMainFlex() {
  86. leftFlex := tview.NewFlex().
  87. SetDirection(tview.FlexRow).
  88. AddItem(app.GuildsTree, 10, 1, false).
  89. AddItem(app.ChannelsTree, 0, 1, false)
  90. rightFlex := tview.NewFlex().
  91. SetDirection(tview.FlexRow).
  92. AddItem(app.MessagesTextView, 0, 1, false).
  93. AddItem(app.MessageInputField, 3, 1, false)
  94. app.MainFlex.
  95. AddItem(leftFlex, 0, 1, false).
  96. AddItem(rightFlex, 0, 4, false)
  97. }
  98. func (app *App) onStateReady(r *gateway.ReadyEvent) {
  99. sort.Slice(r.Guilds, func(a, b int) bool {
  100. found := false
  101. for _, guildID := range r.UserSettings.GuildPositions {
  102. if found && guildID == r.Guilds[b].ID {
  103. return true
  104. }
  105. if !found && guildID == r.Guilds[a].ID {
  106. found = true
  107. }
  108. }
  109. return false
  110. })
  111. rootNode := app.GuildsTree.GetRoot()
  112. for _, g := range r.Guilds {
  113. guildNode := tview.NewTreeNode(g.Name)
  114. guildNode.SetReference(g.ID)
  115. rootNode.AddChild(guildNode)
  116. }
  117. app.GuildsTree.SetCurrentNode(rootNode)
  118. app.SetFocus(app.GuildsTree)
  119. }
  120. func (app *App) onStateGuildCreate(g *gateway.GuildCreateEvent) {
  121. rootNode := app.GuildsTree.GetRoot()
  122. guildNode := tview.NewTreeNode(g.Name)
  123. guildNode.SetReference(g.ID)
  124. rootNode.AddChild(guildNode)
  125. app.Draw()
  126. }
  127. func (app *App) onStateGuildDelete(g *gateway.GuildDeleteEvent) {
  128. rootNode := app.GuildsTree.GetRoot()
  129. var parentNode *tview.TreeNode
  130. rootNode.Walk(func(node, _ *tview.TreeNode) bool {
  131. if node.GetReference() == g.ID {
  132. parentNode = node
  133. return false
  134. }
  135. return true
  136. })
  137. if parentNode != nil {
  138. rootNode.RemoveChild(parentNode)
  139. }
  140. app.Draw()
  141. }
  142. func (app *App) onStateMessageCreate(m *gateway.MessageCreateEvent) {
  143. if app.SelectedChannel != nil && app.SelectedChannel.ID == m.ChannelID {
  144. _, err := app.MessagesTextView.Write(buildMessage(app, m.Message))
  145. if err != nil {
  146. return
  147. }
  148. if len(app.MessagesTextView.GetHighlights()) == 0 {
  149. app.MessagesTextView.ScrollToEnd()
  150. }
  151. }
  152. }