notification.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. package main
  2. import (
  3. "errors"
  4. fs "imuslab.com/arozos/mod/filesystem"
  5. notification "imuslab.com/arozos/mod/notification"
  6. "imuslab.com/arozos/mod/notification/agents/desktopn"
  7. "imuslab.com/arozos/mod/notification/agents/smtpn"
  8. "imuslab.com/arozos/mod/notification/agents/telegram"
  9. "imuslab.com/arozos/mod/notification/agents/webhookn"
  10. "imuslab.com/arozos/mod/notification/preference"
  11. )
  12. /*
  13. Notification System Wiring
  14. This file constructs the ArozOS core notification queue, registers all the
  15. delivery agents (email, Telegram, desktop, custom webhook) and exposes a
  16. preference-aware router (sendUserNotification) used by the AGI notification
  17. library and the notification settings endpoints.
  18. Delivery routing:
  19. A producer raises a NotificationPayload (with a priority) for one or more
  20. receivers. For each receiver, their personal preferences decide which
  21. registered agents actually deliver it and whether the notification meets
  22. their minimum priority threshold.
  23. */
  24. const (
  25. smtpnConfigPath = "./system/smtp_conf.json"
  26. telegramConfigPath = "./system/telegram_conf.json"
  27. )
  28. var (
  29. notificationQueue *notification.NotificationQueue
  30. notificationPreferenceStore *preference.Store
  31. desktopNotificationAgent *desktopn.Agent
  32. telegramNotificationAgent *telegram.Agent
  33. smtpNotificationAgent *smtpn.Agent
  34. )
  35. func notificationInit() {
  36. //Create a new notification queue
  37. notificationQueue = notification.NewNotificationQueue()
  38. //Create the per-user preference store backed by the system database
  39. store, err := preference.NewStore(userHandler.GetDatabase())
  40. if err != nil {
  41. systemWideLogger.PrintAndLog("Notification", "Unable to init notification preference store: "+err.Error(), err)
  42. }
  43. notificationPreferenceStore = store
  44. /*
  45. SMTP (Email) Notification Agent
  46. For handling notification sending via Mail
  47. */
  48. if !fs.FileExists(smtpnConfigPath) {
  49. smtpn.GenerateEmptyConfigFile(smtpnConfigPath)
  50. }
  51. smtpAgent, err := smtpn.NewSMTPNotificationAgent(*host_name, smtpnConfigPath,
  52. func(username string) (string, error) {
  53. return registerHandler.GetUserEmail(username)
  54. })
  55. if err != nil {
  56. systemWideLogger.PrintAndLog("Notification", "Unable to start smtpn agent: "+err.Error(), nil)
  57. } else {
  58. smtpAgent.SystemUUIDProvider = systemIdGetSystemUUID
  59. smtpNotificationAgent = smtpAgent
  60. notificationQueue.RegisterNotificationAgent(smtpAgent)
  61. }
  62. /*
  63. Telegram Notification Agent
  64. Delivers notifications through a Telegram bot; per-user chat ids come
  65. from the notification preference store.
  66. */
  67. if !fs.FileExists(telegramConfigPath) {
  68. telegram.GenerateEmptyConfigFile(telegramConfigPath)
  69. }
  70. telegramAgent, err := telegram.NewTelegramNotificationAgent(telegramConfigPath, telegramChatIDResolver())
  71. if err != nil {
  72. systemWideLogger.PrintAndLog("Notification", "Unable to start telegram agent: "+err.Error(), nil)
  73. } else {
  74. telegramNotificationAgent = telegramAgent
  75. notificationQueue.RegisterNotificationAgent(telegramAgent)
  76. }
  77. /*
  78. Desktop Notification Agent
  79. Buffers notifications in memory so the web desktop can poll and render
  80. them (notification list, desktop popup, browser push).
  81. */
  82. desktopNotificationAgent = desktopn.NewDesktopNotificationAgent()
  83. notificationQueue.RegisterNotificationAgent(desktopNotificationAgent)
  84. /*
  85. Custom HTML API (Webhook) Notification Agent
  86. Delivers notifications to a per-user configurable HTTP endpoint.
  87. */
  88. webhookAgent := webhookn.NewWebhookNotificationAgent(webhookTargetResolver())
  89. notificationQueue.RegisterNotificationAgent(webhookAgent)
  90. systemWideLogger.PrintAndLog("Notification", "Notification system started with agents: "+joinStrings(notificationQueue.ListConsumerAgentNames()), nil)
  91. }
  92. // telegramChatIDResolver returns a resolver that reads the per-user Telegram
  93. // chat id from the preference store (nil-safe).
  94. func telegramChatIDResolver() func(string) (string, error) {
  95. return func(username string) (string, error) {
  96. if notificationPreferenceStore == nil {
  97. return "", errNotificationStoreUnavailable
  98. }
  99. return notificationPreferenceStore.TelegramChatIDResolver()(username)
  100. }
  101. }
  102. // webhookTargetResolver returns a resolver that reads the per-user webhook
  103. // target from the preference store (nil-safe).
  104. func webhookTargetResolver() func(string) (webhookn.Target, error) {
  105. return func(username string) (webhookn.Target, error) {
  106. if notificationPreferenceStore == nil {
  107. return webhookn.Target{}, errNotificationStoreUnavailable
  108. }
  109. return notificationPreferenceStore.WebhookResolver()(username)
  110. }
  111. }
  112. var errNotificationStoreUnavailable = errors.New("notification preference store unavailable")
  113. // sendUserNotification is the preference-aware router. For each receiver, it
  114. // resolves the delivery agents according to that user's preferences and
  115. // priority threshold, then broadcasts a per-user copy through the queue.
  116. // This is the entry point used by the AGI notification library.
  117. func sendUserNotification(payload *notification.NotificationPayload) error {
  118. if notificationQueue == nil {
  119. return errors.New("notification system not initialised")
  120. }
  121. if payload == nil {
  122. return errors.New("nil notification payload")
  123. }
  124. payload.Priority = notification.NormalizePriority(payload.Priority)
  125. availableAgents := notificationQueue.ListConsumerAgentNames()
  126. for _, username := range payload.Receiver {
  127. var targetAgents []string
  128. if notificationPreferenceStore != nil {
  129. targetAgents = notificationPreferenceStore.ResolveAgents(username, payload.Priority, availableAgents, payload.ReciverAgents)
  130. } else {
  131. //No preference store: fall back to producer-requested agents (or none).
  132. targetAgents = payload.ReciverAgents
  133. }
  134. if len(targetAgents) == 0 {
  135. //Nothing enabled for this user at this priority; skip.
  136. continue
  137. }
  138. perUser := *payload
  139. perUser.Receiver = []string{username}
  140. perUser.ReciverAgents = targetAgents
  141. notificationQueue.BroadcastNotification(&perUser)
  142. }
  143. return nil
  144. }
  145. // joinStrings joins a slice with ", " without pulling in strings just for one call site.
  146. func joinStrings(items []string) string {
  147. out := ""
  148. for i, item := range items {
  149. if i > 0 {
  150. out += ", "
  151. }
  152. out += item
  153. }
  154. return out
  155. }