actions_list.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. package ui
  2. import (
  3. "io"
  4. "net/http"
  5. "os"
  6. "path/filepath"
  7. "regexp"
  8. "github.com/atotto/clipboard"
  9. "github.com/diamondburned/arikawa/v3/discord"
  10. "github.com/rivo/tview"
  11. "github.com/skratchdot/open-golang/open"
  12. )
  13. var linkRegex = regexp.MustCompile("https?://.+")
  14. type ActionsList struct {
  15. *tview.List
  16. app *Application
  17. message *discord.Message
  18. }
  19. func newActionsList(app *Application, m *discord.Message) *ActionsList {
  20. v := &ActionsList{
  21. List: tview.NewList(),
  22. app: app,
  23. message: m,
  24. }
  25. v.ShowSecondaryText(false)
  26. v.SetDoneFunc(func() {
  27. app.SetRoot(app.view, true)
  28. app.SetFocus(app.view.MessagesText)
  29. })
  30. isDM := channelIsInDMCategory(app.view.ChannelsTree.selected)
  31. // If the client user has the `SEND_MESSAGES` permission, add "Reply" and "Mention Reply" actions.
  32. if isDM || !isDM && hasPermission(app.state, app.view.ChannelsTree.selected.ID, discord.PermissionSendMessages) {
  33. v.AddItem("Reply", "", 'r', v.replyAction)
  34. v.AddItem("Mention Reply", "", 'R', v.mentionReplyAction)
  35. }
  36. // If the referenced message exists, add a new action to select the reply.
  37. if m.ReferencedMessage != nil {
  38. v.AddItem("Select Reply", "", 'm', v.selectReplyAction)
  39. }
  40. // If the content of the message contains link(s), add the appropriate actions.
  41. links := linkRegex.FindAllString(m.Content, -1)
  42. if len(links) != 0 {
  43. v.AddItem("Open Link", "", 'l', func() {
  44. for _, l := range links {
  45. go open.Run(l)
  46. }
  47. app.SetRoot(app.view, true)
  48. app.SetFocus(app.view.MessagesText)
  49. })
  50. }
  51. // If the message contains attachments, add the appropriate actions to the actions view.
  52. if len(m.Attachments) != 0 {
  53. v.AddItem("Open Attachment", "", 'o', v.openAttachmentAction)
  54. v.AddItem("Download Attachment", "", 'd', v.downloadAttachmentAction)
  55. }
  56. me, _ := app.state.MeStore.Me()
  57. // If the client user has the `MANAGE_MESSAGES` permission, add a new action to delete the message.
  58. if (isDM && m.Author.ID == me.ID) || (!isDM && hasPermission(app.state, app.view.ChannelsTree.selected.ID, discord.PermissionManageMessages)) {
  59. v.AddItem("Delete", "", 'd', v.deleteAction)
  60. }
  61. v.AddItem("Copy Content", "", 'c', v.copyContentAction)
  62. v.AddItem("Copy ID", "", 'i', v.copyIDAction)
  63. v.AddItem("Copy Link", "", 'k', v.copyLinkAction)
  64. v.SetTitle("Press the Escape key to close")
  65. v.SetTitleAlign(tview.AlignLeft)
  66. v.SetBorder(true)
  67. v.SetBorderPadding(0, 0, 1, 1)
  68. return v
  69. }
  70. func (al *ActionsList) replyAction() {
  71. al.app.view.MessageInput.SetTitle("Replying to " + al.message.Author.Tag())
  72. al.app.SetRoot(al.app.view, true)
  73. al.app.SetFocus(al.app.view.MessageInput)
  74. }
  75. func (al *ActionsList) mentionReplyAction() {
  76. al.app.view.MessageInput.SetTitle("[@] Replying to " + al.message.Author.Tag())
  77. al.app.SetRoot(al.app.view, true)
  78. al.app.SetFocus(al.app.view.MessageInput)
  79. }
  80. func (al *ActionsList) selectReplyAction() {
  81. ms, err := al.app.state.Cabinet.Messages(al.message.ChannelID)
  82. if err != nil {
  83. return
  84. }
  85. al.app.view.MessagesText.selected, _ = findMessageByID(ms, al.message.ReferencedMessage.ID)
  86. al.app.view.MessagesText.
  87. Highlight(al.message.ReferencedMessage.ID.String()).
  88. ScrollToHighlight()
  89. al.app.SetRoot(al.app.view, true)
  90. al.app.SetFocus(al.app.view.MessagesText)
  91. }
  92. func (al *ActionsList) openAttachmentAction() {
  93. for _, a := range al.message.Attachments {
  94. cacheDirPath, _ := os.UserCacheDir()
  95. f, err := os.Create(filepath.Join(cacheDirPath, a.Filename))
  96. if err != nil {
  97. return
  98. }
  99. defer f.Close()
  100. resp, err := http.Get(a.URL)
  101. if err != nil {
  102. return
  103. }
  104. d, err := io.ReadAll(resp.Body)
  105. if err != nil {
  106. return
  107. }
  108. f.Write(d)
  109. go open.Run(f.Name())
  110. }
  111. al.app.SetRoot(al.app.view, true)
  112. al.app.SetFocus(al.app.view.MessagesText)
  113. }
  114. func (al *ActionsList) downloadAttachmentAction() {
  115. for _, a := range al.message.Attachments {
  116. path, err := os.UserHomeDir()
  117. if err != nil {
  118. path = os.TempDir()
  119. }
  120. path = filepath.Join(path, "Downloads", a.Filename)
  121. f, err := os.Create(path)
  122. if err != nil {
  123. return
  124. }
  125. defer f.Close()
  126. resp, err := http.Get(a.URL)
  127. if err != nil {
  128. return
  129. }
  130. d, err := io.ReadAll(resp.Body)
  131. if err != nil {
  132. return
  133. }
  134. f.Write(d)
  135. }
  136. al.app.SetRoot(al.app.view, true)
  137. al.app.SetFocus(al.app.view.MessagesText)
  138. }
  139. func (al *ActionsList) deleteAction() {
  140. al.app.view.MessagesText.Clear()
  141. err := al.app.state.MessageRemove(al.message.ChannelID, al.message.ID)
  142. if err != nil {
  143. return
  144. }
  145. err = al.app.state.DeleteMessage(al.message.ChannelID, al.message.ID, "Unknown")
  146. if err != nil {
  147. return
  148. }
  149. // The returned slice will be sorted from latest to oldest.
  150. ms, err := al.app.state.Cabinet.Messages(al.message.ChannelID)
  151. if err != nil {
  152. return
  153. }
  154. for i := len(ms) - 1; i >= 0; i-- {
  155. _, err = al.app.view.MessagesText.Write(buildMessage(al.app, ms[i]))
  156. if err != nil {
  157. return
  158. }
  159. }
  160. al.app.SetRoot(al.app.view, true)
  161. al.app.SetFocus(al.app.view.MessagesText)
  162. }
  163. func (al *ActionsList) copyContentAction() {
  164. err := clipboard.WriteAll(al.message.Content)
  165. if err != nil {
  166. return
  167. }
  168. al.app.SetRoot(al.app.view, true)
  169. al.app.SetFocus(al.app.view.MessagesText)
  170. }
  171. func (al *ActionsList) copyIDAction() {
  172. err := clipboard.WriteAll(al.message.ID.String())
  173. if err != nil {
  174. return
  175. }
  176. al.app.SetRoot(al.app.view, true)
  177. al.app.SetFocus(al.app.view.MessagesText)
  178. }
  179. func (al *ActionsList) copyLinkAction() {
  180. err := clipboard.WriteAll(al.message.URL())
  181. if err != nil {
  182. return
  183. }
  184. al.app.SetRoot(al.app.view, true)
  185. al.app.SetFocus(al.app.view.MessagesText)
  186. }