notifications.go 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. package notifications
  2. import (
  3. "io"
  4. "log/slog"
  5. "net/http"
  6. "os"
  7. "path/filepath"
  8. "strings"
  9. "github.com/ayn2op/discordo/internal/config"
  10. "github.com/ayn2op/discordo/internal/consts"
  11. "github.com/diamondburned/arikawa/v3/discord"
  12. "github.com/diamondburned/arikawa/v3/gateway"
  13. "github.com/diamondburned/ningen/v3"
  14. "github.com/diamondburned/ningen/v3/discordmd"
  15. )
  16. func HandleIncomingMessage(state *ningen.State, msg *gateway.MessageCreateEvent, cfg *config.Config) error {
  17. // Only display notification if enabled and unmuted
  18. if !cfg.Notifications.Enabled || state.MessageMentions(&msg.Message) == 0 || cfg.Identify.Status == discord.DoNotDisturbStatus {
  19. return nil
  20. }
  21. ch, err := state.Cabinet.Channel(msg.ChannelID)
  22. if err != nil {
  23. return err
  24. }
  25. isChannelDM := ch.Type == discord.DirectMessage || ch.Type == discord.GroupDM
  26. guild := (*discord.Guild)(nil)
  27. if !isChannelDM {
  28. guild, err = state.Cabinet.Guild(ch.GuildID)
  29. if err != nil {
  30. return err
  31. }
  32. }
  33. // Render message
  34. src := []byte(msg.Content)
  35. ast := discordmd.ParseWithMessage(src, *state.Cabinet, &msg.Message, false)
  36. buff := strings.Builder{}
  37. if err := defaultRenderer.Render(&buff, src, ast); err != nil {
  38. return err
  39. }
  40. // Handle sent files
  41. notifContent := buff.String()
  42. if msg.Content == "" && len(msg.Attachments) > 0 {
  43. notifContent = "Uploaded " + msg.Message.Attachments[0].Filename
  44. }
  45. if msg.Author.DisplayOrTag() == "" || notifContent == "" {
  46. return nil
  47. }
  48. notifTitle := msg.Author.DisplayOrTag()
  49. if guild != nil {
  50. member, _ := state.Member(ch.GuildID, msg.Author.ID)
  51. if member.Nick != "" {
  52. notifTitle = member.Nick
  53. }
  54. notifTitle = notifTitle + " (#" + ch.Name + ", " + guild.Name + ")"
  55. }
  56. hash := msg.Author.Avatar
  57. if hash == "" {
  58. hash = "default"
  59. }
  60. imagePath, err := getCachedProfileImage(hash, msg.Author.AvatarURLWithType(discord.PNGImage))
  61. if err != nil {
  62. slog.Error("Failed to retrieve avatar image for notification", "err", err)
  63. }
  64. shouldChime := cfg.Notifications.Sound.Enabled && (!cfg.Notifications.Sound.OnlyOnPing || (isChannelDM || state.MessageMentions(&msg.Message) == 3))
  65. if err := sendDesktopNotification(notifTitle, notifContent, imagePath, shouldChime, cfg.Notifications.Duration); err != nil {
  66. return err
  67. }
  68. return nil
  69. }
  70. func getCachedProfileImage(avatarHash discord.Hash, url string) (string, error) {
  71. path, err := os.UserCacheDir()
  72. if err != nil {
  73. return "", err
  74. }
  75. path = filepath.Join(path, consts.Name, "assets")
  76. if err := os.MkdirAll(path, os.ModePerm); err != nil {
  77. return "", err
  78. }
  79. path = filepath.Join(path, avatarHash+".png")
  80. if _, err := os.Stat(path); err == nil {
  81. return path, nil
  82. }
  83. image, err := os.Create(path)
  84. if err != nil {
  85. return "", err
  86. }
  87. defer image.Close()
  88. resp, err := http.Get(url)
  89. if err != nil {
  90. return "", err
  91. }
  92. defer resp.Body.Close()
  93. if _, err := io.Copy(image, resp.Body); err != nil {
  94. return "", err
  95. }
  96. return path, nil
  97. }