core.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. package ui
  2. import (
  3. "context"
  4. "strings"
  5. "github.com/ayntgl/discordo/config"
  6. "github.com/diamondburned/arikawa/v3/api"
  7. "github.com/diamondburned/arikawa/v3/discord"
  8. "github.com/diamondburned/arikawa/v3/gateway"
  9. "github.com/diamondburned/arikawa/v3/state"
  10. "github.com/gdamore/tcell/v2"
  11. "github.com/rivo/tview"
  12. lua "github.com/yuin/gopher-lua"
  13. luar "layeh.com/gopher-luar"
  14. )
  15. // Core is responsible for the following:
  16. // - Initialization of the application, UI elements, configuration, and state.
  17. // - Configuration of the application and state when Run is called.
  18. // - Management of the application and state.
  19. type Core struct {
  20. Application *tview.Application
  21. MainFlex *tview.Flex
  22. GuildsTree *GuildsTree
  23. ChannelsTree *ChannelsTree
  24. MessagesPanel *MessagesPanel
  25. MessageInput *MessageInput
  26. Config *config.Config
  27. State *state.State
  28. }
  29. func NewCore(path string) *Core {
  30. c := &Core{
  31. Application: tview.NewApplication(),
  32. MainFlex: tview.NewFlex(),
  33. Config: config.NewConfig(path),
  34. }
  35. c.MainFlex.SetInputCapture(c.onInputCapture)
  36. c.GuildsTree = NewGuildsTree(c)
  37. c.ChannelsTree = NewChannelsTree(c)
  38. c.MessagesPanel = NewMessagesPanel(c)
  39. c.MessageInput = NewMessageInput(c)
  40. return c
  41. }
  42. func (c *Core) Run(token string) error {
  43. err := c.Config.Load()
  44. if err != nil {
  45. return err
  46. }
  47. c.register()
  48. err = c.Config.State.DoString(string(config.LuaConfig))
  49. if err != nil {
  50. return err
  51. }
  52. c.Application.EnableMouse(lua.LVAsBool(c.Config.State.GetGlobal("mouse")))
  53. identifyProperties, ok := c.Config.State.GetGlobal("identifyProperties").(*lua.LTable)
  54. if !ok {
  55. identifyProperties = c.Config.State.NewTable()
  56. }
  57. userAgent := lua.LVAsString(identifyProperties.RawGetString("userAgent"))
  58. c.State = state.NewWithIdentifier(gateway.NewIdentifier(gateway.IdentifyCommand{
  59. Token: token,
  60. Intents: nil,
  61. Properties: gateway.IdentifyProperties{
  62. Browser: lua.LVAsString(identifyProperties.RawGetString("browser")),
  63. BrowserVersion: lua.LVAsString(identifyProperties.RawGetString("browserVersion")),
  64. BrowserUserAgent: userAgent,
  65. OS: lua.LVAsString(identifyProperties.RawGetString("os")),
  66. },
  67. // The official client sets the compress field as false.
  68. Compress: false,
  69. }))
  70. // For user accounts, all of the guilds, the user is in, are dispatched in the READY gateway event.
  71. // Whereas, for bot accounts, the guilds are dispatched discretely in the GUILD_CREATE gateway events.
  72. if !strings.HasPrefix(c.State.Token, "Bot") {
  73. api.UserAgent = userAgent
  74. c.State.AddHandler(c.onStateReady)
  75. }
  76. c.State.AddHandler(c.onStateGuildCreate)
  77. c.State.AddHandler(c.onStateGuildDelete)
  78. c.State.AddHandler(c.onStateMessageCreate)
  79. return c.State.Open(context.Background())
  80. }
  81. func (c *Core) register() {
  82. c.Config.State.SetGlobal("key", c.Config.State.NewFunction(c.Config.KeyLua))
  83. // Messages panel
  84. c.Config.State.SetGlobal("openMessageActionsList", c.Config.State.NewFunction(c.MessagesPanel.openMessageActionsListLua))
  85. c.Config.State.SetGlobal("selectPreviousMessage", c.Config.State.NewFunction(c.MessagesPanel.selectPreviousMessageLua))
  86. c.Config.State.SetGlobal("selectNextMessage", c.Config.State.NewFunction(c.MessagesPanel.selectNextMessageLua))
  87. c.Config.State.SetGlobal("selectFirstMessage", c.Config.State.NewFunction(c.MessagesPanel.selectFirstMessageLua))
  88. c.Config.State.SetGlobal("selectLastMessage", c.Config.State.NewFunction(c.MessagesPanel.selectLastMessageLua))
  89. // Message input
  90. c.Config.State.SetGlobal("openExternalEditor", c.Config.State.NewFunction(c.MessageInput.openExternalEditorLua))
  91. c.Config.State.SetGlobal("pasteClipboardContent", c.Config.State.NewFunction(c.MessageInput.pasteClipboardContentLua))
  92. }
  93. func (c *Core) DrawMainFlex() {
  94. leftFlex := tview.NewFlex().
  95. SetDirection(tview.FlexRow).
  96. AddItem(c.GuildsTree, 10, 1, false).
  97. AddItem(c.ChannelsTree, 0, 1, false)
  98. rightFlex := tview.NewFlex().
  99. SetDirection(tview.FlexRow).
  100. AddItem(c.MessagesPanel, 0, 1, false).
  101. AddItem(c.MessageInput, 3, 1, false)
  102. c.MainFlex.
  103. AddItem(leftFlex, 0, 1, false).
  104. AddItem(rightFlex, 0, 4, false)
  105. }
  106. func (c *Core) onInputCapture(e *tcell.EventKey) *tcell.EventKey {
  107. if c.MessageInput.HasFocus() {
  108. return e
  109. }
  110. // If the main flex is nil, that is, it is not initialized yet, then the login form is currently focused.
  111. if c.MainFlex == nil {
  112. return e
  113. }
  114. keysTable, ok := c.Config.State.GetGlobal("keys").(*lua.LTable)
  115. if !ok {
  116. return e
  117. }
  118. applicationTable, ok := keysTable.RawGetString("application").(*lua.LTable)
  119. if !ok {
  120. return e
  121. }
  122. var fn lua.LValue
  123. applicationTable.ForEach(func(k, v lua.LValue) {
  124. keyTable := v.(*lua.LTable)
  125. if e.Name() == lua.LVAsString(keyTable.RawGetString("name")) {
  126. fn = keyTable.RawGetString("action")
  127. }
  128. })
  129. c.Config.State.CallByParam(lua.P{
  130. Fn: fn,
  131. NRet: 1,
  132. Protect: true,
  133. }, luar.New(c.Config.State, c), luar.New(c.Config.State, e))
  134. // Returned value
  135. ret, ok := c.Config.State.Get(-1).(*lua.LUserData)
  136. if !ok {
  137. return e
  138. }
  139. // Remove returned value
  140. c.Config.State.Pop(1)
  141. return ret.Value.(*tcell.EventKey)
  142. }
  143. func (c *Core) onStateReady(r *gateway.ReadyEvent) {
  144. rootNode := c.GuildsTree.GetRoot()
  145. for _, gf := range r.UserSettings.GuildFolders {
  146. if gf.ID == 0 {
  147. for _, gID := range gf.GuildIDs {
  148. g, err := c.State.Cabinet.Guild(gID)
  149. if err != nil {
  150. return
  151. }
  152. guildNode := tview.NewTreeNode(g.Name)
  153. guildNode.SetReference(g.ID)
  154. rootNode.AddChild(guildNode)
  155. }
  156. } else {
  157. var b strings.Builder
  158. if gf.Color != discord.NullColor {
  159. b.WriteByte('[')
  160. b.WriteString(gf.Color.String())
  161. b.WriteByte(']')
  162. } else {
  163. b.WriteString("[#ED4245]")
  164. }
  165. if gf.Name != "" {
  166. b.WriteString(gf.Name)
  167. } else {
  168. b.WriteString("Folder")
  169. }
  170. b.WriteString("[-]")
  171. folderNode := tview.NewTreeNode(b.String())
  172. rootNode.AddChild(folderNode)
  173. for _, gID := range gf.GuildIDs {
  174. g, err := c.State.Cabinet.Guild(gID)
  175. if err != nil {
  176. return
  177. }
  178. guildNode := tview.NewTreeNode(g.Name)
  179. guildNode.SetReference(g.ID)
  180. folderNode.AddChild(guildNode)
  181. }
  182. }
  183. }
  184. c.GuildsTree.SetCurrentNode(rootNode)
  185. c.Application.SetFocus(c.GuildsTree)
  186. }
  187. func (c *Core) onStateGuildCreate(g *gateway.GuildCreateEvent) {
  188. guildNode := tview.NewTreeNode(g.Name)
  189. guildNode.SetReference(g.ID)
  190. rootNode := c.GuildsTree.GetRoot()
  191. rootNode.AddChild(guildNode)
  192. c.Application.Draw()
  193. }
  194. func (c *Core) onStateGuildDelete(g *gateway.GuildDeleteEvent) {
  195. rootNode := c.GuildsTree.GetRoot()
  196. var parentNode *tview.TreeNode
  197. rootNode.Walk(func(node, _ *tview.TreeNode) bool {
  198. if node.GetReference() == g.ID {
  199. parentNode = node
  200. return false
  201. }
  202. return true
  203. })
  204. if parentNode != nil {
  205. rootNode.RemoveChild(parentNode)
  206. }
  207. c.Application.Draw()
  208. }
  209. func (c *Core) onStateMessageCreate(m *gateway.MessageCreateEvent) {
  210. if c.ChannelsTree.SelectedChannel != nil && c.ChannelsTree.SelectedChannel.ID == m.ChannelID {
  211. _, err := c.MessagesPanel.Write(buildMessage(c, m.Message))
  212. if err != nil {
  213. return
  214. }
  215. if len(c.MessagesPanel.GetHighlights()) == 0 {
  216. c.MessagesPanel.ScrollToEnd()
  217. }
  218. }
  219. }