WIP CLI
This commit is contained in:
		
							parent
							
								
									1e8421e8ce
								
							
						
					
					
						commit
						a1f513f6a5
					
				
					 9 changed files with 138 additions and 65 deletions
				
			
		|  | @ -34,12 +34,12 @@ type Message struct { | |||
| 	Event    string | ||||
| 	Time     int64 | ||||
| 	Topic    string | ||||
| 	BaseURL  string | ||||
| 	TopicURL string | ||||
| 	Message  string | ||||
| 	Title    string | ||||
| 	Priority int | ||||
| 	Tags     []string | ||||
| 	BaseURL  string | ||||
| 	TopicURL string | ||||
| 	Raw      string | ||||
| } | ||||
| 
 | ||||
|  | @ -73,7 +73,23 @@ func (c *Client) Publish(topicURL, message string, options ...PublishOption) err | |||
| 	return err | ||||
| } | ||||
| 
 | ||||
| func (c *Client) Subscribe(topicURL string) { | ||||
| func (c *Client) Poll(topicURL string, options ...SubscribeOption) ([]*Message, error) { | ||||
| 	ctx := context.Background() | ||||
| 	messages := make([]*Message, 0) | ||||
| 	msgChan := make(chan *Message) | ||||
| 	errChan := make(chan error) | ||||
| 	go func() { | ||||
| 		err := performSubscribeRequest(ctx, msgChan, topicURL, options...) | ||||
| 		close(msgChan) | ||||
| 		errChan <- err | ||||
| 	}() | ||||
| 	for m := range msgChan { | ||||
| 		messages = append(messages, m) | ||||
| 	} | ||||
| 	return messages, <-errChan | ||||
| } | ||||
| 
 | ||||
| func (c *Client) Subscribe(topicURL string, options ...SubscribeOption) { | ||||
| 	c.mu.Lock() | ||||
| 	defer c.mu.Unlock() | ||||
| 	if _, ok := c.subscriptions[topicURL]; ok { | ||||
|  | @ -81,7 +97,7 @@ func (c *Client) Subscribe(topicURL string) { | |||
| 	} | ||||
| 	ctx, cancel := context.WithCancel(context.Background()) | ||||
| 	c.subscriptions[topicURL] = &subscription{cancel} | ||||
| 	go handleConnectionLoop(ctx, c.Messages, topicURL) | ||||
| 	go handleSubscribeConnLoop(ctx, c.Messages, topicURL, options...) | ||||
| } | ||||
| 
 | ||||
| func (c *Client) Unsubscribe(topicURL string) { | ||||
|  | @ -95,25 +111,30 @@ func (c *Client) Unsubscribe(topicURL string) { | |||
| 	return | ||||
| } | ||||
| 
 | ||||
| func handleConnectionLoop(ctx context.Context, msgChan chan *Message, topicURL string) { | ||||
| func handleSubscribeConnLoop(ctx context.Context, msgChan chan *Message, topicURL string, options ...SubscribeOption) { | ||||
| 	for { | ||||
| 		if err := handleConnection(ctx, msgChan, topicURL); err != nil { | ||||
| 			log.Printf("connection to %s failed: %s", topicURL, err.Error()) | ||||
| 		if err := performSubscribeRequest(ctx, msgChan, topicURL, options...); err != nil { | ||||
| 			log.Printf("Connection to %s failed: %s", topicURL, err.Error()) | ||||
| 		} | ||||
| 		select { | ||||
| 		case <-ctx.Done(): | ||||
| 			log.Printf("connection to %s exited", topicURL) | ||||
| 			log.Printf("Connection to %s exited", topicURL) | ||||
| 			return | ||||
| 		case <-time.After(5 * time.Second): | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func handleConnection(ctx context.Context, msgChan chan *Message, topicURL string) error { | ||||
| func performSubscribeRequest(ctx context.Context, msgChan chan *Message, topicURL string, options ...SubscribeOption) error { | ||||
| 	req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/json", topicURL), nil) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	for _, option := range options { | ||||
| 		if err := option(req); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	resp, err := http.DefaultClient.Do(req) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
|  |  | |||
|  | @ -4,42 +4,24 @@ import ( | |||
| 	"net/http" | ||||
| ) | ||||
| 
 | ||||
| type PublishOption func(r *http.Request) error | ||||
| type RequestOption func(r *http.Request) error | ||||
| type PublishOption = RequestOption | ||||
| type SubscribeOption = RequestOption | ||||
| 
 | ||||
| func WithTitle(title string) PublishOption { | ||||
| 	return func(r *http.Request) error { | ||||
| 		if title != "" { | ||||
| 			r.Header.Set("X-Title", title) | ||||
| 		} | ||||
| 		return nil | ||||
| 	} | ||||
| 	return WithHeader("X-Title", title) | ||||
| } | ||||
| 
 | ||||
| func WithPriority(priority string) PublishOption { | ||||
| 	return func(r *http.Request) error { | ||||
| 		if priority != "" { | ||||
| 			r.Header.Set("X-Priority", priority) | ||||
| 		} | ||||
| 		return nil | ||||
| 	} | ||||
| 	return WithHeader("X-Priority", priority) | ||||
| } | ||||
| 
 | ||||
| func WithTags(tags string) PublishOption { | ||||
| 	return func(r *http.Request) error { | ||||
| 		if tags != "" { | ||||
| 			r.Header.Set("X-Tags", tags) | ||||
| 		} | ||||
| 		return nil | ||||
| 	} | ||||
| 	return WithHeader("X-Tags", tags) | ||||
| } | ||||
| 
 | ||||
| func WithDelay(delay string) PublishOption { | ||||
| 	return func(r *http.Request) error { | ||||
| 		if delay != "" { | ||||
| 			r.Header.Set("X-Delay", delay) | ||||
| 		} | ||||
| 		return nil | ||||
| 	} | ||||
| 	return WithHeader("X-Delay", delay) | ||||
| } | ||||
| 
 | ||||
| func WithNoCache() PublishOption { | ||||
|  | @ -50,20 +32,32 @@ func WithNoFirebase() PublishOption { | |||
| 	return WithHeader("X-Firebase", "no") | ||||
| } | ||||
| 
 | ||||
| func WithHeader(header, value string) PublishOption { | ||||
| func WithSince(since string) SubscribeOption { | ||||
| 	return WithQueryParam("since", since) | ||||
| } | ||||
| 
 | ||||
| func WithPoll() SubscribeOption { | ||||
| 	return WithQueryParam("poll", "1") | ||||
| } | ||||
| 
 | ||||
| func WithScheduled() SubscribeOption { | ||||
| 	return WithQueryParam("scheduled", "1") | ||||
| } | ||||
| 
 | ||||
| func WithHeader(header, value string) RequestOption { | ||||
| 	return func(r *http.Request) error { | ||||
| 		r.Header.Set(header, value) | ||||
| 		if value != "" { | ||||
| 			r.Header.Set(header, value) | ||||
| 		} | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| type SubscribeOption func(r *http.Request) error | ||||
| 
 | ||||
| func WithSince(since string) PublishOption { | ||||
| func WithQueryParam(param, value string) RequestOption { | ||||
| 	return func(r *http.Request) error { | ||||
| 		if since != "" { | ||||
| 		if value != "" { | ||||
| 			q := r.URL.Query() | ||||
| 			q.Add("since", since) | ||||
| 			q.Add(param, value) | ||||
| 			r.URL.RawQuery = q.Encode() | ||||
| 		} | ||||
| 		return nil | ||||
|  |  | |||
|  | @ -36,7 +36,7 @@ func New() *cli.App { | |||
| 
 | ||||
| func execMainApp(c *cli.Context) error { | ||||
| 	log.Printf("\x1b[1;33mDeprecation notice: Please run the server using 'ntfy serve'; see 'ntfy -h' for help.\x1b[0m") | ||||
| 	log.Printf("\x1b[1;33mThis way of running the server will be removed Feb 2022.\x1b[0m") | ||||
| 	log.Printf("\x1b[1;33mThis way of running the server will be removed March 2022. See https://ntfy.sh/docs/deprecations/ for details.\x1b[0m") | ||||
| 	return execServe(c) | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -9,7 +9,7 @@ import ( | |||
| 
 | ||||
| var cmdPublish = &cli.Command{ | ||||
| 	Name:      "publish", | ||||
| 	Aliases:   []string{"pub", "send"}, | ||||
| 	Aliases:   []string{"pub", "send", "push"}, | ||||
| 	Usage:     "Send message via a ntfy server", | ||||
| 	UsageText: "ntfy send [OPTIONS..] TOPIC MESSAGE", | ||||
| 	Action:    execPublish, | ||||
|  |  | |||
|  | @ -21,6 +21,8 @@ var cmdSubscribe = &cli.Command{ | |||
| 	Flags: []cli.Flag{ | ||||
| 		&cli.StringFlag{Name: "exec", Aliases: []string{"e"}, Usage: "execute command for each message event"}, | ||||
| 		&cli.StringFlag{Name: "since", Aliases: []string{"s"}, Usage: "return events since (Unix timestamp, or all)"}, | ||||
| 		&cli.BoolFlag{Name: "poll", Aliases: []string{"p"}, Usage: "return events and exit, do not listen for new events"}, | ||||
| 		&cli.BoolFlag{Name: "scheduled", Aliases: []string{"sched", "S"}, Usage: "also return scheduled/delayed events"}, | ||||
| 	}, | ||||
| 	Description: `(THIS COMMAND IS INCUBATING. IT MAY CHANGE WITHOUT NOTICE.) | ||||
| 
 | ||||
|  | @ -45,7 +47,8 @@ are passed to the command as environment variables: | |||
| Examples: | ||||
|   ntfy subscribe mytopic                       # Prints JSON for incoming messages to stdout | ||||
|   ntfy sub home.lan/backups alerts             # Subscribe to two different topics | ||||
|   ntfy sub --exec='notify-send "$m"' mytopic   # Execute command for incoming messages'   | ||||
|   ntfy sub --exec='notify-send "$m"' mytopic   # Execute command for incoming messages | ||||
|   ntfy sub --exec=/my/script topic1 topic2     # Subscribe to two topics and execute command for each message | ||||
| `, | ||||
| } | ||||
| 
 | ||||
|  | @ -56,11 +59,37 @@ func execSubscribe(c *cli.Context) error { | |||
| 	log.Printf("\x1b[1;33mThis command is incubating. The interface may change without notice.\x1b[0m") | ||||
| 	cl := client.DefaultClient | ||||
| 	command := c.String("exec") | ||||
| 	for _, topic := range c.Args().Slice() { | ||||
| 		cl.Subscribe(expandTopicURL(topic)) | ||||
| 	since := c.String("since") | ||||
| 	poll := c.Bool("poll") | ||||
| 	scheduled := c.Bool("scheduled") | ||||
| 	topics := c.Args().Slice() | ||||
| 	var options []client.SubscribeOption | ||||
| 	if since != "" { | ||||
| 		options = append(options, client.WithSince(since)) | ||||
| 	} | ||||
| 	for m := range cl.Messages { | ||||
| 		_ = dispatchMessage(c, command, m) | ||||
| 	if poll { | ||||
| 		options = append(options, client.WithPoll()) | ||||
| 	} | ||||
| 	if scheduled { | ||||
| 		options = append(options, client.WithScheduled()) | ||||
| 	} | ||||
| 	if poll { | ||||
| 		for _, topic := range topics { | ||||
| 			messages, err := cl.Poll(expandTopicURL(topic), options...) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			for _, m := range messages { | ||||
| 				_ = dispatchMessage(c, command, m) | ||||
| 			} | ||||
| 		} | ||||
| 	} else { | ||||
| 		for _, topic := range topics { | ||||
| 			cl.Subscribe(expandTopicURL(topic), options...) | ||||
| 		} | ||||
| 		for m := range cl.Messages { | ||||
| 			_ = dispatchMessage(c, command, m) | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | @ -77,11 +106,9 @@ func execCommand(c *cli.Context, command string, m *client.Message) error { | |||
| 	if m.Event == client.OpenEvent { | ||||
| 		log.Printf("[%s] Connection opened, subscribed to topic", collapseTopicURL(m.TopicURL)) | ||||
| 	} else if m.Event == client.MessageEvent { | ||||
| 		go func() { | ||||
| 			if err := runCommandInternal(c, command, m); err != nil { | ||||
| 				log.Printf("[%s] Command failed: %s", collapseTopicURL(m.TopicURL), err.Error()) | ||||
| 			} | ||||
| 		}() | ||||
| 		if err := runCommandInternal(c, command, m); err != nil { | ||||
| 			log.Printf("[%s] Command failed: %s", collapseTopicURL(m.TopicURL), err.Error()) | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  |  | |||
							
								
								
									
										25
									
								
								docs/deprecations.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								docs/deprecations.md
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,25 @@ | |||
| # Deprecation notices | ||||
| This page is used to list deprecation notices for ntfy. Deprecated commands and options will be  | ||||
| **removed after ~3 months** from the time they were deprecated. | ||||
| 
 | ||||
| ## Active deprecations | ||||
| 
 | ||||
| ### Running server via `ntfy` (instead of `ntfy serve`) | ||||
| > since 2021-12-17 | ||||
| 
 | ||||
| As more commands are added to the `ntfy` CLI tool, using just `ntfy` to run the server is not practical | ||||
| anymore. Please use `ntfy serve` instead. This also applies to Docker images, as they can also execute more than | ||||
| just the server. | ||||
| 
 | ||||
| === "Before" | ||||
|     ``` | ||||
|     $ ntfy | ||||
|     2021/12/17 08:16:01 Listening on :80/http | ||||
|     ``` | ||||
| 
 | ||||
| === "After" | ||||
|     ``` | ||||
|     $ ntfy serve | ||||
|     2021/12/17 08:16:01 Listening on :80/http | ||||
|     ``` | ||||
| 
 | ||||
|  | @ -12,7 +12,7 @@ We support amd64, armv7 and arm64. | |||
| 
 | ||||
| 1. Install ntfy using one of the methods described below | ||||
| 2. Then (optionally) edit `/etc/ntfy/config.yml` (see [configuration](config.md)) | ||||
| 3. Then just run it with `ntfy` (or `systemctl start ntfy` when using the deb/rpm). | ||||
| 3. Then just run it with `ntfy serve` (or `systemctl start ntfy` when using the deb/rpm). | ||||
| 
 | ||||
| ## Binaries and packages | ||||
| Please check out the [releases page](https://github.com/binwiederhier/ntfy/releases) for binaries and | ||||
|  | @ -22,21 +22,21 @@ deb/rpm packages. | |||
|     ```bash | ||||
|     wget https://github.com/binwiederhier/ntfy/releases/download/v1.7.0/ntfy_1.7.0_linux_x86_64.tar.gz | ||||
|     sudo tar -C /usr/bin -zxf ntfy_*.tar.gz ntfy | ||||
|     sudo ./ntfy | ||||
|     sudo ./ntfy serve | ||||
|     ``` | ||||
| 
 | ||||
| === "armv7/armhf" | ||||
|     ```bash | ||||
|     wget https://github.com/binwiederhier/ntfy/releases/download/v1.7.0/ntfy_1.7.0_linux_armv7.tar.gz | ||||
|     sudo tar -C /usr/bin -zxf ntfy_*.tar.gz ntfy | ||||
|     sudo ./ntfy | ||||
|     sudo ./ntfy serve | ||||
|     ``` | ||||
| 
 | ||||
| === "arm64" | ||||
|     ```bash | ||||
|     wget https://github.com/binwiederhier/ntfy/releases/download/v1.7.0/ntfy_1.7.0_linux_arm64.tar.gz | ||||
|     sudo tar -C /usr/bin -zxf ntfy_*.tar.gz ntfy | ||||
|     sudo ./ntfy | ||||
|     sudo ./ntfy serve | ||||
|     ``` | ||||
| 
 | ||||
| ## Debian/Ubuntu repository | ||||
|  | @ -132,12 +132,12 @@ The [ntfy image](https://hub.docker.com/r/binwiederhier/ntfy) is available for a | |||
| straight forward to use. | ||||
| 
 | ||||
| The server exposes its web UI and the API on port 80, so you need to expose that in Docker. To use the persistent  | ||||
| [message cache](config.md#message-cache), you also need to map a volume to `/var/cache/ntfy`. To change other settings, you should map `/etc/ntfy`, | ||||
| so you can edit `/etc/ntfy/config.yml`. | ||||
| [message cache](config.md#message-cache), you also need to map a volume to `/var/cache/ntfy`. To change other settings,  | ||||
| you should map `/etc/ntfy`, so you can edit `/etc/ntfy/config.yml`. | ||||
| 
 | ||||
| Basic usage (no cache or additional config): | ||||
| ``` | ||||
| docker run -p 80:80 -it binwiederhier/ntfy | ||||
| docker run -p 80:80 -it binwiederhier/ntfy serve | ||||
| ``` | ||||
| 
 | ||||
| With persistent cache (configured as command line arguments): | ||||
|  | @ -147,7 +147,8 @@ docker run \ | |||
|   -p 80:80 \ | ||||
|   -it \ | ||||
|   binwiederhier/ntfy \ | ||||
|     --cache-file /var/cache/ntfy/cache.db | ||||
|     --cache-file /var/cache/ntfy/cache.db \ | ||||
|     serve | ||||
| ``` | ||||
| 
 | ||||
| With other config options (configured via `/etc/ntfy/config.yml`, see [configuration](config.md) for details): | ||||
|  | @ -156,7 +157,8 @@ docker run \ | |||
|   -v /etc/ntfy:/etc/ntfy \ | ||||
|   -p 80:80 \ | ||||
|   -it \ | ||||
|   binwiederhier/ntfy | ||||
|   binwiederhier/ntfy \ | ||||
|   serve | ||||
| ``` | ||||
| 
 | ||||
| ## Go | ||||
|  |  | |||
							
								
								
									
										3
									
								
								docs/subscribe/cli.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								docs/subscribe/cli.md
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | |||
| # Subscribe via CLI | ||||
| 
 | ||||
| XXXXXXXXXxxx | ||||
|  | @ -1,11 +1,11 @@ | |||
| site_dir: server/docs | ||||
| site_name: ntfy | ||||
| site_url: https://ntfy.sh | ||||
| site_description: simple HTTP-based pub-sub | ||||
| site_description: Send push notifications to your phone via PUT/POST | ||||
| copyright: Made with ❤️ by Philipp C. Heckel | ||||
| repo_name: binwiederhier/ntfy | ||||
| repo_url: https://github.com/binwiederhier/ntfy | ||||
| edit_uri: edit/main/docs/ | ||||
| edit_uri: blob/main/docs/ | ||||
| 
 | ||||
| theme: | ||||
|   name: material | ||||
|  | @ -31,7 +31,6 @@ theme: | |||
|     - search.highlight | ||||
|     - search.share | ||||
|     - navigation.sections | ||||
|     # - navigation.instant | ||||
|     - toc.integrate | ||||
|     - content.tabs.link | ||||
| extra: | ||||
|  | @ -75,6 +74,7 @@ nav: | |||
| - "Subscribing": | ||||
|   - "From your phone": subscribe/phone.md | ||||
|   - "From the Web UI": subscribe/web.md | ||||
|   - "Using the CLI": subscribe/cli.md | ||||
|   - "Using the API": subscribe/api.md | ||||
| - "Self-hosting": | ||||
|   - "Installation": install.md | ||||
|  | @ -83,6 +83,7 @@ nav: | |||
|   - "FAQs": faq.md | ||||
|   - "Examples": examples.md | ||||
|   - "Emojis 🥳 🎉": emojis.md | ||||
|   - "Deprecation notices": deprecations.md | ||||
|   - "Development": develop.md | ||||
|   - "Privacy policy": privacy.md | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue