actions_view.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 ActionsView struct {
  15. *tview.List
  16. core *Core
  17. message *discord.Message
  18. }
  19. func newActionsView(c *Core, m *discord.Message) *ActionsView {
  20. v := &ActionsView{
  21. List: tview.NewList(),
  22. core: c,
  23. message: m,
  24. }
  25. v.ShowSecondaryText(false)
  26. v.SetDoneFunc(func() {
  27. c.App.SetRoot(c.View, true)
  28. c.App.SetFocus(c.MessagesView)
  29. })
  30. isDM := channelIsInDMCategory(c.ChannelsView.selectedChannel)
  31. // If the client user has the `SEND_MESSAGES` permission, add "Reply" and "Mention Reply" actions.
  32. if isDM || !isDM && hasPermission(c.State, c.ChannelsView.selectedChannel.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. c.App.SetRoot(c.View, true)
  48. c.App.SetFocus(c.MessagesView)
  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, _ := c.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(c.State, c.ChannelsView.selectedChannel.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 (v *ActionsView) replyAction() {
  71. v.core.InputView.SetTitle("Replying to " + v.message.Author.Tag())
  72. v.core.App.SetRoot(v.core.View, true)
  73. v.core.App.SetFocus(v.core.InputView)
  74. }
  75. func (v *ActionsView) mentionReplyAction() {
  76. v.core.InputView.SetTitle("[@] Replying to " + v.message.Author.Tag())
  77. v.core.App.SetRoot(v.core.View, true)
  78. v.core.App.SetFocus(v.core.InputView)
  79. }
  80. func (v *ActionsView) selectReplyAction() {
  81. ms, err := v.core.State.Cabinet.Messages(v.message.ChannelID)
  82. if err != nil {
  83. return
  84. }
  85. v.core.MessagesView.selectedMessage, _ = findMessageByID(ms, v.message.ReferencedMessage.ID)
  86. v.core.MessagesView.
  87. Highlight(v.message.ReferencedMessage.ID.String()).
  88. ScrollToHighlight()
  89. v.core.App.SetRoot(v.core.View, true)
  90. v.core.App.SetFocus(v.core.MessagesView)
  91. }
  92. func (v *ActionsView) openAttachmentAction() {
  93. for _, a := range v.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. v.core.App.SetRoot(v.core.View, true)
  112. v.core.App.SetFocus(v.core.MessagesView)
  113. }
  114. func (v *ActionsView) downloadAttachmentAction() {
  115. for _, a := range v.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. v.core.App.SetRoot(v.core.View, true)
  137. v.core.App.SetFocus(v.core.MessagesView)
  138. }
  139. func (v *ActionsView) deleteAction() {
  140. v.core.MessagesView.Clear()
  141. err := v.core.State.MessageRemove(v.message.ChannelID, v.message.ID)
  142. if err != nil {
  143. return
  144. }
  145. err = v.core.State.DeleteMessage(v.message.ChannelID, v.message.ID, "Unknown")
  146. if err != nil {
  147. return
  148. }
  149. // The returned slice will be sorted from latest to oldest.
  150. ms, err := v.core.State.Cabinet.Messages(v.message.ChannelID)
  151. if err != nil {
  152. return
  153. }
  154. for i := len(ms) - 1; i >= 0; i-- {
  155. _, err = v.core.MessagesView.Write(buildMessage(v.core, ms[i]))
  156. if err != nil {
  157. return
  158. }
  159. }
  160. v.core.App.SetRoot(v.core.View, true)
  161. v.core.App.SetFocus(v.core.MessagesView)
  162. }
  163. func (v *ActionsView) copyContentAction() {
  164. err := clipboard.WriteAll(v.message.Content)
  165. if err != nil {
  166. return
  167. }
  168. v.core.App.SetRoot(v.core.View, true)
  169. v.core.App.SetFocus(v.core.MessagesView)
  170. }
  171. func (v *ActionsView) copyIDAction() {
  172. err := clipboard.WriteAll(v.message.ID.String())
  173. if err != nil {
  174. return
  175. }
  176. v.core.App.SetRoot(v.core.View, true)
  177. v.core.App.SetFocus(v.core.MessagesView)
  178. }
  179. func (v *ActionsView) copyLinkAction() {
  180. err := clipboard.WriteAll(v.message.URL())
  181. if err != nil {
  182. return
  183. }
  184. v.core.App.SetRoot(v.core.View, true)
  185. v.core.App.SetFocus(v.core.MessagesView)
  186. }