mirror of
https://github.com/binwiederhier/ntfy.git
synced 2024-12-14 11:47:33 +00:00
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…
Reference in a new issue