Merge branch 'main' into e2e

This commit is contained in:
Philipp Heckel 2022-10-01 20:56:50 -04:00
commit a66731641c
60 changed files with 4572 additions and 3400 deletions

View file

@ -60,13 +60,13 @@ func parseActions(s string) (actions []*action, err error) {
return nil, fmt.Errorf("only %d actions allowed", actionsMax)
}
for _, action := range actions {
if !util.InStringList(actionsAll, action.Action) {
if !util.Contains(actionsAll, action.Action) {
return nil, fmt.Errorf("parameter 'action' cannot be '%s', valid values are 'view', 'broadcast' and 'http'", action.Action)
} else if action.Label == "" {
return nil, fmt.Errorf("parameter 'label' is required")
} else if util.InStringList(actionsWithURL, action.Action) && action.URL == "" {
} else if util.Contains(actionsWithURL, action.Action) && action.URL == "" {
return nil, fmt.Errorf("parameter 'url' is required for action '%s'", action.Action)
} else if action.Action == actionHTTP && util.InStringList([]string{"GET", "HEAD"}, action.Method) && action.Body != "" {
} else if action.Action == actionHTTP && util.Contains([]string{"GET", "HEAD"}, action.Method) && action.Body != "" {
return nil, fmt.Errorf("parameter 'body' cannot be set if method is %s", action.Method)
}
}
@ -87,7 +87,8 @@ func parseActionsFromJSON(s string) ([]*action, error) {
// https://ntfy.sh/docs/publish/#action-buttons), into an array of actions.
//
// It can parse an actions string like this:
// view, "Look ma, commas and \"quotes\" too", url=https://..; action=broadcast, ...
//
// view, "Look ma, commas and \"quotes\" too", url=https://..; action=broadcast, ...
//
// It works by advancing the position ("pos") through the input string ("input").
//
@ -96,10 +97,11 @@ func parseActionsFromJSON(s string) ([]*action, error) {
// though it does not use state functions at all.
//
// Other resources:
// https://adampresley.github.io/2015/04/12/writing-a-lexer-and-parser-in-go-part-1.html
// https://github.com/adampresley/sample-ini-parser/blob/master/services/lexer/lexer/Lexer.go
// https://github.com/benbjohnson/sql-parser/blob/master/scanner.go
// https://blog.gopheracademy.com/advent-2014/parsers-lexers/
//
// https://adampresley.github.io/2015/04/12/writing-a-lexer-and-parser-in-go-part-1.html
// https://github.com/adampresley/sample-ini-parser/blob/master/services/lexer/lexer/Lexer.go
// https://github.com/benbjohnson/sql-parser/blob/master/scanner.go
// https://blog.gopheracademy.com/advent-2014/parsers-lexers/
func parseActionsFromSimple(s string) ([]*action, error) {
if !utf8.ValidString(s) {
return nil, errors.New("invalid utf-8 string")
@ -154,7 +156,7 @@ func populateAction(newAction *action, section int, key, value string) error {
key = "action"
} else if key == "" && section == 1 {
key = "label"
} else if key == "" && section == 2 && util.InStringList(actionsWithURL, newAction.Action) {
} else if key == "" && section == 2 && util.Contains(actionsWithURL, newAction.Action) {
key = "url"
}
@ -176,7 +178,7 @@ func populateAction(newAction *action, section int, key, value string) error {
newAction.Label = value
case "clear":
lvalue := strings.ToLower(value)
if !util.InStringList([]string{"true", "yes", "1", "false", "no", "0"}, lvalue) {
if !util.Contains([]string{"true", "yes", "1", "false", "no", "0"}, lvalue) {
return fmt.Errorf("parameter 'clear' cannot be '%s', only boolean values are allowed (true/yes/1/false/no/0)", value)
}
newAction.Clear = lvalue == "true" || lvalue == "yes" || lvalue == "1"

View file

@ -23,7 +23,7 @@ func (e errHTTP) JSON() string {
return string(b)
}
func wrapErrHTTP(err *errHTTP, message string, args ...interface{}) *errHTTP {
func wrapErrHTTP(err *errHTTP, message string, args ...any) *errHTTP {
return &errHTTP{
Code: err.Code,
HTTPCode: err.HTTPCode,
@ -53,6 +53,7 @@ var (
errHTTPBadRequestMatrixMessageInvalid = &errHTTP{40019, http.StatusBadRequest, "invalid request: Matrix JSON invalid", "https://ntfy.sh/docs/publish/#matrix-gateway"}
errHTTPBadRequestMatrixPushkeyBaseURLMismatch = &errHTTP{40020, http.StatusBadRequest, "invalid request: push key must be prefixed with base URL", "https://ntfy.sh/docs/publish/#matrix-gateway"}
errHTTPBadRequestUnexpectedMultipartField = &errHTTP{40021, http.StatusBadRequest, "invalid request: unexpected multipart field", "https://ntfy.sh/docs/publish/#end-to-end-encryption"}
errHTTPBadRequestIconURLInvalid = &errHTTP{40021, http.StatusBadRequest, "invalid request: icon URL is invalid", "https://ntfy.sh/docs/publish/#icons"}
errHTTPNotFound = &errHTTP{40401, http.StatusNotFound, "page not found", ""}
errHTTPUnauthorized = &errHTTP{40101, http.StatusUnauthorized, "unauthorized", "https://ntfy.sh/docs/publish/#authentication"}
errHTTPForbidden = &errHTTP{40301, http.StatusForbidden, "forbidden", "https://ntfy.sh/docs/publish/#authentication"}

View file

@ -30,6 +30,7 @@ const (
priority INT NOT NULL,
tags TEXT NOT NULL,
click TEXT NOT NULL,
icon TEXT NOT NULL,
actions TEXT NOT NULL,
attachment_name TEXT NOT NULL,
attachment_type TEXT NOT NULL,
@ -45,37 +46,37 @@ const (
COMMIT;
`
insertMessageQuery = `
INSERT INTO messages (mid, time, topic, message, title, priority, tags, click, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, encoding, published)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
INSERT INTO messages (mid, time, topic, message, title, priority, tags, click, icon, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, encoding, published)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`
pruneMessagesQuery = `DELETE FROM messages WHERE time < ? AND published = 1`
selectRowIDFromMessageID = `SELECT id FROM messages WHERE mid = ?` // Do not include topic, see #336 and TestServer_PollSinceID_MultipleTopics
selectMessagesSinceTimeQuery = `
SELECT mid, time, topic, message, title, priority, tags, click, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, encoding
SELECT mid, time, topic, message, title, priority, tags, click, icon, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, encoding
FROM messages
WHERE topic = ? AND time >= ? AND published = 1
ORDER BY time, id
`
selectMessagesSinceTimeIncludeScheduledQuery = `
SELECT mid, time, topic, message, title, priority, tags, click, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, encoding
SELECT mid, time, topic, message, title, priority, tags, click, icon, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, encoding
FROM messages
WHERE topic = ? AND time >= ?
ORDER BY time, id
`
selectMessagesSinceIDQuery = `
SELECT mid, time, topic, message, title, priority, tags, click, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, encoding
SELECT mid, time, topic, message, title, priority, tags, click, icon, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, encoding
FROM messages
WHERE topic = ? AND id > ? AND published = 1
ORDER BY time, id
`
selectMessagesSinceIDIncludeScheduledQuery = `
SELECT mid, time, topic, message, title, priority, tags, click, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, encoding
SELECT mid, time, topic, message, title, priority, tags, click, icon, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, encoding
FROM messages
WHERE topic = ? AND (id > ? OR published = 0)
ORDER BY time, id
`
selectMessagesDueQuery = `
SELECT mid, time, topic, message, title, priority, tags, click, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, encoding
SELECT mid, time, topic, message, title, priority, tags, click, icon, actions, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, sender, encoding
FROM messages
WHERE time <= ? AND published = 0
ORDER BY time, id
@ -89,7 +90,7 @@ const (
// Schema management queries
const (
currentSchemaVersion = 7
currentSchemaVersion = 8
createSchemaVersionTableQuery = `
CREATE TABLE IF NOT EXISTS schemaVersion (
id INT PRIMARY KEY,
@ -177,6 +178,11 @@ const (
migrate6To7AlterMessagesTableQuery = `
ALTER TABLE messages RENAME COLUMN attachment_owner TO sender;
`
// 7 -> 8
migrate7To8AlterMessagesTableQuery = `
ALTER TABLE messages ADD COLUMN icon TEXT NOT NULL DEFAULT('');
`
)
type messageCache struct {
@ -266,6 +272,7 @@ func (c *messageCache) addMessages(ms []*message) error {
m.Priority,
tags,
m.Click,
m.Icon,
actionsStr,
attachmentName,
attachmentType,
@ -414,7 +421,7 @@ func readMessages(rows *sql.Rows) ([]*message, error) {
for rows.Next() {
var timestamp, attachmentSize, attachmentExpires int64
var priority int
var id, topic, msg, title, tagsStr, click, actionsStr, attachmentName, attachmentType, attachmentURL, sender, encoding string
var id, topic, msg, title, tagsStr, click, icon, actionsStr, attachmentName, attachmentType, attachmentURL, sender, encoding string
err := rows.Scan(
&id,
&timestamp,
@ -424,6 +431,7 @@ func readMessages(rows *sql.Rows) ([]*message, error) {
&priority,
&tagsStr,
&click,
&icon,
&actionsStr,
&attachmentName,
&attachmentType,
@ -466,6 +474,7 @@ func readMessages(rows *sql.Rows) ([]*message, error) {
Priority: priority,
Tags: tags,
Click: click,
Icon: icon,
Actions: actions,
Attachment: att,
Sender: sender,
@ -524,6 +533,8 @@ func setupCacheDB(db *sql.DB, startupQueries string) error {
return migrateFrom5(db)
} else if schemaVersion == 6 {
return migrateFrom6(db)
} else if schemaVersion == 7 {
return migrateFrom7(db)
}
return fmt.Errorf("unexpected schema version found: %d", schemaVersion)
}
@ -618,5 +629,16 @@ func migrateFrom6(db *sql.DB) error {
if _, err := db.Exec(updateSchemaVersion, 7); err != nil {
return err
}
return migrateFrom7(db)
}
func migrateFrom7(db *sql.DB) error {
log.Info("Migrating cache database schema: from 7 to 8")
if _, err := db.Exec(migrate7To8AlterMessagesTableQuery); err != nil {
return err
}
if _, err := db.Exec(updateSchemaVersion, 8); err != nil {
return err
}
return nil // Update this when a new version is added
}

View file

@ -75,7 +75,7 @@ var (
docsRegex = regexp.MustCompile(`^/docs(|/.*)$`)
fileRegex = regexp.MustCompile(`^/file/([-_A-Za-z0-9]{1,64})(?:\.[A-Za-z0-9]{1,16})?$`)
disallowedTopics = []string{"docs", "static", "file", "app", "settings"} // If updated, also update in Android app
attachURLRegex = regexp.MustCompile(`^https?://`)
urlRegex = regexp.MustCompile(`^https?://`)
//go:embed site
webFs embed.FS
@ -671,7 +671,7 @@ func (s *Server) checkAndConvertPublishMessage(v *visitor, im *inputMessage) (m
m.Attachment.Name = im.Filename
}
if im.Attach != "" {
if !attachURLRegex.MatchString(im.Attach) {
if !urlRegex.MatchString(im.Attach) {
return nil, errHTTPBadRequestAttachmentURLInvalid
}
if im.AttachmentBody != nil {
@ -691,6 +691,12 @@ func (s *Server) checkAndConvertPublishMessage(v *visitor, im *inputMessage) (m
m.Attachment.Name = "attachment"
}
}
if im.Icon != "" {
if !urlRegex.MatchString(im.Icon) {
return nil, errHTTPBadRequestIconURLInvalid
}
m.Icon = im.Icon
}
if im.Email != "" {
if err := v.EmailAllowed(); err != nil {
return nil, errHTTPTooManyRequestsLimitEmails
@ -791,6 +797,7 @@ func (s *Server) parsePublishParams(r *http.Request, m *inputMessage) error {
m.Message = strings.ReplaceAll(readParam(r, "x-message", "message", "m"), "\\n", "\n")
m.Title = readParam(r, "x-title", "title", "t")
m.Click = readParam(r, "x-click", "click")
m.Icon = readParam(r, "x-icon", "icon")
m.Filename = readParam(r, "x-filename", "filename", "file", "f")
m.Attach = readParam(r, "x-attach", "attach", "a")
m.Email = readParam(r, "x-email", "x-e-mail", "email", "e-mail", "mail", "e")
@ -824,18 +831,18 @@ func (s *Server) parsePublishParams(r *http.Request, m *inputMessage) error {
// handlePublishBody consumes the PUT/POST body and decides whether the body is an attachment or the message.
//
// 1. curl -X POST -H "Poll: 1234" ntfy.sh/...
// If a message is flagged as poll request, the body does not matter and is discarded
// 2. curl -T somebinarydata.bin "ntfy.sh/mytopic?up=1"
// If body is binary, encode as base64, if not do not encode
// 3. curl -H "Attach: http://example.com/file.jpg" ntfy.sh/mytopic
// Body must be a message, because we attached an external URL
// 4. curl -T short.txt -H "Filename: short.txt" ntfy.sh/mytopic
// Body must be attachment, because we passed a filename
// 5. curl -T file.txt ntfy.sh/mytopic
// If file.txt is <= 4096 (message limit) and valid UTF-8, treat it as a message
// 6. curl -T file.txt ntfy.sh/mytopic
// If file.txt is > message limit, treat it as an attachment
// 1. curl -X POST -H "Poll: 1234" ntfy.sh/...
// If a message is flagged as poll request, the body does not matter and is discarded
// 2. curl -T somebinarydata.bin "ntfy.sh/mytopic?up=1"
// If body is binary, encode as base64, if not do not encode
// 3. curl -H "Attach: http://example.com/file.jpg" ntfy.sh/mytopic
// Body must be a message, because we attached an external URL
// 4. curl -T short.txt -H "Filename: short.txt" ntfy.sh/mytopic
// Body must be attachment, because we passed a filename
// 5. curl -T file.txt ntfy.sh/mytopic
// If file.txt is <= 4096 (message limit) and valid UTF-8, treat it as a message
// 6. curl -T file.txt ntfy.sh/mytopic
// If file.txt is > message limit, treat it as an attachment
func (s *Server) handlePublishBody(r *http.Request, v *visitor, m *message, body *util.PeekedReadCloser, unifiedpush bool) error {
if m.Event == pollRequestEvent { // Case 1
return s.handleBodyDiscard(body)
@ -1255,7 +1262,7 @@ func (s *Server) topicsFromIDs(ids ...string) ([]*topic, error) {
defer s.mu.Unlock()
topics := make([]*topic, 0)
for _, id := range ids {
if id == "" || util.InStringList(disallowedTopics, id) {
if util.Contains(disallowedTopics, id) {
return nil, errHTTPBadRequestTopicDisallowed
}
if _, ok := s.topics[id]; !ok {
@ -1453,7 +1460,7 @@ func (s *Server) sendDelayedMessage(v *visitor, m *message) error {
func (s *Server) limitRequests(next handleFunc) handleFunc {
return func(w http.ResponseWriter, r *http.Request, v *visitor) error {
if util.InStringList(s.config.VisitorRequestExemptIPAddrs, v.ip) {
if util.Contains(s.config.VisitorRequestExemptIPAddrs, v.ip) {
return next(w, r, v)
} else if err := v.RequestAllowed(); err != nil {
return errHTTPTooManyRequestsLimitRequests

View file

@ -148,6 +148,7 @@ func toFirebaseMessage(m *message, auther auth.Auther) (*messaging.Message, erro
"priority": fmt.Sprintf("%d", m.Priority),
"tags": strings.Join(m.Tags, ","),
"click": m.Click,
"icon": m.Icon,
"title": m.Title,
"message": m.Message,
"encoding": m.Encoding,
@ -216,7 +217,7 @@ func maybeTruncateFCMMessage(m *messaging.Message) *messaging.Message {
// We must set the Alert struct ("alert"), and we need to set MutableContent ("mutable-content"), so the Notification Service
// Extension in iOS can modify the message.
func createAPNSAlertConfig(m *message, data map[string]string) *messaging.APNSConfig {
apnsData := make(map[string]interface{})
apnsData := make(map[string]any)
for k, v := range data {
apnsData[k] = v
}
@ -240,7 +241,7 @@ func createAPNSAlertConfig(m *message, data map[string]string) *messaging.APNSCo
//
// See https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/pushing_background_updates_to_your_app
func createAPNSBackgroundConfig(data map[string]string) *messaging.APNSConfig {
apnsData := make(map[string]interface{})
apnsData := make(map[string]any)
for k, v := range data {
apnsData[k] = v
}

View file

@ -71,7 +71,7 @@ func TestToFirebaseMessage_Keepalive(t *testing.T) {
Aps: &messaging.Aps{
ContentAvailable: true,
},
CustomData: map[string]interface{}{
CustomData: map[string]any{
"id": m.ID,
"time": fmt.Sprintf("%d", m.Time),
"event": m.Event,
@ -102,7 +102,7 @@ func TestToFirebaseMessage_Open(t *testing.T) {
Aps: &messaging.Aps{
ContentAvailable: true,
},
CustomData: map[string]interface{}{
CustomData: map[string]any{
"id": m.ID,
"time": fmt.Sprintf("%d", m.Time),
"event": m.Event,
@ -123,6 +123,7 @@ func TestToFirebaseMessage_Message_Normal_Allowed(t *testing.T) {
m.Priority = 4
m.Tags = []string{"tag 1", "tag2"}
m.Click = "https://google.com"
m.Icon = "https://ntfy.sh/static/img/ntfy.png"
m.Title = "some title"
m.Actions = []*action{
{
@ -165,7 +166,7 @@ func TestToFirebaseMessage_Message_Normal_Allowed(t *testing.T) {
Body: "this is a message",
},
},
CustomData: map[string]interface{}{
CustomData: map[string]any{
"id": m.ID,
"time": fmt.Sprintf("%d", m.Time),
"event": "message",
@ -173,6 +174,7 @@ func TestToFirebaseMessage_Message_Normal_Allowed(t *testing.T) {
"priority": "4",
"tags": strings.Join(m.Tags, ","),
"click": "https://google.com",
"icon": "https://ntfy.sh/static/img/ntfy.png",
"title": "some title",
"message": "this is a message",
"actions": `[{"id":"123","action":"view","label":"Open page","clear":true,"url":"https://ntfy.sh"},{"id":"456","action":"http","label":"Close door","clear":false,"url":"https://door.com/close","method":"PUT","headers":{"really":"yes"}}]`,
@ -193,6 +195,7 @@ func TestToFirebaseMessage_Message_Normal_Allowed(t *testing.T) {
"priority": "4",
"tags": strings.Join(m.Tags, ","),
"click": "https://google.com",
"icon": "https://ntfy.sh/static/img/ntfy.png",
"title": "some title",
"message": "this is a message",
"actions": `[{"id":"123","action":"view","label":"Open page","clear":true,"url":"https://ntfy.sh"},{"id":"456","action":"http","label":"Close door","clear":false,"url":"https://door.com/close","method":"PUT","headers":{"really":"yes"}}]`,
@ -239,7 +242,7 @@ func TestToFirebaseMessage_PollRequest(t *testing.T) {
Body: "New message",
},
},
CustomData: map[string]interface{}{
CustomData: map[string]any{
"id": m.ID,
"time": fmt.Sprintf("%d", m.Time),
"event": "poll_request",

View file

@ -47,17 +47,17 @@ import (
//
// From the message, we only require the "pushkey", as it represents our target topic URL.
// A message may look like this (excerpt):
// {
// "notification": {
// "devices": [
// {
// "pushkey": "https://ntfy.sh/upDAHJKFFDFD?up=1",
// ...
// }
// ]
// }
// }
//
// {
// "notification": {
// "devices": [
// {
// "pushkey": "https://ntfy.sh/upDAHJKFFDFD?up=1",
// ...
// }
// ]
// }
// }
type matrixRequest struct {
Notification *struct {
Devices []*struct {
@ -96,14 +96,13 @@ const (
//
// It basically converts a Matrix push gatewqy request:
//
// POST /_matrix/push/v1/notify HTTP/1.1
// { "notification": { "devices": [ { "pushkey": "https://ntfy.sh/upDAHJKFFDFD?up=1", ... } ] } }
// POST /_matrix/push/v1/notify HTTP/1.1
// { "notification": { "devices": [ { "pushkey": "https://ntfy.sh/upDAHJKFFDFD?up=1", ... } ] } }
//
// to a ntfy request, looking like this:
//
// POST /upDAHJKFFDFD?up=1 HTTP/1.1
// { "notification": { "devices": [ { "pushkey": "https://ntfy.sh/upDAHJKFFDFD?up=1", ... } ] } }
//
// POST /upDAHJKFFDFD?up=1 HTTP/1.1
// { "notification": { "devices": [ { "pushkey": "https://ntfy.sh/upDAHJKFFDFD?up=1", ... } ] } }
func newRequestFromMatrixJSON(r *http.Request, baseURL string, messageLimit int) (*http.Request, error) {
if baseURL == "" {
return nil, errHTTPInternalErrorMissingBaseURL
@ -124,7 +123,7 @@ func newRequestFromMatrixJSON(r *http.Request, baseURL string, messageLimit int)
}
pushKey := m.Notification.Devices[0].PushKey // We ignore other devices for now, see discussion in #316
if !strings.HasPrefix(pushKey, baseURL+"/") {
return nil, &errMatrix{pushKey: pushKey, err: errHTTPBadRequestMatrixPushkeyBaseURLMismatch}
return nil, &errMatrix{pushKey: pushKey, err: wrapErrHTTP(errHTTPBadRequestMatrixPushkeyBaseURLMismatch, "received push key: %s, configured base URL: %s", pushKey, baseURL)}
}
newRequest, err := http.NewRequest(http.MethodPost, pushKey, io.NopCloser(bytes.NewReader(body.PeekedBytes)))
if err != nil {

View file

@ -56,7 +56,7 @@ func TestMatrix_NewRequestFromMatrixJSON_MismatchingPushKey(t *testing.T) {
_, err := newRequestFromMatrixJSON(r, baseURL, maxLength)
matrixErr, ok := err.(*errMatrix)
require.True(t, ok)
require.Equal(t, errHTTPBadRequestMatrixPushkeyBaseURLMismatch, matrixErr.err)
require.Equal(t, "invalid request: push key must be prefixed with base URL, received push key: https://ntfy.example.com/upABCDEFGHI?up=1, configured base URL: https://ntfy.sh", matrixErr.err.Error())
require.Equal(t, "https://ntfy.example.com/upABCDEFGHI?up=1", matrixErr.pushKey)
}

View file

@ -1048,7 +1048,7 @@ func TestServer_PublishAsJSON(t *testing.T) {
s := newTestServer(t, newTestConfig(t))
body := `{"topic":"mytopic","message":"A message","title":"a title\nwith lines","tags":["tag1","tag 2"],` +
`"not-a-thing":"ok", "attach":"http://google.com","filename":"google.pdf", "click":"http://ntfy.sh","priority":4,` +
`"delay":"30min"}`
`"icon":"https://ntfy.sh/static/img/ntfy.png", "delay":"30min"}`
response := request(t, s, "PUT", "/", body, nil)
require.Equal(t, 200, response.Code)
@ -1060,6 +1060,8 @@ func TestServer_PublishAsJSON(t *testing.T) {
require.Equal(t, "http://google.com", m.Attachment.URL)
require.Equal(t, "google.pdf", m.Attachment.Name)
require.Equal(t, "http://ntfy.sh", m.Click)
require.Equal(t, "https://ntfy.sh/static/img/ntfy.png", m.Icon)
require.Equal(t, 4, m.Priority)
require.True(t, m.Time > time.Now().Unix()+29*60)
require.True(t, m.Time < time.Now().Unix()+31*60)

View file

@ -137,7 +137,7 @@ func toEmojis(tags []string) (emojisOut []string, tagsOut []string, err error) {
nextTag:
for _, t := range tags { // TODO Super inefficient; we should just create a .json file with a map
for _, e := range emojis {
if util.InStringList(e.Aliases, t) {
if util.Contains(e.Aliases, t) {
emojisOut = append(emojisOut, e.Emoji)
continue nextTag
}

View file

@ -29,6 +29,7 @@ type message struct {
Priority int `json:"priority,omitempty"`
Tags []string `json:"tags,omitempty"`
Click string `json:"click,omitempty"`
Icon string `json:"icon,omitempty"`
Actions []*action `json:"actions,omitempty"`
Attachment *attachment `json:"attachment,omitempty"`
PollID string `json:"poll_id,omitempty"`
@ -66,17 +67,18 @@ func newAction() *action {
// PublishMessage is used as input when publishing as JSON
type PublishMessage struct {
Topic string `json:"topic"`
Title string `json:"title"`
Message string `json:"message"`
Priority int `json:"priority"`
Tags []string `json:"tags"`
Click string `json:"click"`
Actions []*action `json:"actions"`
Attach string `json:"attach"`
Filename string `json:"filename"`
Email string `json:"email"`
Delay string `json:"delay"`
Topic string `json:"topic"`
Title string `json:"title"`
Message string `json:"message"`
Priority int `json:"priority"`
Tags []string `json:"tags"`
Click string `json:"click"`
Icon string `json:"icon"`
Actions []action `json:"actions"`
Attach string `json:"attach"`
Filename string `json:"filename"`
Email string `json:"email"`
Delay string `json:"delay"`
}
// messageEncoder is a function that knows how to encode a message
@ -207,10 +209,10 @@ func (q *queryFilter) Pass(msg *message) bool {
if messagePriority == 0 {
messagePriority = 3 // For query filters, default priority (3) is the same as "not set" (0)
}
if len(q.Priority) > 0 && !util.InIntList(q.Priority, messagePriority) {
if len(q.Priority) > 0 && !util.Contains(q.Priority, messagePriority) {
return false
}
if len(q.Tags) > 0 && !util.InStringListAll(msg.Tags, q.Tags) {
if len(q.Tags) > 0 && !util.ContainsAll(msg.Tags, q.Tags) {
return false
}
return true