message_input.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. package ui
  2. import (
  3. "io"
  4. "os"
  5. "os/exec"
  6. "strings"
  7. "github.com/atotto/clipboard"
  8. "github.com/diamondburned/arikawa/v3/api"
  9. "github.com/diamondburned/arikawa/v3/discord"
  10. "github.com/diamondburned/arikawa/v3/utils/json/option"
  11. "github.com/gdamore/tcell/v2"
  12. "github.com/rivo/tview"
  13. lua "github.com/yuin/gopher-lua"
  14. luar "layeh.com/gopher-luar"
  15. )
  16. type MessageInput struct {
  17. *tview.InputField
  18. core *Core
  19. }
  20. func NewMessageInput(c *Core) *MessageInput {
  21. mi := &MessageInput{
  22. InputField: tview.NewInputField(),
  23. core: c,
  24. }
  25. mi.SetFieldBackgroundColor(tview.Styles.PrimitiveBackgroundColor)
  26. mi.SetPlaceholder("Message...")
  27. mi.SetPlaceholderStyle(tcell.StyleDefault.Background(tview.Styles.PrimitiveBackgroundColor))
  28. mi.SetInputCapture(mi.onInputCapture)
  29. mi.SetTitleAlign(tview.AlignLeft)
  30. mi.SetBorder(true)
  31. mi.SetBorderPadding(0, 0, 1, 1)
  32. return mi
  33. }
  34. func (mi *MessageInput) onInputCapture(e *tcell.EventKey) *tcell.EventKey {
  35. keysTable, ok := mi.core.Config.State.GetGlobal("keys").(*lua.LTable)
  36. if !ok {
  37. return e
  38. }
  39. messageInputTable, ok := keysTable.RawGetString("messageInput").(*lua.LTable)
  40. if !ok {
  41. return e
  42. }
  43. var fn lua.LValue
  44. messageInputTable.ForEach(func(k, v lua.LValue) {
  45. keyTable := v.(*lua.LTable)
  46. if e.Name() == lua.LVAsString(keyTable.RawGetString("name")) {
  47. fn = keyTable.RawGetString("action")
  48. }
  49. })
  50. if fn != nil {
  51. mi.core.Config.State.CallByParam(lua.P{
  52. Fn: fn,
  53. NRet: 1,
  54. Protect: true,
  55. }, luar.New(mi.core.Config.State, mi.core), luar.New(mi.core.Config.State, e))
  56. // Returned value
  57. ret, ok := mi.core.Config.State.Get(-1).(*lua.LUserData)
  58. if !ok {
  59. return e
  60. }
  61. // Remove returned value
  62. mi.core.Config.State.Pop(1)
  63. ev, ok := ret.Value.(*tcell.EventKey)
  64. if ok {
  65. return ev
  66. }
  67. }
  68. // Defaults
  69. switch e.Name() {
  70. case "Enter":
  71. return mi.sendMessage()
  72. case "Esc":
  73. mi.
  74. SetText("").
  75. SetTitle("")
  76. mi.core.Application.SetFocus(mi.core.MainFlex)
  77. mi.core.MessagesPanel.SelectedMessage = -1
  78. mi.core.MessagesPanel.Highlight()
  79. return nil
  80. }
  81. return e
  82. }
  83. func (mi *MessageInput) sendMessage() *tcell.EventKey {
  84. if mi.core.ChannelsTree.SelectedChannel == nil {
  85. return nil
  86. }
  87. t := strings.TrimSpace(mi.GetText())
  88. if t == "" {
  89. return nil
  90. }
  91. messagesLimit := mi.core.Config.State.GetGlobal("messagesLimit")
  92. ms, err := mi.core.State.Messages(mi.core.ChannelsTree.SelectedChannel.ID, uint(lua.LVAsNumber(messagesLimit)))
  93. if err != nil {
  94. return nil
  95. }
  96. if len(mi.core.MessagesPanel.GetHighlights()) != 0 {
  97. mID, err := discord.ParseSnowflake(mi.core.MessagesPanel.GetHighlights()[0])
  98. if err != nil {
  99. return nil
  100. }
  101. _, m := findMessageByID(ms, discord.MessageID(mID))
  102. d := api.SendMessageData{
  103. Content: t,
  104. Reference: m.Reference,
  105. AllowedMentions: &api.AllowedMentions{RepliedUser: option.False},
  106. }
  107. // If the title of the message InputField widget has "[@]" as a prefix, send the message as a reply and mention the replied user.
  108. if strings.HasPrefix(mi.GetTitle(), "[@]") {
  109. d.AllowedMentions.RepliedUser = option.True
  110. }
  111. go mi.core.State.SendMessageComplex(m.ChannelID, d)
  112. mi.core.MessagesPanel.SelectedMessage = -1
  113. mi.core.MessagesPanel.Highlight()
  114. mi.SetTitle("")
  115. } else {
  116. go mi.core.State.SendMessage(mi.core.ChannelsTree.SelectedChannel.ID, t)
  117. }
  118. mi.SetText("")
  119. return nil
  120. }
  121. func (mi *MessageInput) pasteClipboardContentLua(s *lua.LState) int {
  122. text, _ := clipboard.ReadAll()
  123. text = mi.GetText() + text
  124. mi.SetText(text)
  125. return returnNilLua(s)
  126. }
  127. func (mi *MessageInput) openExternalEditorLua(s *lua.LState) int {
  128. e := os.Getenv("EDITOR")
  129. if e == "" {
  130. return returnNilLua(s)
  131. }
  132. f, err := os.CreateTemp(os.TempDir(), "discordo-*.md")
  133. if err != nil {
  134. return returnNilLua(s)
  135. }
  136. defer os.Remove(f.Name())
  137. cmd := exec.Command(e, f.Name())
  138. cmd.Stdin = os.Stdin
  139. cmd.Stdout = os.Stdout
  140. mi.core.Application.Suspend(func() {
  141. err = cmd.Run()
  142. if err != nil {
  143. return
  144. }
  145. })
  146. b, err := io.ReadAll(f)
  147. if err != nil {
  148. return returnNilLua(s)
  149. }
  150. mi.SetText(string(b))
  151. return returnNilLua(s)
  152. }