types.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. package server
  2. import (
  3. "heckel.io/ntfy/util"
  4. "net/http"
  5. "time"
  6. )
  7. // List of possible events
  8. const (
  9. openEvent = "open"
  10. keepaliveEvent = "keepalive"
  11. messageEvent = "message"
  12. pollRequestEvent = "poll_request"
  13. )
  14. const (
  15. messageIDLength = 12
  16. )
  17. // message represents a message published to a topic
  18. type message struct {
  19. ID string `json:"id"` // Random message ID
  20. Time int64 `json:"time"` // Unix time in seconds
  21. Event string `json:"event"` // One of the above
  22. Topic string `json:"topic"`
  23. Priority int `json:"priority,omitempty"`
  24. Tags []string `json:"tags,omitempty"`
  25. Click string `json:"click,omitempty"`
  26. Attachment *attachment `json:"attachment,omitempty"`
  27. Title string `json:"title,omitempty"`
  28. Message string `json:"message,omitempty"`
  29. Encoding string `json:"encoding,omitempty"` // empty for raw UTF-8, or "base64" for encoded bytes
  30. }
  31. type attachment struct {
  32. Name string `json:"name"`
  33. Type string `json:"type,omitempty"`
  34. Size int64 `json:"size,omitempty"`
  35. Expires int64 `json:"expires,omitempty"`
  36. URL string `json:"url"`
  37. Owner string `json:"-"` // IP address of uploader, used for rate limiting
  38. }
  39. // publishMessage is used as input when publishing as JSON
  40. type publishMessage struct {
  41. Topic string `json:"topic"`
  42. Title string `json:"title"`
  43. Message string `json:"message"`
  44. Priority int `json:"priority"`
  45. Tags []string `json:"tags"`
  46. Click string `json:"click"`
  47. Attach string `json:"attach"`
  48. Filename string `json:"filename"`
  49. Email string `json:"email"`
  50. Delay string `json:"delay"`
  51. }
  52. // messageEncoder is a function that knows how to encode a message
  53. type messageEncoder func(msg *message) (string, error)
  54. // newMessage creates a new message with the current timestamp
  55. func newMessage(event, topic, msg string) *message {
  56. return &message{
  57. ID: util.RandomString(messageIDLength),
  58. Time: time.Now().Unix(),
  59. Event: event,
  60. Topic: topic,
  61. Priority: 0,
  62. Tags: nil,
  63. Title: "",
  64. Message: msg,
  65. }
  66. }
  67. // newOpenMessage is a convenience method to create an open message
  68. func newOpenMessage(topic string) *message {
  69. return newMessage(openEvent, topic, "")
  70. }
  71. // newKeepaliveMessage is a convenience method to create a keepalive message
  72. func newKeepaliveMessage(topic string) *message {
  73. return newMessage(keepaliveEvent, topic, "")
  74. }
  75. // newDefaultMessage is a convenience method to create a notification message
  76. func newDefaultMessage(topic, msg string) *message {
  77. return newMessage(messageEvent, topic, msg)
  78. }
  79. func validMessageID(s string) bool {
  80. return util.ValidRandomString(s, messageIDLength)
  81. }
  82. type sinceMarker struct {
  83. time time.Time
  84. id string
  85. }
  86. func newSinceTime(timestamp int64) sinceMarker {
  87. return sinceMarker{time.Unix(timestamp, 0), ""}
  88. }
  89. func newSinceID(id string) sinceMarker {
  90. return sinceMarker{time.Unix(0, 0), id}
  91. }
  92. func (t sinceMarker) IsAll() bool {
  93. return t == sinceAllMessages
  94. }
  95. func (t sinceMarker) IsNone() bool {
  96. return t == sinceNoMessages
  97. }
  98. func (t sinceMarker) IsID() bool {
  99. return t.id != ""
  100. }
  101. func (t sinceMarker) Time() time.Time {
  102. return t.time
  103. }
  104. func (t sinceMarker) ID() string {
  105. return t.id
  106. }
  107. var (
  108. sinceAllMessages = sinceMarker{time.Unix(0, 0), ""}
  109. sinceNoMessages = sinceMarker{time.Unix(1, 0), ""}
  110. )
  111. type queryFilter struct {
  112. Message string
  113. Title string
  114. Tags []string
  115. Priority []int
  116. }
  117. func parseQueryFilters(r *http.Request) (*queryFilter, error) {
  118. messageFilter := readParam(r, "x-message", "message", "m")
  119. titleFilter := readParam(r, "x-title", "title", "t")
  120. tagsFilter := util.SplitNoEmpty(readParam(r, "x-tags", "tags", "tag", "ta"), ",")
  121. priorityFilter := make([]int, 0)
  122. for _, p := range util.SplitNoEmpty(readParam(r, "x-priority", "priority", "prio", "p"), ",") {
  123. priority, err := util.ParsePriority(p)
  124. if err != nil {
  125. return nil, err
  126. }
  127. priorityFilter = append(priorityFilter, priority)
  128. }
  129. return &queryFilter{
  130. Message: messageFilter,
  131. Title: titleFilter,
  132. Tags: tagsFilter,
  133. Priority: priorityFilter,
  134. }, nil
  135. }
  136. func (q *queryFilter) Pass(msg *message) bool {
  137. if msg.Event != messageEvent {
  138. return true // filters only apply to messages
  139. }
  140. if q.Message != "" && msg.Message != q.Message {
  141. return false
  142. }
  143. if q.Title != "" && msg.Title != q.Title {
  144. return false
  145. }
  146. messagePriority := msg.Priority
  147. if messagePriority == 0 {
  148. messagePriority = 3 // For query filters, default priority (3) is the same as "not set" (0)
  149. }
  150. if len(q.Priority) > 0 && !util.InIntList(q.Priority, messagePriority) {
  151. return false
  152. }
  153. if len(q.Tags) > 0 && !util.InStringListAll(msg.Tags, q.Tags) {
  154. return false
  155. }
  156. return true
  157. }