types.go 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. package server
  2. import (
  3. "heckel.io/ntfy/auth"
  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. Sender netip.Addr `json:"-"` // IP address of uploader, used for rate limiting
  35. Encoding string `json:"encoding,omitempty"` // empty for raw UTF-8, or "base64" for encoded bytes
  36. }
  37. type attachment struct {
  38. Name string `json:"name"`
  39. Type string `json:"type,omitempty"`
  40. Size int64 `json:"size,omitempty"`
  41. Expires int64 `json:"expires,omitempty"`
  42. URL string `json:"url"`
  43. }
  44. type action struct {
  45. ID string `json:"id"`
  46. Action string `json:"action"` // "view", "broadcast", or "http"
  47. Label string `json:"label"` // action button label
  48. Clear bool `json:"clear"` // clear notification after successful execution
  49. URL string `json:"url,omitempty"` // used in "view" and "http" actions
  50. Method string `json:"method,omitempty"` // used in "http" action, default is POST (!)
  51. Headers map[string]string `json:"headers,omitempty"` // used in "http" action
  52. Body string `json:"body,omitempty"` // used in "http" action
  53. Intent string `json:"intent,omitempty"` // used in "broadcast" action
  54. Extras map[string]string `json:"extras,omitempty"` // used in "broadcast" action
  55. }
  56. func newAction() *action {
  57. return &action{
  58. Headers: make(map[string]string),
  59. Extras: make(map[string]string),
  60. }
  61. }
  62. // publishMessage is used as input when publishing as JSON
  63. type publishMessage struct {
  64. Topic string `json:"topic"`
  65. Title string `json:"title"`
  66. Message string `json:"message"`
  67. Priority int `json:"priority"`
  68. Tags []string `json:"tags"`
  69. Click string `json:"click"`
  70. Icon string `json:"icon"`
  71. Actions []action `json:"actions"`
  72. Attach string `json:"attach"`
  73. Filename string `json:"filename"`
  74. Email string `json:"email"`
  75. Delay string `json:"delay"`
  76. }
  77. // messageEncoder is a function that knows how to encode a message
  78. type messageEncoder func(msg *message) (string, error)
  79. // newMessage creates a new message with the current timestamp
  80. func newMessage(event, topic, msg string) *message {
  81. return &message{
  82. ID: util.RandomString(messageIDLength),
  83. Time: time.Now().Unix(),
  84. Event: event,
  85. Topic: topic,
  86. Message: msg,
  87. }
  88. }
  89. // newOpenMessage is a convenience method to create an open message
  90. func newOpenMessage(topic string) *message {
  91. return newMessage(openEvent, topic, "")
  92. }
  93. // newKeepaliveMessage is a convenience method to create a keepalive message
  94. func newKeepaliveMessage(topic string) *message {
  95. return newMessage(keepaliveEvent, topic, "")
  96. }
  97. // newDefaultMessage is a convenience method to create a notification message
  98. func newDefaultMessage(topic, msg string) *message {
  99. return newMessage(messageEvent, topic, msg)
  100. }
  101. // newPollRequestMessage is a convenience method to create a poll request message
  102. func newPollRequestMessage(topic, pollID string) *message {
  103. m := newMessage(pollRequestEvent, topic, newMessageBody)
  104. m.PollID = pollID
  105. return m
  106. }
  107. func validMessageID(s string) bool {
  108. return util.ValidRandomString(s, messageIDLength)
  109. }
  110. type sinceMarker struct {
  111. time time.Time
  112. id string
  113. }
  114. func newSinceTime(timestamp int64) sinceMarker {
  115. return sinceMarker{time.Unix(timestamp, 0), ""}
  116. }
  117. func newSinceID(id string) sinceMarker {
  118. return sinceMarker{time.Unix(0, 0), id}
  119. }
  120. func (t sinceMarker) IsAll() bool {
  121. return t == sinceAllMessages
  122. }
  123. func (t sinceMarker) IsNone() bool {
  124. return t == sinceNoMessages
  125. }
  126. func (t sinceMarker) IsID() bool {
  127. return t.id != ""
  128. }
  129. func (t sinceMarker) Time() time.Time {
  130. return t.time
  131. }
  132. func (t sinceMarker) ID() string {
  133. return t.id
  134. }
  135. var (
  136. sinceAllMessages = sinceMarker{time.Unix(0, 0), ""}
  137. sinceNoMessages = sinceMarker{time.Unix(1, 0), ""}
  138. )
  139. type queryFilter struct {
  140. ID string
  141. Message string
  142. Title string
  143. Tags []string
  144. Priority []int
  145. }
  146. func parseQueryFilters(r *http.Request) (*queryFilter, error) {
  147. idFilter := readParam(r, "x-id", "id")
  148. messageFilter := readParam(r, "x-message", "message", "m")
  149. titleFilter := readParam(r, "x-title", "title", "t")
  150. tagsFilter := util.SplitNoEmpty(readParam(r, "x-tags", "tags", "tag", "ta"), ",")
  151. priorityFilter := make([]int, 0)
  152. for _, p := range util.SplitNoEmpty(readParam(r, "x-priority", "priority", "prio", "p"), ",") {
  153. priority, err := util.ParsePriority(p)
  154. if err != nil {
  155. return nil, errHTTPBadRequestPriorityInvalid
  156. }
  157. priorityFilter = append(priorityFilter, priority)
  158. }
  159. return &queryFilter{
  160. ID: idFilter,
  161. Message: messageFilter,
  162. Title: titleFilter,
  163. Tags: tagsFilter,
  164. Priority: priorityFilter,
  165. }, nil
  166. }
  167. func (q *queryFilter) Pass(msg *message) bool {
  168. if msg.Event != messageEvent {
  169. return true // filters only apply to messages
  170. } else if q.ID != "" && msg.ID != q.ID {
  171. return false
  172. } else if q.Message != "" && msg.Message != q.Message {
  173. return false
  174. } else if q.Title != "" && msg.Title != q.Title {
  175. return false
  176. }
  177. messagePriority := msg.Priority
  178. if messagePriority == 0 {
  179. messagePriority = 3 // For query filters, default priority (3) is the same as "not set" (0)
  180. }
  181. if len(q.Priority) > 0 && !util.Contains(q.Priority, messagePriority) {
  182. return false
  183. }
  184. if len(q.Tags) > 0 && !util.ContainsAll(msg.Tags, q.Tags) {
  185. return false
  186. }
  187. return true
  188. }
  189. type apiAccountCreateRequest struct {
  190. Username string `json:"username"`
  191. Password string `json:"password"`
  192. }
  193. type apiAccountTokenResponse struct {
  194. Token string `json:"token"`
  195. }
  196. type apiAccountSettingsPlan struct {
  197. Id int `json:"id"`
  198. Name string `json:"name"`
  199. }
  200. type apiAccountSettingsResponse struct {
  201. Username string `json:"username"`
  202. Role string `json:"role,omitempty"`
  203. Plan *apiAccountSettingsPlan `json:"plan,omitempty"`
  204. Language string `json:"language,omitempty"`
  205. Notification *auth.UserNotificationPrefs `json:"notification,omitempty"`
  206. Subscriptions []*auth.UserSubscription `json:"subscriptions,omitempty"`
  207. }