From 6bd4e4bd7cdd5d3fd39b75d25fb0c75145335c66 Mon Sep 17 00:00:00 2001 From: Philipp Heckel Date: Wed, 27 Apr 2022 10:25:01 -0400 Subject: [PATCH] User actions docs, tests and release notes --- docs/publish.md | 61 +++++++++++++++++++++++------------------- docs/releases.md | 16 +++++++++++ server/actions.go | 6 ++--- server/actions_test.go | 43 ++++++++++++++++++++++++----- 4 files changed, 88 insertions(+), 38 deletions(-) diff --git a/docs/publish.md b/docs/publish.md index 5229a537..48d8ca56 100644 --- a/docs/publish.md +++ b/docs/publish.md @@ -850,27 +850,32 @@ To define actions using the `X-Actions` header (or any of its aliases: `Actions` , , paramN=... [; , , ...] ``` -The `action=` and `label=` prefix are optional in all actions, and the `url=` prefix is optional in the `view` and `http` action. -The format has **some limitations**: You cannot use `,` or `;` in any of the values, and depending on your language/library, UTF-8 -characters may not work. Use the [JSON array format](#using-a-json-array) instead to overcome these limitations. +Multiple actions are separated by a semicolon (`;`), and key/value pairs are separated by commas (`,`). Values may be +quoted with double quotes (`"`) or single quotes (`'`) if the value itself contains commas or semicolons. + +The `action=` and `label=` prefix are optional in all actions, and the `url=` prefix is optional in the `view` and +`http` action. The only limitation of this format is that depending on your language/library, UTF-8 characters may not +work. If they don't, use the [JSON array format](#using-a-json-array) instead. As an example, here's how you can create the above notification using this format. Refer to the [`view` action](#open-websiteapp) and [`http` action](#send-http-request) section for details on the specific actions: === "Command line (curl)" ``` + body='{"temperature": 65}' curl \ -d "You left the house. Turn down the A/C?" \ -H "Actions: view, Open portal, https://home.nest.com/, clear=true; \ - http, Turn down, https://api.nest.com/device/XZ1D2, body=target_temp_f=65" \ - ntfy.sh/myhome + http, Turn down, https://api.nest.com/, body='$body'" \ + ntfy.sh/myhome ``` === "ntfy CLI" ``` + body='{"temperature": 65}' ntfy publish \ --actions="view, Open portal, https://home.nest.com/, clear=true; \ - http, Turn down, https://api.nest.com/device/XZ1D2, body=target_temp_f=65" \ + http, Turn down, https://api.nest.com/, body='$body'" \ myhome \ "You left the house. Turn down the A/C?" ``` @@ -879,7 +884,7 @@ As an example, here's how you can create the above notification using this forma ``` http POST /myhome HTTP/1.1 Host: ntfy.sh - Actions: view, Open portal, https://home.nest.com/, clear=true; http, Turn down, https://api.nest.com/device/XZ1D2, body=target_temp_f=65 + Actions: view, Open portal, https://home.nest.com/, clear=true; http, Turn down, https://api.nest.com/, body='{"temperature": 65}' You left the house. Turn down the A/C? ``` @@ -890,7 +895,7 @@ As an example, here's how you can create the above notification using this forma method: 'POST', body: 'You left the house. Turn down the A/C?', headers: { - 'Actions': 'view, Open portal, https://home.nest.com/, clear=true; http, Turn down, https://api.nest.com/device/XZ1D2, body=target_temp_f=65' + 'Actions': 'view, Open portal, https://home.nest.com/, clear=true; http, Turn down, https://api.nest.com/, body=\'{"temperature": 65}\'' } }) ``` @@ -898,14 +903,14 @@ As an example, here's how you can create the above notification using this forma === "Go" ``` go req, _ := http.NewRequest("POST", "https://ntfy.sh/myhome", strings.NewReader("You left the house. Turn down the A/C?")) - req.Header.Set("Actions", "view, Open portal, https://home.nest.com/, clear=true; http, Turn down, https://api.nest.com/device/XZ1D2, body=target_temp_f=65") + req.Header.Set("Actions", "view, Open portal, https://home.nest.com/, clear=true; http, Turn down, https://api.nest.com/, body='{\"temperature\": 65}'") http.DefaultClient.Do(req) ``` === "PowerShell" ``` powershell $uri = "https://ntfy.sh/myhome" - $headers = @{ Actions="view, Open portal, https://home.nest.com/, clear=true; http, Turn down, https://api.nest.com/device/XZ1D2, body=target_temp_f=65" } + $headers = @{ Actions="view, Open portal, https://home.nest.com/, clear=true; http, Turn down, https://api.nest.com/, body='{\"temperature\": 65}'" } $body = "You left the house. Turn down the A/C?" Invoke-RestMethod -Method 'Post' -Uri $uri -Headers $headers -Body $body -UseBasicParsing ``` @@ -914,7 +919,7 @@ As an example, here's how you can create the above notification using this forma ``` python requests.post("https://ntfy.sh/myhome", data="You left the house. Turn down the A/C?", - headers={ "Actions": "view, Open portal, https://home.nest.com/, clear=true; http, Turn down, https://api.nest.com/device/XZ1D2, body=target_temp_f=65" }) + headers={ "Actions": "view, Open portal, https://home.nest.com/, clear=true; http, Turn down, https://api.nest.com/, body='{\"temperature\": 65}'" }) ``` === "PHP" @@ -924,7 +929,7 @@ As an example, here's how you can create the above notification using this forma 'method' => 'POST', 'header' => "Content-Type: text/plain\r\n" . - "Actions: view, Open portal, https://home.nest.com/, clear=true; http, Turn down, https://api.nest.com/device/XZ1D2, body=target_temp_f=65", + "Actions: view, Open portal, https://home.nest.com/, clear=true; http, Turn down, https://api.nest.com/, body='{\"temperature\": 65}'", 'content' => 'You left the house. Turn down the A/C?' ] ])); @@ -950,8 +955,8 @@ Alternatively, the same actions can be defined as **JSON array**, if the notific { "action": "http", "label": "Turn down", - "url": "https://api.nest.com/device/XZ1D2", - "body": "target_temp_f=65" + "url": "https://api.nest.com/", + "body": "{\"temperature\": 65}" } ] }' @@ -970,8 +975,8 @@ Alternatively, the same actions can be defined as **JSON array**, if the notific { "action": "http", "label": "Turn down", - "url": "https://api.nest.com/device/XZ1D2", - "body": "target_temp_f=65" + "url": "https://api.nest.com/", + "body": "{\"temperature\": 65}" } ]' \ myhome \ @@ -996,8 +1001,8 @@ Alternatively, the same actions can be defined as **JSON array**, if the notific { "action": "http", "label": "Turn down", - "url": "https://api.nest.com/device/XZ1D2", - "body": "target_temp_f=65" + "url": "https://api.nest.com/", + "body": "{\"temperature\": 65}" } ] } @@ -1020,8 +1025,8 @@ Alternatively, the same actions can be defined as **JSON array**, if the notific { action: "http", label: "Turn down", - url: "https://api.nest.com/device/XZ1D2", - body: "target_temp_f=65" + url: "https://api.nest.com/", + body: "{\"temperature\": 65}" } ] }) @@ -1046,8 +1051,8 @@ Alternatively, the same actions can be defined as **JSON array**, if the notific { "action": "http", "label": "Turn down", - "url": "https://api.nest.com/device/XZ1D2", - "body": "target_temp_f=65" + "url": "https://api.nest.com/", + "body": "{\"temperature\": 65}" } ] }` @@ -1071,8 +1076,8 @@ Alternatively, the same actions can be defined as **JSON array**, if the notific @{ "action"="http", "label"="Turn down" - "url"="https://api.nest.com/device/XZ1D2" - "body"="target_temp_f=65" + "url"="https://api.nest.com/" + "body"="{\"temperature\": 65}" } ) } | ConvertTo-Json @@ -1095,8 +1100,8 @@ Alternatively, the same actions can be defined as **JSON array**, if the notific { "action": "http", "label": "Turn down", - "url": "https://api.nest.com/device/XZ1D2", - "body": "target_temp_f=65" + "url": "https://api.nest.com/", + "body": "{\"temperature\": 65}" } ] }) @@ -1122,11 +1127,11 @@ Alternatively, the same actions can be defined as **JSON array**, if the notific [ "action": "http", "label": "Turn down", - "url": "https://api.nest.com/device/XZ1D2", + "url": "https://api.nest.com/", "headers": [ "Authorization": "Bearer ..." ], - "body": "target_temp_f=65" + "body": "{\"temperature\": 65}" ] ] ]) diff --git a/docs/releases.md b/docs/releases.md index 6888e625..c44577bd 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -2,6 +2,22 @@ Binaries for all releases can be found on the GitHub releases pages for the [ntfy server](https://github.com/binwiederhier/ntfy/releases) and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/releases). + + ## ntfy Android app v1.12.0 Released Apr 25, 2022 diff --git a/server/actions.go b/server/actions.go index 296a649a..59852838 100644 --- a/server/actions.go +++ b/server/actions.go @@ -134,7 +134,6 @@ func (p *actionParser) parseAction() (*action, error) { section := 0 for { key, value, last, err := p.parseSection() - fmt.Printf("--> key=%s, value=%s, last=%t, err=%#v\n", key, value, last, err) if err != nil { return nil, err } @@ -226,14 +225,15 @@ func (p *actionParser) parseKey() string { } // parseValue reads the input until EOF, "," or ";" and returns the value string. Unlike parseQuotedValue, -// this function does not support "," or ";" in the value itself. +// this function does not support "," or ";" in the value itself, and spaces in the beginning and end of the +// string are trimmed. func (p *actionParser) parseValue() (value string, last bool) { start := p.pos for { r, w := p.peek() if isSectionEnd(r) { last = isLastSection(r) - value = p.input[start:p.pos] + value = strings.TrimSpace(p.input[start:p.pos]) p.pos += w return } diff --git a/server/actions_test.go b/server/actions_test.go index da15c5f4..4f16bea0 100644 --- a/server/actions_test.go +++ b/server/actions_test.go @@ -78,6 +78,15 @@ func TestParseActions(t *testing.T) { require.Equal(t, `"quotes" and \'single quotes\'`, actions[0].Label) require.Equal(t, `http://example.com`, actions[0].URL) + // Single quotes (JSON) + actions, err = parseActions(`action=http, Post it, url=http://example.com, body='{"temperature": 65}'`) + require.Nil(t, err) + require.Equal(t, 1, len(actions)) + require.Equal(t, "http", actions[0].Action) + require.Equal(t, "Post it", actions[0].Label) + require.Equal(t, `http://example.com`, actions[0].URL) + require.Equal(t, `{"temperature": 65}`, actions[0].Body) + // Out of order actions, err = parseActions(`label="Out of order!" , action="http", url=http://example.com`) require.Nil(t, err) @@ -102,25 +111,45 @@ func TestParseActions(t *testing.T) { require.Equal(t, `Кохайтеся а не воюйте, 💙🫤`, actions[0].Label) require.Equal(t, `http://google.com`, actions[0].URL) + // Multiple actions, awkward spacing + actions, err = parseActions(`http , 'Make love, not war 💙🫤' , https://ntfy.sh ; view, " yo ", https://x.org`) + require.Nil(t, err) + require.Equal(t, 2, len(actions)) + require.Equal(t, "http", actions[0].Action) + require.Equal(t, `Make love, not war 💙🫤`, actions[0].Label) + require.Equal(t, `https://ntfy.sh`, actions[0].URL) + require.Equal(t, "view", actions[1].Action) + require.Equal(t, " yo ", actions[1].Label) + require.Equal(t, `https://x.org`, actions[1].URL) + // Invalid syntax - actions, err = parseActions(`label="Out of order!" x, action="http", url=http://example.com`) + _, err = parseActions(`label="Out of order!" x, action="http", url=http://example.com`) require.EqualError(t, err, "unexpected character 'x' at position 22") - actions, err = parseActions(`label="", action="http", url=http://example.com`) + _, err = parseActions(`label="", action="http", url=http://example.com`) require.EqualError(t, err, "parameter 'label' is required") - actions, err = parseActions(`label=, action="http", url=http://example.com`) + _, err = parseActions(`label=, action="http", url=http://example.com`) require.EqualError(t, err, "parameter 'label' is required") - actions, err = parseActions(`label="xx", action="http", url=http://example.com, what is this anyway`) + _, err = parseActions(`label="xx", action="http", url=http://example.com, what is this anyway`) require.EqualError(t, err, "term 'what is this anyway' unknown") - actions, err = parseActions(`fdsfdsf`) + _, err = parseActions(`fdsfdsf`) require.EqualError(t, err, "action 'fdsfdsf' unknown") - actions, err = parseActions(`aaa=a, "bbb, 'ccc, ddd, eee "`) + _, err = parseActions(`aaa=a, "bbb, 'ccc, ddd, eee "`) require.EqualError(t, err, "key 'aaa' unknown") - actions, err = parseActions(`action=http, label="omg the end quote is missing`) + _, err = parseActions(`action=http, label="omg the end quote is missing`) require.EqualError(t, err, "unexpected end of input, quote started at position 20") + + _, err = parseActions(`;;;;`) + require.EqualError(t, err, "only 3 actions allowed") + + _, err = parseActions(`,,,,,,;;`) + require.EqualError(t, err, "term '' unknown") + + _, err = parseActions(`''";,;"`) + require.EqualError(t, err, "unexpected character '\"' at position 2") }