messages.go 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. package ui
  2. import (
  3. "io"
  4. "os"
  5. "os/exec"
  6. "strings"
  7. "github.com/atotto/clipboard"
  8. "github.com/ayntgl/discordgo"
  9. "github.com/ayntgl/discordo/discord"
  10. "github.com/gdamore/tcell/v2"
  11. "github.com/rivo/tview"
  12. )
  13. type MessagesTextView struct {
  14. *tview.TextView
  15. app *App
  16. }
  17. func NewMessagesTextView(app *App) *MessagesTextView {
  18. mtv := &MessagesTextView{
  19. TextView: tview.NewTextView(),
  20. app: app,
  21. }
  22. mtv.SetDynamicColors(true)
  23. mtv.SetRegions(true)
  24. mtv.SetWordWrap(true)
  25. mtv.SetChangedFunc(func() {
  26. mtv.app.Draw()
  27. })
  28. mtv.SetBorder(true)
  29. mtv.SetBorderPadding(0, 0, 1, 1)
  30. mtv.SetInputCapture(mtv.onInputCapture)
  31. return mtv
  32. }
  33. func (mtv *MessagesTextView) onInputCapture(e *tcell.EventKey) *tcell.EventKey {
  34. if mtv.app.SelectedChannel == nil {
  35. return nil
  36. }
  37. ms := mtv.app.SelectedChannel.Messages
  38. if len(ms) == 0 {
  39. return nil
  40. }
  41. switch e.Name() {
  42. case mtv.app.Config.Keybindings.SelectPreviousMessage:
  43. if len(mtv.app.MessagesTextView.GetHighlights()) == 0 {
  44. mtv.app.SelectedMessage = len(ms) - 1
  45. } else {
  46. mtv.app.SelectedMessage--
  47. if mtv.app.SelectedMessage < 0 {
  48. mtv.app.SelectedMessage = 0
  49. }
  50. }
  51. mtv.app.MessagesTextView.
  52. Highlight(ms[mtv.app.SelectedMessage].ID).
  53. ScrollToHighlight()
  54. return nil
  55. case mtv.app.Config.Keybindings.SelectNextMessage:
  56. if len(mtv.app.MessagesTextView.GetHighlights()) == 0 {
  57. mtv.app.SelectedMessage = len(ms) - 1
  58. } else {
  59. mtv.app.SelectedMessage++
  60. if mtv.app.SelectedMessage >= len(ms) {
  61. mtv.app.SelectedMessage = len(ms) - 1
  62. }
  63. }
  64. mtv.app.MessagesTextView.
  65. Highlight(ms[mtv.app.SelectedMessage].ID).
  66. ScrollToHighlight()
  67. return nil
  68. case mtv.app.Config.Keybindings.SelectFirstMessage:
  69. mtv.app.SelectedMessage = 0
  70. mtv.app.MessagesTextView.
  71. Highlight(ms[mtv.app.SelectedMessage].ID).
  72. ScrollToHighlight()
  73. return nil
  74. case mtv.app.Config.Keybindings.SelectLastMessage:
  75. mtv.app.SelectedMessage = len(ms) - 1
  76. mtv.app.MessagesTextView.
  77. Highlight(ms[mtv.app.SelectedMessage].ID).
  78. ScrollToHighlight()
  79. return nil
  80. case mtv.app.Config.Keybindings.OpenMessageActionsList:
  81. messageActionsList := tview.NewList()
  82. hs := mtv.app.MessagesTextView.GetHighlights()
  83. if len(hs) == 0 {
  84. return nil
  85. }
  86. _, m := discord.FindMessageByID(mtv.app.SelectedChannel.Messages, hs[0])
  87. if m == nil {
  88. return nil
  89. }
  90. if discord.HasPermission(mtv.app.Session.State, mtv.app.SelectedChannel.ID, discordgo.PermissionSendMessages) {
  91. messageActionsList.
  92. AddItem("Reply", "", 'r', nil).
  93. AddItem("Mention Reply", "", 'R', nil)
  94. }
  95. if m.ReferencedMessage != nil {
  96. messageActionsList.AddItem("Select Reply", "", 'm', nil)
  97. }
  98. messageActionsList.
  99. ShowSecondaryText(false).
  100. AddItem("Copy Content", "", 'c', nil).
  101. AddItem("Copy ID", "", 'i', nil).
  102. SetDoneFunc(func() {
  103. mtv.app.
  104. SetRoot(mtv.app.MainFlex, true).
  105. SetFocus(mtv.app.MessagesTextView)
  106. }).
  107. SetSelectedFunc(func(_ int, mainText string, _ string, _ rune) {
  108. onMessageActionsListSelected(mtv.app, mainText, m)
  109. }).
  110. SetTitle("Press the Escape key to close").
  111. SetBorder(true)
  112. mtv.app.SetRoot(messageActionsList, true)
  113. return nil
  114. case "Esc":
  115. mtv.app.SelectedMessage = -1
  116. mtv.app.SetFocus(mtv.app.MainFlex)
  117. mtv.app.MessagesTextView.
  118. Clear().
  119. Highlight()
  120. return nil
  121. }
  122. return e
  123. }
  124. func onMessageActionsListSelected(app *App, mainText string, m *discordgo.Message) {
  125. switch mainText {
  126. case "Copy Content":
  127. if err := clipboard.WriteAll(m.Content); err != nil {
  128. return
  129. }
  130. app.SetRoot(app.MainFlex, false)
  131. case "Copy ID":
  132. if err := clipboard.WriteAll(m.ID); err != nil {
  133. return
  134. }
  135. app.SetRoot(app.MainFlex, false)
  136. case "Reply":
  137. app.MessageInputField.SetTitle("Replying to " + m.Author.String())
  138. app.
  139. SetRoot(app.MainFlex, false).
  140. SetFocus(app.MessageInputField)
  141. case "Mention Reply":
  142. app.MessageInputField.SetTitle("[@] Replying to " + m.Author.String())
  143. app.
  144. SetRoot(app.MainFlex, false).
  145. SetFocus(app.MessageInputField)
  146. case "Select Reply":
  147. app.SelectedMessage, _ = discord.FindMessageByID(app.SelectedChannel.Messages, m.ReferencedMessage.ID)
  148. app.MessagesTextView.
  149. Highlight(m.ReferencedMessage.ID).
  150. ScrollToHighlight()
  151. app.
  152. SetRoot(app.MainFlex, false).
  153. SetFocus(app.MessagesTextView)
  154. }
  155. }
  156. type MessageInputField struct {
  157. *tview.InputField
  158. app *App
  159. }
  160. func NewMessageInputField(app *App) *MessageInputField {
  161. mi := &MessageInputField{
  162. InputField: tview.NewInputField(),
  163. app: app,
  164. }
  165. mi.SetFieldBackgroundColor(tview.Styles.PrimitiveBackgroundColor)
  166. mi.SetPlaceholder("Message...")
  167. mi.SetPlaceholderStyle(tcell.StyleDefault.Background(tview.Styles.PrimitiveBackgroundColor))
  168. mi.SetTitleAlign(tview.AlignLeft)
  169. mi.SetBorder(true)
  170. mi.SetBorderPadding(0, 0, 1, 1)
  171. mi.SetInputCapture(mi.onInputCapture)
  172. return mi
  173. }
  174. func (mi *MessageInputField) onInputCapture(e *tcell.EventKey) *tcell.EventKey {
  175. switch e.Name() {
  176. case "Enter":
  177. if mi.app.SelectedChannel == nil {
  178. return nil
  179. }
  180. t := strings.TrimSpace(mi.app.MessageInputField.GetText())
  181. if t == "" {
  182. return nil
  183. }
  184. if len(mi.app.MessagesTextView.GetHighlights()) != 0 {
  185. _, m := discord.FindMessageByID(mi.app.SelectedChannel.Messages, mi.app.MessagesTextView.GetHighlights()[0])
  186. d := &discordgo.MessageSend{
  187. Content: t,
  188. Reference: m.Reference(),
  189. AllowedMentions: &discordgo.MessageAllowedMentions{RepliedUser: false},
  190. }
  191. if strings.HasPrefix(mi.app.MessageInputField.GetTitle(), "[@]") {
  192. d.AllowedMentions.RepliedUser = true
  193. } else {
  194. d.AllowedMentions.RepliedUser = false
  195. }
  196. go mi.app.Session.ChannelMessageSendComplex(m.ChannelID, d)
  197. mi.app.SelectedMessage = -1
  198. mi.app.MessagesTextView.Highlight()
  199. mi.app.MessageInputField.SetTitle("")
  200. } else {
  201. go mi.app.Session.ChannelMessageSend(mi.app.SelectedChannel.ID, t)
  202. }
  203. mi.app.MessageInputField.SetText("")
  204. return nil
  205. case "Ctrl+V":
  206. text, _ := clipboard.ReadAll()
  207. text = mi.app.MessageInputField.GetText() + text
  208. mi.app.MessageInputField.SetText(text)
  209. return nil
  210. case "Esc":
  211. mi.app.MessageInputField.
  212. SetText("").
  213. SetTitle("")
  214. mi.app.SetFocus(mi.app.MainFlex)
  215. mi.app.SelectedMessage = -1
  216. mi.app.MessagesTextView.Highlight()
  217. return nil
  218. case mi.app.Config.Keybindings.OpenExternalEditor:
  219. e := os.Getenv("EDITOR")
  220. if e == "" {
  221. return nil
  222. }
  223. f, err := os.CreateTemp(os.TempDir(), "discordo-*.md")
  224. if err != nil {
  225. return nil
  226. }
  227. defer os.Remove(f.Name())
  228. cmd := exec.Command(e, f.Name())
  229. cmd.Stdin = os.Stdin
  230. cmd.Stdout = os.Stdout
  231. mi.app.Suspend(func() {
  232. err = cmd.Run()
  233. if err != nil {
  234. return
  235. }
  236. })
  237. b, err := io.ReadAll(f)
  238. if err != nil {
  239. return nil
  240. }
  241. mi.app.MessageInputField.SetText(string(b))
  242. return nil
  243. }
  244. return e
  245. }