User actions docs, tests and release notes
This commit is contained in:
		
							parent
							
								
									1f6118f068
								
							
						
					
					
						commit
						6bd4e4bd7c
					
				
					 4 changed files with 88 additions and 38 deletions
				
			
		|  | @ -850,27 +850,32 @@ To define actions using the `X-Actions` header (or any of its aliases: `Actions` | ||||||
|     <action1>, <label1>, paramN=... [; <action2>, <label2>, ...] |     <action1>, <label1>, paramN=... [; <action2>, <label2>, ...] | ||||||
|     ``` |     ``` | ||||||
| 
 | 
 | ||||||
| The `action=` and `label=` prefix are optional in all actions, and the `url=` prefix is optional in the `view` and `http` action. | Multiple actions are separated by a semicolon (`;`), and key/value pairs are separated by commas (`,`). Values may be  | ||||||
| The format has **some limitations**: You cannot use `,` or `;` in any of the values, and depending on your language/library, UTF-8 | quoted with double quotes (`"`) or single quotes (`'`) if the value itself contains commas or semicolons.  | ||||||
| characters may not work. Use the [JSON array format](#using-a-json-array) instead to overcome these limitations. | 
 | ||||||
|  | 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  | 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: | [`http` action](#send-http-request) section for details on the specific actions: | ||||||
| 
 | 
 | ||||||
| === "Command line (curl)" | === "Command line (curl)" | ||||||
|     ``` |     ``` | ||||||
|  |     body='{"temperature": 65}' | ||||||
|     curl \ |     curl \ | ||||||
|         -d "You left the house. Turn down the A/C?" \ |         -d "You left the house. Turn down the A/C?" \ | ||||||
|         -H "Actions: view, Open portal, https://home.nest.com/, clear=true; \ |         -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" \ |                      http, Turn down, https://api.nest.com/, body='$body'" \ | ||||||
|     ntfy.sh/myhome |         ntfy.sh/myhome | ||||||
|     ``` |     ``` | ||||||
| 
 | 
 | ||||||
| === "ntfy CLI" | === "ntfy CLI" | ||||||
|     ``` |     ``` | ||||||
|  |     body='{"temperature": 65}' | ||||||
|     ntfy publish \ |     ntfy publish \ | ||||||
|         --actions="view, Open portal, https://home.nest.com/, clear=true; \ |         --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 \ |         myhome \ | ||||||
|         "You left the house. Turn down the A/C?" |         "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 |     ``` http | ||||||
|     POST /myhome HTTP/1.1 |     POST /myhome HTTP/1.1 | ||||||
|     Host: ntfy.sh |     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? |     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', |         method: 'POST', | ||||||
|         body: 'You left the house. Turn down the A/C?', |         body: 'You left the house. Turn down the A/C?', | ||||||
|         headers: {  |         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" | ||||||
|     ``` go |     ``` go | ||||||
|     req, _ := http.NewRequest("POST", "https://ntfy.sh/myhome", strings.NewReader("You left the house. Turn down the A/C?")) |     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) |     http.DefaultClient.Do(req) | ||||||
|     ``` |     ``` | ||||||
| 
 | 
 | ||||||
| === "PowerShell" | === "PowerShell" | ||||||
|     ``` powershell |     ``` powershell | ||||||
|     $uri = "https://ntfy.sh/myhome" |     $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?" |     $body = "You left the house. Turn down the A/C?" | ||||||
|     Invoke-RestMethod -Method 'Post' -Uri $uri -Headers $headers -Body $body -UseBasicParsing |     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 |     ``` python | ||||||
|     requests.post("https://ntfy.sh/myhome", |     requests.post("https://ntfy.sh/myhome", | ||||||
|         data="You left the house. Turn down the A/C?", |         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" | === "PHP" | ||||||
|  | @ -924,7 +929,7 @@ As an example, here's how you can create the above notification using this forma | ||||||
|             'method' => 'POST', |             'method' => 'POST', | ||||||
|             'header' => |             'header' => | ||||||
|                 "Content-Type: text/plain\r\n" . |                 "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?' |             '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", |             "action": "http", | ||||||
|             "label": "Turn down", |             "label": "Turn down", | ||||||
|             "url": "https://api.nest.com/device/XZ1D2", |             "url": "https://api.nest.com/", | ||||||
|             "body": "target_temp_f=65" |             "body": "{\"temperature\": 65}" | ||||||
|           } |           } | ||||||
|         ] |         ] | ||||||
|       }' |       }' | ||||||
|  | @ -970,8 +975,8 @@ Alternatively, the same actions can be defined as **JSON array**, if the notific | ||||||
|             { |             { | ||||||
|                 "action": "http", |                 "action": "http", | ||||||
|                 "label": "Turn down", |                 "label": "Turn down", | ||||||
|                 "url": "https://api.nest.com/device/XZ1D2", |                 "url": "https://api.nest.com/", | ||||||
|                 "body": "target_temp_f=65" |                 "body": "{\"temperature\": 65}" | ||||||
|             } |             } | ||||||
|         ]' \ |         ]' \ | ||||||
|         myhome \ |         myhome \ | ||||||
|  | @ -996,8 +1001,8 @@ Alternatively, the same actions can be defined as **JSON array**, if the notific | ||||||
|           { |           { | ||||||
|             "action": "http", |             "action": "http", | ||||||
|             "label": "Turn down", |             "label": "Turn down", | ||||||
|             "url": "https://api.nest.com/device/XZ1D2", |             "url": "https://api.nest.com/", | ||||||
|             "body": "target_temp_f=65" |             "body": "{\"temperature\": 65}" | ||||||
|           } |           } | ||||||
|         ] |         ] | ||||||
|     } |     } | ||||||
|  | @ -1020,8 +1025,8 @@ Alternatively, the same actions can be defined as **JSON array**, if the notific | ||||||
|                 { |                 { | ||||||
|                     action: "http", |                     action: "http", | ||||||
|                     label: "Turn down", |                     label: "Turn down", | ||||||
|                     url: "https://api.nest.com/device/XZ1D2", |                     url: "https://api.nest.com/", | ||||||
|                     body: "target_temp_f=65" |                     body: "{\"temperature\": 65}" | ||||||
|                 } |                 } | ||||||
|             ] |             ] | ||||||
|         }) |         }) | ||||||
|  | @ -1046,8 +1051,8 @@ Alternatively, the same actions can be defined as **JSON array**, if the notific | ||||||
|           { |           { | ||||||
|             "action": "http", |             "action": "http", | ||||||
|             "label": "Turn down", |             "label": "Turn down", | ||||||
|             "url": "https://api.nest.com/device/XZ1D2", |             "url": "https://api.nest.com/", | ||||||
|             "body": "target_temp_f=65" |             "body": "{\"temperature\": 65}" | ||||||
|           } |           } | ||||||
|         ] |         ] | ||||||
|     }` |     }` | ||||||
|  | @ -1071,8 +1076,8 @@ Alternatively, the same actions can be defined as **JSON array**, if the notific | ||||||
|             @{ |             @{ | ||||||
|                 "action"="http", |                 "action"="http", | ||||||
|                 "label"="Turn down" |                 "label"="Turn down" | ||||||
|                 "url"="https://api.nest.com/device/XZ1D2" |                 "url"="https://api.nest.com/" | ||||||
|                 "body"="target_temp_f=65" |                 "body"="{\"temperature\": 65}" | ||||||
|             } |             } | ||||||
|         ) |         ) | ||||||
|     } | ConvertTo-Json |     } | ConvertTo-Json | ||||||
|  | @ -1095,8 +1100,8 @@ Alternatively, the same actions can be defined as **JSON array**, if the notific | ||||||
|                 { |                 { | ||||||
|                     "action": "http", |                     "action": "http", | ||||||
|                     "label": "Turn down", |                     "label": "Turn down", | ||||||
|                     "url": "https://api.nest.com/device/XZ1D2", |                     "url": "https://api.nest.com/", | ||||||
|                     "body": "target_temp_f=65" |                     "body": "{\"temperature\": 65}" | ||||||
|                 } |                 } | ||||||
|             ] |             ] | ||||||
|         }) |         }) | ||||||
|  | @ -1122,11 +1127,11 @@ Alternatively, the same actions can be defined as **JSON array**, if the notific | ||||||
|                     [ |                     [ | ||||||
|                         "action": "http", |                         "action": "http", | ||||||
|                         "label": "Turn down", |                         "label": "Turn down", | ||||||
|                         "url": "https://api.nest.com/device/XZ1D2", |                         "url": "https://api.nest.com/", | ||||||
|                         "headers": [ |                         "headers": [ | ||||||
|                             "Authorization": "Bearer ..." |                             "Authorization": "Bearer ..." | ||||||
|                         ], |                         ], | ||||||
|                         "body": "target_temp_f=65" |                         "body": "{\"temperature\": 65}" | ||||||
|                     ] |                     ] | ||||||
|                 ] |                 ] | ||||||
|             ]) |             ]) | ||||||
|  |  | ||||||
|  | @ -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) | 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). | and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/releases). | ||||||
| 
 | 
 | ||||||
|  | <!-- | ||||||
|  | 
 | ||||||
|  | ## ntfy Android app v1.13.0 (UNRELEASED) | ||||||
|  | 
 | ||||||
|  | Bugs: | ||||||
|  | * Accurate naming of "mute notifications" from "pause notifications" ([#224](https://github.com/binwiederhier/ntfy/issues/224),  | ||||||
|  |   thanks to [@shadow00](https://github.com/shadow00) for reporting) | ||||||
|  | 
 | ||||||
|  | ## ntfy server v1.22.0 (UNRELEASED) | ||||||
|  | 
 | ||||||
|  | **Features:** | ||||||
|  | 
 | ||||||
|  | * Better parsing of the user actions, allowing quotes (no ticket) | ||||||
|  | 
 | ||||||
|  | --> | ||||||
|  | 
 | ||||||
| ## ntfy Android app v1.12.0 | ## ntfy Android app v1.12.0 | ||||||
| Released Apr 25, 2022 | Released Apr 25, 2022 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -134,7 +134,6 @@ func (p *actionParser) parseAction() (*action, error) { | ||||||
| 	section := 0 | 	section := 0 | ||||||
| 	for { | 	for { | ||||||
| 		key, value, last, err := p.parseSection() | 		key, value, last, err := p.parseSection() | ||||||
| 		fmt.Printf("--> key=%s, value=%s, last=%t, err=%#v\n", key, value, last, err) |  | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			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, | // 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) { | func (p *actionParser) parseValue() (value string, last bool) { | ||||||
| 	start := p.pos | 	start := p.pos | ||||||
| 	for { | 	for { | ||||||
| 		r, w := p.peek() | 		r, w := p.peek() | ||||||
| 		if isSectionEnd(r) { | 		if isSectionEnd(r) { | ||||||
| 			last = isLastSection(r) | 			last = isLastSection(r) | ||||||
| 			value = p.input[start:p.pos] | 			value = strings.TrimSpace(p.input[start:p.pos]) | ||||||
| 			p.pos += w | 			p.pos += w | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | @ -78,6 +78,15 @@ func TestParseActions(t *testing.T) { | ||||||
| 	require.Equal(t, `"quotes" and \'single quotes\'`, actions[0].Label) | 	require.Equal(t, `"quotes" and \'single quotes\'`, actions[0].Label) | ||||||
| 	require.Equal(t, `http://example.com`, actions[0].URL) | 	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 | 	// Out of order | ||||||
| 	actions, err = parseActions(`label="Out of order!" , action="http", url=http://example.com`) | 	actions, err = parseActions(`label="Out of order!" , action="http", url=http://example.com`) | ||||||
| 	require.Nil(t, err) | 	require.Nil(t, err) | ||||||
|  | @ -102,25 +111,45 @@ func TestParseActions(t *testing.T) { | ||||||
| 	require.Equal(t, `Кохайтеся а не воюйте, 💙🫤`, actions[0].Label) | 	require.Equal(t, `Кохайтеся а не воюйте, 💙🫤`, actions[0].Label) | ||||||
| 	require.Equal(t, `http://google.com`, actions[0].URL) | 	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 | 	// 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") | 	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") | 	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") | 	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") | 	require.EqualError(t, err, "term 'what is this anyway' unknown") | ||||||
| 
 | 
 | ||||||
| 	actions, err = parseActions(`fdsfdsf`) | 	_, err = parseActions(`fdsfdsf`) | ||||||
| 	require.EqualError(t, err, "action 'fdsfdsf' unknown") | 	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") | 	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") | 	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") | ||||||
| } | } | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue