types.go 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. package server
  2. import (
  3. "heckel.io/ntfy/user"
  4. "net/http"
  5. "net/netip"
  6. "time"
  7. "heckel.io/ntfy/util"
  8. )
  9. // List of possible events
  10. const (
  11. openEvent = "open"
  12. keepaliveEvent = "keepalive"
  13. messageEvent = "message"
  14. pollRequestEvent = "poll_request"
  15. )
  16. const (
  17. messageIDLength = 12
  18. )
  19. // message represents a message published to a topic
  20. type message struct {
  21. ID string `json:"id"` // Random message ID
  22. Time int64 `json:"time"` // Unix time in seconds
  23. Event string `json:"event"` // One of the above
  24. Topic string `json:"topic"`
  25. Title string `json:"title,omitempty"`
  26. Message string `json:"message,omitempty"`
  27. Priority int `json:"priority,omitempty"`
  28. Tags []string `json:"tags,omitempty"`
  29. Click string `json:"click,omitempty"`
  30. Icon string `json:"icon,omitempty"`
  31. Actions []*action `json:"actions,omitempty"`
  32. Attachment *attachment `json:"attachment,omitempty"`
  33. PollID string `json:"poll_id,omitempty"`
  34. Encoding string `json:"encoding,omitempty"` // empty for raw UTF-8, or "base64" for encoded bytes
  35. Sender netip.Addr `json:"-"` // IP address of uploader, used for rate limiting
  36. User string `json:"-"` // Username of the uploader, used to associated attachments
  37. }
  38. type attachment struct {
  39. Name string `json:"name"`
  40. Type string `json:"type,omitempty"`
  41. Size int64 `json:"size,omitempty"`
  42. Expires int64 `json:"expires,omitempty"`
  43. URL string `json:"url"`
  44. }
  45. type action struct {
  46. ID string `json:"id"`
  47. Action string `json:"action"` // "view", "broadcast", or "http"
  48. Label string `json:"label"` // action button label
  49. Clear bool `json:"clear"` // clear notification after successful execution
  50. URL string `json:"url,omitempty"` // used in "view" and "http" actions
  51. Method string `json:"method,omitempty"` // used in "http" action, default is POST (!)
  52. Headers map[string]string `json:"headers,omitempty"` // used in "http" action
  53. Body string `json:"body,omitempty"` // used in "http" action
  54. Intent string `json:"intent,omitempty"` // used in "broadcast" action
  55. Extras map[string]string `json:"extras,omitempty"` // used in "broadcast" action
  56. }
  57. func newAction() *action {
  58. return &action{
  59. Headers: make(map[string]string),
  60. Extras: make(map[string]string),
  61. }
  62. }
  63. // publishMessage is used as input when publishing as JSON
  64. type publishMessage struct {
  65. Topic string `json:"topic"`
  66. Title string `json:"title"`
  67. Message string `json:"message"`
  68. Priority int `json:"priority"`
  69. Tags []string `json:"tags"`
  70. Click string `json:"click"`
  71. Icon string `json:"icon"`
  72. Actions []action `json:"actions"`
  73. Attach string `json:"attach"`
  74. Filename string `json:"filename"`
  75. Email string `json:"email"`
  76. Delay string `json:"delay"`
  77. }
  78. // messageEncoder is a function that knows how to encode a message
  79. type messageEncoder func(msg *message) (string, error)
  80. // newMessage creates a new message with the current timestamp
  81. func newMessage(event, topic, msg string) *message {
  82. return &message{
  83. ID: util.RandomString(messageIDLength),
  84. Time: time.Now().Unix(),
  85. Event: event,
  86. Topic: topic,
  87. Message: msg,
  88. }
  89. }
  90. // newOpenMessage is a convenience method to create an open message
  91. func newOpenMessage(topic string) *message {
  92. return newMessage(openEvent, topic, "")
  93. }
  94. // newKeepaliveMessage is a convenience method to create a keepalive message
  95. func newKeepaliveMessage(topic string) *message {
  96. return newMessage(keepaliveEvent, topic, "")
  97. }
  98. // newDefaultMessage is a convenience method to create a notification message
  99. func newDefaultMessage(topic, msg string) *message {
  100. return newMessage(messageEvent, topic, msg)
  101. }
  102. // newPollRequestMessage is a convenience method to create a poll request message
  103. func newPollRequestMessage(topic, pollID string) *message {
  104. m := newMessage(pollRequestEvent, topic, newMessageBody)
  105. m.PollID = pollID
  106. return m
  107. }
  108. func validMessageID(s string) bool {
  109. return util.ValidRandomString(s, messageIDLength)
  110. }
  111. type sinceMarker struct {
  112. time time.Time
  113. id string
  114. }
  115. func newSinceTime(timestamp int64) sinceMarker {
  116. return sinceMarker{time.Unix(timestamp, 0), ""}
  117. }
  118. func newSinceID(id string) sinceMarker {
  119. return sinceMarker{time.Unix(0, 0), id}
  120. }
  121. func (t sinceMarker) IsAll() bool {
  122. return t == sinceAllMessages
  123. }
  124. func (t sinceMarker) IsNone() bool {
  125. return t == sinceNoMessages
  126. }
  127. func (t sinceMarker) IsID() bool {
  128. return t.id != ""
  129. }
  130. func (t sinceMarker) Time() time.Time {
  131. return t.time
  132. }
  133. func (t sinceMarker) ID() string {
  134. return t.id
  135. }
  136. var (
  137. sinceAllMessages = sinceMarker{time.Unix(0, 0), ""}
  138. sinceNoMessages = sinceMarker{time.Unix(1, 0), ""}
  139. )
  140. type queryFilter struct {
  141. ID string
  142. Message string
  143. Title string
  144. Tags []string
  145. Priority []int
  146. }
  147. func parseQueryFilters(r *http.Request) (*queryFilter, error) {
  148. idFilter := readParam(r, "x-id", "id")
  149. messageFilter := readParam(r, "x-message", "message", "m")
  150. titleFilter := readParam(r, "x-title", "title", "t")
  151. tagsFilter := util.SplitNoEmpty(readParam(r, "x-tags", "tags", "tag", "ta"), ",")
  152. priorityFilter := make([]int, 0)
  153. for _, p := range util.SplitNoEmpty(readParam(r, "x-priority", "priority", "prio", "p"), ",") {
  154. priority, err := util.ParsePriority(p)
  155. if err != nil {
  156. return nil, errHTTPBadRequestPriorityInvalid
  157. }
  158. priorityFilter = append(priorityFilter, priority)
  159. }
  160. return &queryFilter{
  161. ID: idFilter,
  162. Message: messageFilter,
  163. Title: titleFilter,
  164. Tags: tagsFilter,
  165. Priority: priorityFilter,
  166. }, nil
  167. }
  168. func (q *queryFilter) Pass(msg *message) bool {
  169. if msg.Event != messageEvent {
  170. return true // filters only apply to messages
  171. } else if q.ID != "" && msg.ID != q.ID {
  172. return false
  173. } else if q.Message != "" && msg.Message != q.Message {
  174. return false
  175. } else if q.Title != "" && msg.Title != q.Title {
  176. return false
  177. }
  178. messagePriority := msg.Priority
  179. if messagePriority == 0 {
  180. messagePriority = 3 // For query filters, default priority (3) is the same as "not set" (0)
  181. }
  182. if len(q.Priority) > 0 && !util.Contains(q.Priority, messagePriority) {
  183. return false
  184. }
  185. if len(q.Tags) > 0 && !util.ContainsAll(msg.Tags, q.Tags) {
  186. return false
  187. }
  188. return true
  189. }
  190. type apiHealthResponse struct {
  191. Healthy bool `json:"healthy"`
  192. }
  193. type apiAccountCreateRequest struct {
  194. Username string `json:"username"`
  195. Password string `json:"password"`
  196. }
  197. type apiAccountTokenResponse struct {
  198. Token string `json:"token"`
  199. Expires int64 `json:"expires"`
  200. }
  201. type apiAccountPlan struct {
  202. Code string `json:"code"`
  203. Upgradable bool `json:"upgradable"`
  204. }
  205. type apiAccountLimits struct {
  206. Basis string `json:"basis"` // "ip", "role" or "plan"
  207. Messages int64 `json:"messages"`
  208. Emails int64 `json:"emails"`
  209. AttachmentTotalSize int64 `json:"attachment_total_size"`
  210. AttachmentFileSize int64 `json:"attachment_file_size"`
  211. }
  212. type apiAccountStats struct {
  213. Messages int64 `json:"messages"`
  214. MessagesRemaining int64 `json:"messages_remaining"`
  215. Emails int64 `json:"emails"`
  216. EmailsRemaining int64 `json:"emails_remaining"`
  217. AttachmentTotalSize int64 `json:"attachment_total_size"`
  218. AttachmentTotalSizeRemaining int64 `json:"attachment_total_size_remaining"`
  219. }
  220. type apiAccountSettingsResponse struct {
  221. Username string `json:"username"`
  222. Role string `json:"role,omitempty"`
  223. Language string `json:"language,omitempty"`
  224. Notification *user.NotificationPrefs `json:"notification,omitempty"`
  225. Subscriptions []*user.Subscription `json:"subscriptions,omitempty"`
  226. Plan *apiAccountPlan `json:"plan,omitempty"`
  227. Limits *apiAccountLimits `json:"limits,omitempty"`
  228. Stats *apiAccountStats `json:"stats,omitempty"`
  229. }