mirror of
https://github.com/crazy-max/diun.git
synced 2025-12-21 13:23:09 +01:00
switch to http client for pushover notifier
This commit is contained in:
@@ -12,6 +12,7 @@ You can send notifications using [Pushover](https://pushover.net/).
|
|||||||
recipient: gznej3rKEVAvPUxu9vvNnqpmZpokzF
|
recipient: gznej3rKEVAvPUxu9vvNnqpmZpokzF
|
||||||
priority: -2
|
priority: -2
|
||||||
sound: none
|
sound: none
|
||||||
|
timeout: 10s
|
||||||
templateTitle: "{{ .Entry.Image }} released"
|
templateTitle: "{{ .Entry.Image }} released"
|
||||||
templateBody: |
|
templateBody: |
|
||||||
Docker tag {{ .Entry.Image }} which you subscribed to through {{ .Entry.Provider }} provider has been released.
|
Docker tag {{ .Entry.Image }} which you subscribed to through {{ .Entry.Provider }} provider has been released.
|
||||||
@@ -25,6 +26,7 @@ You can send notifications using [Pushover](https://pushover.net/).
|
|||||||
| `recipientFile` | | Use content of secret file as User key if `recipient` not defined |
|
| `recipientFile` | | Use content of secret file as User key if `recipient` not defined |
|
||||||
| `priority` | | Priority of the notification |
|
| `priority` | | Priority of the notification |
|
||||||
| `sound` | | Notification sound to be used |
|
| `sound` | | Notification sound to be used |
|
||||||
|
| `timeout` | `10s` | Timeout specifies a time limit for the request to be made |
|
||||||
| `templateTitle`[^1] | See [below](#default-templatetitle) | [Notification template](../faq.md#notification-template) for message title |
|
| `templateTitle`[^1] | See [below](#default-templatetitle) | [Notification template](../faq.md#notification-template) for message title |
|
||||||
| `templateBody`[^1] | See [below](#default-templatebody) | [Notification template](../faq.md#notification-template) for message body |
|
| `templateBody`[^1] | See [below](#default-templatebody) | [Notification template](../faq.md#notification-template) for message body |
|
||||||
|
|
||||||
@@ -35,6 +37,7 @@ You can send notifications using [Pushover](https://pushover.net/).
|
|||||||
* `DIUN_NOTIF_PUSHOVER_RECIPIENTFILE`
|
* `DIUN_NOTIF_PUSHOVER_RECIPIENTFILE`
|
||||||
* `DIUN_NOTIF_PUSHOVER_PRIORITY`
|
* `DIUN_NOTIF_PUSHOVER_PRIORITY`
|
||||||
* `DIUN_NOTIF_PUSHOVER_SOUND`
|
* `DIUN_NOTIF_PUSHOVER_SOUND`
|
||||||
|
* `DIUN_NOTIF_PUSHOVER_TIMEOUT`
|
||||||
* `DIUN_NOTIF_PUSHOVER_TEMPLATETITLE`
|
* `DIUN_NOTIF_PUSHOVER_TEMPLATETITLE`
|
||||||
* `DIUN_NOTIF_PUSHOVER_TEMPLATEBODY`
|
* `DIUN_NOTIF_PUSHOVER_TEMPLATEBODY`
|
||||||
|
|
||||||
|
|||||||
1
go.mod
1
go.mod
@@ -21,7 +21,6 @@ require (
|
|||||||
github.com/eclipse/paho.mqtt.golang v1.5.0
|
github.com/eclipse/paho.mqtt.golang v1.5.0
|
||||||
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df
|
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df
|
||||||
github.com/go-playground/validator/v10 v10.27.0
|
github.com/go-playground/validator/v10 v10.27.0
|
||||||
github.com/gregdel/pushover v1.3.1
|
|
||||||
github.com/hashicorp/nomad/api v0.0.0-20231213195942-64e3dca9274b // v1.7.2
|
github.com/hashicorp/nomad/api v0.0.0-20231213195942-64e3dca9274b // v1.7.2
|
||||||
github.com/jedib0t/go-pretty/v6 v6.6.8
|
github.com/jedib0t/go-pretty/v6 v6.6.8
|
||||||
github.com/matcornic/hermes/v2 v2.1.0
|
github.com/matcornic/hermes/v2 v2.1.0
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -167,8 +167,6 @@ github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWS
|
|||||||
github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
|
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
|
||||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
|
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
|
||||||
github.com/gregdel/pushover v1.3.1 h1:4bMLITOZ15+Zpi6qqoGqOPuVHCwSUvMCgVnN5Xhilfo=
|
|
||||||
github.com/gregdel/pushover v1.3.1/go.mod h1:EcaO66Nn1StkpEm1iKtBTV3d2A16SoMsVER1PthX7to=
|
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 h1:e9Rjr40Z98/clHv5Yg79Is0NtosR5LXRvdr7o/6NwbA=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 h1:e9Rjr40Z98/clHv5Yg79Is0NtosR5LXRvdr7o/6NwbA=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M=
|
||||||
github.com/hashicorp/cronexpr v1.1.2 h1:wG/ZYIKT+RT3QkOdgYc+xsKWVRgnxJ1OJtjjy84fJ9A=
|
github.com/hashicorp/cronexpr v1.1.2 h1:wG/ZYIKT+RT3QkOdgYc+xsKWVRgnxJ1OJtjjy84fJ9A=
|
||||||
|
|||||||
@@ -160,6 +160,7 @@ for <code>{{ .Entry.Manifest.Platform }}</code> platform.
|
|||||||
Pushover: &model.NotifPushover{
|
Pushover: &model.NotifPushover{
|
||||||
Token: "uQiRzpo4DXghDmr9QzzfQu27cmVRsG",
|
Token: "uQiRzpo4DXghDmr9QzzfQu27cmVRsG",
|
||||||
Recipient: "gznej3rKEVAvPUxu9vvNnqpmZpokzF",
|
Recipient: "gznej3rKEVAvPUxu9vvNnqpmZpokzF",
|
||||||
|
Timeout: utl.NewDuration(10 * time.Second),
|
||||||
TemplateTitle: model.NotifDefaultTemplateTitle,
|
TemplateTitle: model.NotifDefaultTemplateTitle,
|
||||||
TemplateBody: model.NotifDefaultTemplateBody,
|
TemplateBody: model.NotifDefaultTemplateBody,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/crazy-max/diun/v4/pkg/utl"
|
||||||
|
)
|
||||||
|
|
||||||
// NotifPushover holds Pushover notification configuration details
|
// NotifPushover holds Pushover notification configuration details
|
||||||
type NotifPushover struct {
|
type NotifPushover struct {
|
||||||
Token string `yaml:"token,omitempty" json:"token,omitempty" validate:"omitempty"`
|
Token string `yaml:"token,omitempty" json:"token,omitempty" validate:"omitempty"`
|
||||||
@@ -8,6 +14,7 @@ type NotifPushover struct {
|
|||||||
RecipientFile string `yaml:"recipientFile,omitempty" json:"recipientFile,omitempty" validate:"omitempty,file"`
|
RecipientFile string `yaml:"recipientFile,omitempty" json:"recipientFile,omitempty" validate:"omitempty,file"`
|
||||||
Priority int `yaml:"priority,omitempty" json:"priority,omitempty" validate:"omitempty,min=-2,max=2"`
|
Priority int `yaml:"priority,omitempty" json:"priority,omitempty" validate:"omitempty,min=-2,max=2"`
|
||||||
Sound string `yaml:"sound,omitempty" json:"sound,omitempty" validate:"omitempty"`
|
Sound string `yaml:"sound,omitempty" json:"sound,omitempty" validate:"omitempty"`
|
||||||
|
Timeout *time.Duration `yaml:"timeout,omitempty" json:"timeout,omitempty" validate:"required"`
|
||||||
TemplateTitle string `yaml:"templateTitle,omitempty" json:"templateTitle,omitempty" validate:"required"`
|
TemplateTitle string `yaml:"templateTitle,omitempty" json:"templateTitle,omitempty" validate:"required"`
|
||||||
TemplateBody string `yaml:"templateBody,omitempty" json:"templateBody,omitempty" validate:"required"`
|
TemplateBody string `yaml:"templateBody,omitempty" json:"templateBody,omitempty" validate:"required"`
|
||||||
}
|
}
|
||||||
@@ -21,6 +28,7 @@ func (s *NotifPushover) GetDefaults() *NotifPushover {
|
|||||||
|
|
||||||
// SetDefaults sets the default values
|
// SetDefaults sets the default values
|
||||||
func (s *NotifPushover) SetDefaults() {
|
func (s *NotifPushover) SetDefaults() {
|
||||||
|
s.Timeout = utl.NewDuration(10 * time.Second)
|
||||||
s.TemplateTitle = NotifDefaultTemplateTitle
|
s.TemplateTitle = NotifDefaultTemplateTitle
|
||||||
s.TemplateBody = NotifDefaultTemplateBody
|
s.TemplateBody = NotifDefaultTemplateBody
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,24 @@
|
|||||||
package pushover
|
package pushover
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/crazy-max/diun/v4/internal/model"
|
"github.com/crazy-max/diun/v4/internal/model"
|
||||||
"github.com/crazy-max/diun/v4/internal/msg"
|
"github.com/crazy-max/diun/v4/internal/msg"
|
||||||
"github.com/crazy-max/diun/v4/internal/notif/notifier"
|
"github.com/crazy-max/diun/v4/internal/notif/notifier"
|
||||||
"github.com/crazy-max/diun/v4/pkg/utl"
|
"github.com/crazy-max/diun/v4/pkg/utl"
|
||||||
"github.com/gregdel/pushover"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const pushoverAPIURL = "https://api.pushover.net/1/messages.json"
|
||||||
|
|
||||||
// Client represents an active Pushover notification object
|
// Client represents an active Pushover notification object
|
||||||
type Client struct {
|
type Client struct {
|
||||||
*notifier.Notifier
|
*notifier.Notifier
|
||||||
@@ -38,11 +46,15 @@ func (c *Client) Send(entry model.NotifEntry) error {
|
|||||||
token, err := utl.GetSecret(c.cfg.Token, c.cfg.TokenFile)
|
token, err := utl.GetSecret(c.cfg.Token, c.cfg.TokenFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "cannot retrieve token secret for Pushover notifier")
|
return errors.Wrap(err, "cannot retrieve token secret for Pushover notifier")
|
||||||
|
} else if token == "" {
|
||||||
|
return errors.New("Pushover API token cannot be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
recipient, err := utl.GetSecret(c.cfg.Recipient, c.cfg.RecipientFile)
|
recipient, err := utl.GetSecret(c.cfg.Recipient, c.cfg.RecipientFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "cannot retrieve recipient secret for Pushover notifier")
|
return errors.Wrap(err, "cannot retrieve recipient secret for Pushover notifier")
|
||||||
|
} else if recipient == "" {
|
||||||
|
return errors.New("Pushover recipient cannot be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
message, err := msg.New(msg.Options{
|
message, err := msg.New(msg.Options{
|
||||||
@@ -60,16 +72,77 @@ func (c *Client) Send(entry model.NotifEntry) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = pushover.New(token).SendMessage(&pushover.Message{
|
cancelCtx, cancel := context.WithCancelCause(context.Background())
|
||||||
Title: string(title),
|
timeoutCtx, _ := context.WithTimeoutCause(cancelCtx, *c.cfg.Timeout, errors.WithStack(context.DeadlineExceeded)) //nolint:govet // no need to manually cancel this context as we already rely on parent
|
||||||
Message: string(body),
|
defer func() { cancel(errors.WithStack(context.Canceled)) }()
|
||||||
Priority: c.cfg.Priority,
|
|
||||||
Sound: c.cfg.Sound,
|
|
||||||
URL: c.meta.URL,
|
|
||||||
URLTitle: c.meta.Name,
|
|
||||||
Timestamp: time.Now().Unix(),
|
|
||||||
HTML: true,
|
|
||||||
}, pushover.NewRecipient(recipient))
|
|
||||||
|
|
||||||
|
form := url.Values{}
|
||||||
|
form.Add("token", token)
|
||||||
|
form.Add("user", recipient)
|
||||||
|
form.Add("title", string(title))
|
||||||
|
form.Add("message", string(body))
|
||||||
|
form.Add("priority", strconv.Itoa(c.cfg.Priority))
|
||||||
|
if c.cfg.Sound != "" {
|
||||||
|
form.Add("sound", c.cfg.Sound)
|
||||||
|
}
|
||||||
|
if c.meta.URL != "" {
|
||||||
|
form.Add("url", c.meta.URL)
|
||||||
|
}
|
||||||
|
if c.meta.Name != "" {
|
||||||
|
form.Add("url_title", c.meta.Name)
|
||||||
|
}
|
||||||
|
form.Add("timestamp", strconv.FormatInt(time.Now().Unix(), 10))
|
||||||
|
form.Add("html", "1")
|
||||||
|
|
||||||
|
hc := http.Client{}
|
||||||
|
req, err := http.NewRequestWithContext(timeoutCtx, "POST", pushoverAPIURL, strings.NewReader(form.Encode()))
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
req.Header.Set("User-Agent", c.meta.UserAgent)
|
||||||
|
|
||||||
|
resp, err := hc.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.Header != nil {
|
||||||
|
var appLimit, appRemaining int
|
||||||
|
var appReset time.Time
|
||||||
|
if limit := resp.Header.Get("X-Limit-App-Limit"); limit != "" {
|
||||||
|
if i, err := strconv.Atoi(limit); err == nil {
|
||||||
|
appLimit = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if remaining := resp.Header.Get("X-Limit-App-Remaining"); remaining != "" {
|
||||||
|
if i, err := strconv.Atoi(remaining); err == nil {
|
||||||
|
appRemaining = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if reset := resp.Header.Get("X-Limit-App-Reset"); reset != "" {
|
||||||
|
if i, err := strconv.Atoi(reset); err == nil {
|
||||||
|
appReset = time.Unix(int64(i), 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Debug().Msgf("Pushover app limit: %d, remaining: %d, reset: %s", appLimit, appRemaining, appReset)
|
||||||
|
}
|
||||||
|
|
||||||
|
var respBody struct {
|
||||||
|
Status int `json:"status"`
|
||||||
|
Request string `json:"request"`
|
||||||
|
Errors []string `json:"errors"`
|
||||||
|
User string `json:"user"`
|
||||||
|
Token string `json:"token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = json.NewDecoder(resp.Body).Decode(&respBody); err != nil {
|
||||||
|
return errors.Wrapf(err, "cannot decode JSON body response for HTTP %d %s status: %+v", resp.StatusCode, http.StatusText(resp.StatusCode), respBody)
|
||||||
|
}
|
||||||
|
if respBody.Status != 1 {
|
||||||
|
return errors.Errorf("Pushover API call failed with status %d: %v", respBody.Status, respBody.Errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
9
vendor/github.com/gregdel/pushover/.travis.yml
generated
vendored
9
vendor/github.com/gregdel/pushover/.travis.yml
generated
vendored
@@ -1,9 +0,0 @@
|
|||||||
language: go
|
|
||||||
go:
|
|
||||||
- 1.15.2
|
|
||||||
before_install:
|
|
||||||
- go get github.com/axw/gocov/gocov
|
|
||||||
- go get github.com/mattn/goveralls
|
|
||||||
- if ! go get code.google.com/p/go.tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi
|
|
||||||
script:
|
|
||||||
- $HOME/gopath/bin/goveralls -service=travis-ci
|
|
||||||
21
vendor/github.com/gregdel/pushover/LICENSE
generated
vendored
21
vendor/github.com/gregdel/pushover/LICENSE
generated
vendored
@@ -1,21 +0,0 @@
|
|||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) Grégoire Delattre <gregoire.delattre@gmail.com>
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
134
vendor/github.com/gregdel/pushover/README.md
generated
vendored
134
vendor/github.com/gregdel/pushover/README.md
generated
vendored
@@ -1,134 +0,0 @@
|
|||||||
pushover
|
|
||||||
=========
|
|
||||||
|
|
||||||
[](http://godoc.org/github.com/gregdel/pushover)
|
|
||||||
[](https://travis-ci.org/gregdel/pushover)
|
|
||||||
[](https://coveralls.io/github/gregdel/pushover?branch=master)
|
|
||||||
[](https://goreportcard.com/report/github.com/gregdel/pushover)
|
|
||||||
|
|
||||||
pushover is a wrapper around the Superblock's Pushover API written in go.
|
|
||||||
Based on their [documentation](https://pushover.net/api). It's a convenient way to send notifications from a go program with only a few lines of code.
|
|
||||||
|
|
||||||
## Messages
|
|
||||||
|
|
||||||
### Send a simple message
|
|
||||||
|
|
||||||
Here is a simple example for sending a notification to a recipient. A recipient can be a user or a group. There is no real difference, they both use a notification token.
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/gregdel/pushover"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
// Create a new pushover app with a token
|
|
||||||
app := pushover.New("uQiRzpo4DXghDmr9QzzfQu27cmVRsG")
|
|
||||||
|
|
||||||
// Create a new recipient
|
|
||||||
recipient := pushover.NewRecipient("gznej3rKEVAvPUxu9vvNnqpmZpokzF")
|
|
||||||
|
|
||||||
// Create the message to send
|
|
||||||
message := pushover.NewMessage("Hello !")
|
|
||||||
|
|
||||||
// Send the message to the recipient
|
|
||||||
response, err := app.SendMessage(message, recipient)
|
|
||||||
if err != nil {
|
|
||||||
log.Panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print the response if you want
|
|
||||||
log.Println(response)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Send a message with a title
|
|
||||||
|
|
||||||
There is a simple way to create a message with a title. Instead of using pushover.NewMessage you can use pushover.NewMessageWithTitle.
|
|
||||||
|
|
||||||
```go
|
|
||||||
message := pushover.NewMessageWithTitle("My awesome message", "My title")
|
|
||||||
```
|
|
||||||
|
|
||||||
### Send a fancy message
|
|
||||||
|
|
||||||
If you want a more detailed message you can still do it.
|
|
||||||
|
|
||||||
```go
|
|
||||||
message := &pushover.Message{
|
|
||||||
Message: "My awesome message",
|
|
||||||
Title: "My title",
|
|
||||||
Priority: pushover.PriorityEmergency,
|
|
||||||
URL: "http://google.com",
|
|
||||||
URLTitle: "Google",
|
|
||||||
Timestamp: time.Now().Unix(),
|
|
||||||
Retry: 60 * time.Second,
|
|
||||||
Expire: time.Hour,
|
|
||||||
DeviceName: "SuperDevice",
|
|
||||||
CallbackURL: "http://yourapp.com/callback",
|
|
||||||
Sound: pushover.SoundCosmic,
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Send a message with an attachment
|
|
||||||
|
|
||||||
You can send an image attachment along with the message.
|
|
||||||
|
|
||||||
```go
|
|
||||||
file, err := os.Open("/some/image.png")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
message := pushover.NewMessage("Hello !")
|
|
||||||
if err := message.AddAttachment(file); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Callbacks and receipts
|
|
||||||
|
|
||||||
If you're using an emergency notification you'll have to specify a retry period and an expiration delay. You can get the receipt details using the token in the message response.
|
|
||||||
|
|
||||||
|
|
||||||
```go
|
|
||||||
...
|
|
||||||
response, err := app.SendMessage(message, recipient)
|
|
||||||
if err != nil {
|
|
||||||
log.Panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
receiptDetails, err := app.GetReceiptDetails(response.Receipt)
|
|
||||||
if err != nil {
|
|
||||||
log.Panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("Acknowledged status :", receiptDetails.Acknowledged)
|
|
||||||
```
|
|
||||||
|
|
||||||
You can also cancel an emergency notification before the expiration time.
|
|
||||||
|
|
||||||
```go
|
|
||||||
response, err := app.CancelEmergencyNotification(response.Receipt)
|
|
||||||
if err != nil {
|
|
||||||
log.Panic(err)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## User verification
|
|
||||||
|
|
||||||
If you want to validate that the recipient token is valid.
|
|
||||||
|
|
||||||
```go
|
|
||||||
...
|
|
||||||
recipientDetails, err := app.GetRecipientDetails(recipient)
|
|
||||||
if err != nil {
|
|
||||||
log.Panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println(recipientDetails)
|
|
||||||
```
|
|
||||||
18
vendor/github.com/gregdel/pushover/errors.go
generated
vendored
18
vendor/github.com/gregdel/pushover/errors.go
generated
vendored
@@ -1,18 +0,0 @@
|
|||||||
package pushover
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Errors represents the errors returned by pushover.
|
|
||||||
type Errors []string
|
|
||||||
|
|
||||||
// Error represents the error as a string.
|
|
||||||
func (e Errors) Error() string {
|
|
||||||
ret := ""
|
|
||||||
if len(e) > 0 {
|
|
||||||
ret = "Errors:\n"
|
|
||||||
ret += strings.Join(e, "\n")
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
118
vendor/github.com/gregdel/pushover/glances.go
generated
vendored
118
vendor/github.com/gregdel/pushover/glances.go
generated
vendored
@@ -1,118 +0,0 @@
|
|||||||
package pushover
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"unicode/utf8"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// GlancesAllDevices can be passed as a device name to send a glances-message to all devices
|
|
||||||
GlancesAllDevices = ""
|
|
||||||
// GlancesMessageMaxTitleLength is the max title length in a pushover glance update
|
|
||||||
GlancesMessageMaxTitleLength = 100
|
|
||||||
// GlancesMessageMaxTextLength is the max text length in a pushover glance update
|
|
||||||
GlancesMessageMaxTextLength = 100
|
|
||||||
// GlancesMessageMaxSubtextLength is the max subtext length in a pushover glance update
|
|
||||||
GlancesMessageMaxSubtextLength = 100
|
|
||||||
)
|
|
||||||
|
|
||||||
// Glance represents a pushover glances update request.
|
|
||||||
type Glance struct {
|
|
||||||
// Title(max 100): a description of the data being shown, such as "Widgets Sold"
|
|
||||||
Title *string
|
|
||||||
// Text(max 100): the main line of data, used on most screens
|
|
||||||
Text *string
|
|
||||||
// Subtext(max 100): a second line of data
|
|
||||||
Subtext *string
|
|
||||||
// Count(can be negative): shown on smaller screens; useful for simple counts
|
|
||||||
Count *int
|
|
||||||
// Percent(0-100): shown on some screens as a progress bar/circle
|
|
||||||
Percent *int
|
|
||||||
DeviceName string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int returns the pointer of the input i
|
|
||||||
// Used to check for the Glance API if a parameter was left empty or if none was specified
|
|
||||||
func Int(i int) *int {
|
|
||||||
return &i
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the pointer of the input s
|
|
||||||
// Used to check for the Glance API if a parameter was left empty or if none was specified
|
|
||||||
func String(s string) *string {
|
|
||||||
return &s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Glance) validate() error {
|
|
||||||
// check if data is present
|
|
||||||
if m.Title == nil && m.Text == nil && m.Subtext == nil && m.Count == nil && m.Percent == nil {
|
|
||||||
return ErrGlancesMissingData
|
|
||||||
}
|
|
||||||
if m.Title != nil && utf8.RuneCountInString(*m.Title) > GlancesMessageMaxTitleLength {
|
|
||||||
return ErrGlancesTitleTooLong
|
|
||||||
}
|
|
||||||
if m.Text != nil && utf8.RuneCountInString(*m.Text) > GlancesMessageMaxTextLength {
|
|
||||||
return ErrGlancesTextTooLong
|
|
||||||
}
|
|
||||||
if m.Subtext != nil && utf8.RuneCountInString(*m.Subtext) > GlancesMessageMaxSubtextLength {
|
|
||||||
return ErrGlancesSubtextTooLong
|
|
||||||
}
|
|
||||||
if m.Percent != nil && (*m.Percent < 0 || *m.Percent > 100) {
|
|
||||||
return ErrGlancesInvalidPercent
|
|
||||||
}
|
|
||||||
// Test device name
|
|
||||||
if m.DeviceName != "" {
|
|
||||||
// Accept comma separated device names
|
|
||||||
devices := strings.Split(m.DeviceName, ",")
|
|
||||||
for _, d := range devices {
|
|
||||||
if !deviceNameRegexp.MatchString(d) {
|
|
||||||
return ErrInvalidDeviceName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// send sends the message using the pushover and the recipient tokens.
|
|
||||||
func (m *Glance) send(pToken, rToken string) (*Response, error) {
|
|
||||||
url := fmt.Sprintf("%s/glances.json", APIEndpoint)
|
|
||||||
|
|
||||||
params := map[string]string{
|
|
||||||
"token": pToken,
|
|
||||||
"user": rToken,
|
|
||||||
}
|
|
||||||
if m.DeviceName != "" {
|
|
||||||
params["device"] = m.DeviceName
|
|
||||||
}
|
|
||||||
|
|
||||||
// data
|
|
||||||
if m.Count != nil {
|
|
||||||
params["count"] = strconv.Itoa(*m.Count)
|
|
||||||
}
|
|
||||||
if m.Percent != nil {
|
|
||||||
params["percent"] = strconv.Itoa(*m.Percent)
|
|
||||||
}
|
|
||||||
if m.Title != nil {
|
|
||||||
params["title"] = *m.Title
|
|
||||||
}
|
|
||||||
if m.Text != nil {
|
|
||||||
params["text"] = *m.Text
|
|
||||||
}
|
|
||||||
if m.Subtext != nil {
|
|
||||||
params["subtext"] = *m.Subtext
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := newURLEncodedRequest("POST", url, params)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
resp := new(Response)
|
|
||||||
if err = do(req, resp, true); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
45
vendor/github.com/gregdel/pushover/helpers.go
generated
vendored
45
vendor/github.com/gregdel/pushover/helpers.go
generated
vendored
@@ -1,45 +0,0 @@
|
|||||||
package pushover
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Helper to unmarshal a timestamp as string to a time.Time.
|
|
||||||
type timestamp struct{ *time.Time }
|
|
||||||
|
|
||||||
func (t *timestamp) UnmarshalJSON(data []byte) error {
|
|
||||||
var i int64
|
|
||||||
if err := json.Unmarshal(data, &i); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if i > 0 {
|
|
||||||
unixTime := time.Unix(i, 0)
|
|
||||||
*t = timestamp{&unixTime}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper to unmarshal a int as a boolean.
|
|
||||||
type intBool bool
|
|
||||||
|
|
||||||
func (i *intBool) UnmarshalJSON(data []byte) error {
|
|
||||||
var v int64
|
|
||||||
if err := json.Unmarshal(data, &v); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch v {
|
|
||||||
case 0:
|
|
||||||
*i = false
|
|
||||||
case 1:
|
|
||||||
*i = true
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("failed to unmarshal int to bool")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
57
vendor/github.com/gregdel/pushover/limit.go
generated
vendored
57
vendor/github.com/gregdel/pushover/limit.go
generated
vendored
@@ -1,57 +0,0 @@
|
|||||||
package pushover
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Limit represents the limitation of the application. This information is
|
|
||||||
// fetched when posting a new message.
|
|
||||||
// Headers example:
|
|
||||||
// X-Limit-App-Limit: 7500
|
|
||||||
// X-Limit-App-Remaining: 7496
|
|
||||||
// X-Limit-App-Reset: 1393653600
|
|
||||||
type Limit struct {
|
|
||||||
// Total number of messages you can send during a month.
|
|
||||||
Total int
|
|
||||||
// Remaining number of messages you can send until the next reset.
|
|
||||||
Remaining int
|
|
||||||
// NextReset is the time when all the app counters will be reseted.
|
|
||||||
NextReset time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func newLimit(headers http.Header) (*Limit, error) {
|
|
||||||
headersStrings := []string{
|
|
||||||
"X-Limit-App-Limit",
|
|
||||||
"X-Limit-App-Remaining",
|
|
||||||
"X-Limit-App-Reset",
|
|
||||||
}
|
|
||||||
headersValues := map[string]int{}
|
|
||||||
|
|
||||||
for _, header := range headersStrings {
|
|
||||||
// Check if the header is present
|
|
||||||
h, ok := headers[header]
|
|
||||||
if !ok {
|
|
||||||
return nil, ErrInvalidHeaders
|
|
||||||
}
|
|
||||||
|
|
||||||
// The header must have only one element
|
|
||||||
if len(h) != 1 {
|
|
||||||
return nil, ErrInvalidHeaders
|
|
||||||
}
|
|
||||||
|
|
||||||
i, err := strconv.Atoi(h[0])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
headersValues[header] = i
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Limit{
|
|
||||||
Total: headersValues["X-Limit-App-Limit"],
|
|
||||||
Remaining: headersValues["X-Limit-App-Remaining"],
|
|
||||||
NextReset: time.Unix(int64(headersValues["X-Limit-App-Reset"]), 0),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
253
vendor/github.com/gregdel/pushover/message.go
generated
vendored
253
vendor/github.com/gregdel/pushover/message.go
generated
vendored
@@ -1,253 +0,0 @@
|
|||||||
package pushover
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"mime/multipart"
|
|
||||||
"net/http"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
"unicode/utf8"
|
|
||||||
)
|
|
||||||
|
|
||||||
var deviceNameRegexp *regexp.Regexp
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
deviceNameRegexp = regexp.MustCompile(`^[A-Za-z0-9_-]{1,25}$`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Message represents a pushover message.
|
|
||||||
type Message struct {
|
|
||||||
// Required
|
|
||||||
Message string
|
|
||||||
|
|
||||||
// Optional
|
|
||||||
Title string
|
|
||||||
Priority int
|
|
||||||
URL string
|
|
||||||
URLTitle string
|
|
||||||
Timestamp int64
|
|
||||||
Retry time.Duration
|
|
||||||
Expire time.Duration
|
|
||||||
CallbackURL string
|
|
||||||
DeviceName string
|
|
||||||
Sound string
|
|
||||||
HTML bool
|
|
||||||
Monospace bool
|
|
||||||
TTL time.Duration
|
|
||||||
|
|
||||||
// attachment
|
|
||||||
attachment io.Reader
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMessage returns a simple new message.
|
|
||||||
func NewMessage(message string) *Message {
|
|
||||||
return &Message{Message: message}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMessageWithTitle returns a simple new message with a title.
|
|
||||||
func NewMessageWithTitle(message, title string) *Message {
|
|
||||||
return &Message{Message: message, Title: title}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddAttachment adds an attachment to the message it's programmer's
|
|
||||||
// responsibility to close the reader.
|
|
||||||
func (m *Message) AddAttachment(attachment io.Reader) error {
|
|
||||||
m.attachment = attachment
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate the message values.
|
|
||||||
func (m *Message) validate() error {
|
|
||||||
// Message should no be empty
|
|
||||||
if m.Message == "" {
|
|
||||||
return ErrMessageEmpty
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate message length
|
|
||||||
if utf8.RuneCountInString(m.Message) > MessageMaxLength {
|
|
||||||
return ErrMessageTooLong
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate Title field length
|
|
||||||
if utf8.RuneCountInString(m.Title) > MessageTitleMaxLength {
|
|
||||||
return ErrMessageTitleTooLong
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate URL field
|
|
||||||
if utf8.RuneCountInString(m.URL) > MessageURLMaxLength {
|
|
||||||
return ErrMessageURLTooLong
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate URL title field
|
|
||||||
if utf8.RuneCountInString(m.URLTitle) > MessageURLTitleMaxLength {
|
|
||||||
return ErrMessageURLTitleTooLong
|
|
||||||
}
|
|
||||||
|
|
||||||
// URLTitle should not be set with an empty URL
|
|
||||||
if m.URL == "" && m.URLTitle != "" {
|
|
||||||
return ErrEmptyURL
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate priorities
|
|
||||||
if m.Priority > PriorityEmergency || m.Priority < PriorityLowest {
|
|
||||||
return ErrInvalidPriority
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate emergency priority
|
|
||||||
if m.Priority == PriorityEmergency {
|
|
||||||
if m.Retry == 0 || m.Expire == 0 {
|
|
||||||
return ErrMissingEmergencyParameter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test device name
|
|
||||||
if m.DeviceName != "" {
|
|
||||||
// Accept comma separated device names
|
|
||||||
devices := strings.Split(m.DeviceName, ",")
|
|
||||||
for _, d := range devices {
|
|
||||||
if !deviceNameRegexp.MatchString(d) {
|
|
||||||
return ErrInvalidDeviceName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return a map filled with the relevant data.
|
|
||||||
func (m *Message) toMap(pToken, rToken string) map[string]string {
|
|
||||||
ret := map[string]string{
|
|
||||||
"token": pToken,
|
|
||||||
"user": rToken,
|
|
||||||
"message": m.Message,
|
|
||||||
"priority": strconv.Itoa(m.Priority),
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.Title != "" {
|
|
||||||
ret["title"] = m.Title
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.URL != "" {
|
|
||||||
ret["url"] = m.URL
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.URLTitle != "" {
|
|
||||||
ret["url_title"] = m.URLTitle
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.Sound != "" {
|
|
||||||
ret["sound"] = m.Sound
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.DeviceName != "" {
|
|
||||||
ret["device"] = m.DeviceName
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.Timestamp != 0 {
|
|
||||||
ret["timestamp"] = strconv.FormatInt(m.Timestamp, 10)
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.HTML {
|
|
||||||
ret["html"] = "1"
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.Monospace {
|
|
||||||
ret["monospace"] = "1"
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.Priority == PriorityEmergency {
|
|
||||||
ret["retry"] = strconv.FormatFloat(m.Retry.Seconds(), 'f', -1, 64)
|
|
||||||
ret["expire"] = strconv.FormatFloat(m.Expire.Seconds(), 'f', -1, 64)
|
|
||||||
if m.CallbackURL != "" {
|
|
||||||
ret["callback"] = m.CallbackURL
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.TTL != 0 {
|
|
||||||
ret["ttl"] = strconv.FormatFloat(m.TTL.Seconds(), 'f', -1, 64)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send sends the message using the pushover and the recipient tokens.
|
|
||||||
func (m *Message) send(pToken, rToken string) (*Response, error) {
|
|
||||||
url := fmt.Sprintf("%s/messages.json", APIEndpoint)
|
|
||||||
|
|
||||||
var f func(string, string, string) (*http.Request, error)
|
|
||||||
if m.attachment == nil {
|
|
||||||
// Use a URL-encoded request if there's no need to attach files
|
|
||||||
f = m.urlEncodedRequest
|
|
||||||
} else {
|
|
||||||
// Use a multipart request if a file should be sent
|
|
||||||
f = m.multipartRequest
|
|
||||||
}
|
|
||||||
|
|
||||||
// Post the from and check the headers of the response
|
|
||||||
req, err := f(pToken, rToken, url)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
resp := &Response{}
|
|
||||||
if err := do(req, resp, true); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// multipartRequest returns a new multipart POST request with a file attached.
|
|
||||||
func (m *Message) multipartRequest(pToken, rToken, url string) (*http.Request, error) {
|
|
||||||
body := &bytes.Buffer{}
|
|
||||||
|
|
||||||
if m.attachment == nil {
|
|
||||||
return nil, ErrMissingAttachment
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the body as multipart form data
|
|
||||||
w := multipart.NewWriter(body)
|
|
||||||
|
|
||||||
// Write the file in the body
|
|
||||||
fw, err := w.CreateFormFile("attachment", "attachment")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
written, err := io.Copy(fw, m.attachment)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if written > MessageMaxAttachmentByte {
|
|
||||||
return nil, ErrMessageAttachmentTooLarge
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle params
|
|
||||||
for k, v := range m.toMap(pToken, rToken) {
|
|
||||||
if err := w.WriteField(k, v); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := w.Close(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := http.NewRequest("POST", url, body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
req.Header.Set("Content-Type", w.FormDataContentType())
|
|
||||||
|
|
||||||
return req, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// urlEncodedRequest returns a new url encoded request.
|
|
||||||
func (m *Message) urlEncodedRequest(pToken, rToken, endpoint string) (*http.Request, error) {
|
|
||||||
return newURLEncodedRequest("POST", endpoint, m.toMap(pToken, rToken))
|
|
||||||
}
|
|
||||||
236
vendor/github.com/gregdel/pushover/pushover.go
generated
vendored
236
vendor/github.com/gregdel/pushover/pushover.go
generated
vendored
@@ -1,236 +0,0 @@
|
|||||||
// Package pushover provides a wrapper around the Pushover API
|
|
||||||
package pushover
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"regexp"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Regexp validation.
|
|
||||||
var tokenRegexp *regexp.Regexp
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
tokenRegexp = regexp.MustCompile(`^[A-Za-z0-9]{30}$`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// APIEndpoint is the API base URL for any request.
|
|
||||||
var APIEndpoint = "https://api.pushover.net/1"
|
|
||||||
|
|
||||||
// Pushover custom errors.
|
|
||||||
var (
|
|
||||||
ErrHTTPPushover = errors.New("pushover: http error")
|
|
||||||
ErrEmptyToken = errors.New("pushover: empty API token")
|
|
||||||
ErrEmptyURL = errors.New("pushover: empty URL, URLTitle needs an URL")
|
|
||||||
ErrEmptyRecipientToken = errors.New("pushover: empty recipient token")
|
|
||||||
ErrInvalidRecipientToken = errors.New("pushover: invalid recipient token")
|
|
||||||
ErrInvalidHeaders = errors.New("pushover: invalid headers in server response")
|
|
||||||
ErrInvalidPriority = errors.New("pushover: invalid priority")
|
|
||||||
ErrInvalidToken = errors.New("pushover: invalid API token")
|
|
||||||
ErrMessageEmpty = errors.New("pushover: message empty")
|
|
||||||
ErrMessageTitleTooLong = errors.New("pushover: message title too long")
|
|
||||||
ErrMessageTooLong = errors.New("pushover: message too long")
|
|
||||||
ErrMessageAttachmentTooLarge = errors.New("pushover: message attachment is too large")
|
|
||||||
ErrMessageURLTitleTooLong = errors.New("pushover: message URL title too long")
|
|
||||||
ErrMessageURLTooLong = errors.New("pushover: message URL too long")
|
|
||||||
ErrMissingAttachment = errors.New("pushover: missing attachment")
|
|
||||||
ErrMissingEmergencyParameter = errors.New("pushover: missing emergency parameter")
|
|
||||||
ErrInvalidDeviceName = errors.New("pushover: invalid device name")
|
|
||||||
ErrEmptyReceipt = errors.New("pushover: empty receipt")
|
|
||||||
ErrGlancesMissingData = errors.New("pushover: glance update data missing")
|
|
||||||
ErrGlancesTitleTooLong = errors.New("pushover: glance title too long")
|
|
||||||
ErrGlancesTextTooLong = errors.New("pushover: glance text too long")
|
|
||||||
ErrGlancesSubtextTooLong = errors.New("pushover: glance subtext too long")
|
|
||||||
ErrGlancesInvalidPercent = errors.New("pushover: glance percent must be in range of 0-100")
|
|
||||||
)
|
|
||||||
|
|
||||||
// API limitations.
|
|
||||||
const (
|
|
||||||
// MessageMaxLength is the max message number of characters.
|
|
||||||
MessageMaxLength = 1024
|
|
||||||
// MessageTitleMaxLength is the max title number of characters.
|
|
||||||
MessageTitleMaxLength = 250
|
|
||||||
// MessageURLMaxLength is the max URL number of characters.
|
|
||||||
MessageURLMaxLength = 512
|
|
||||||
// MessageURLTitleMaxLength is the max URL title number of characters.
|
|
||||||
MessageURLTitleMaxLength = 100
|
|
||||||
// MessageMaxAttachmentByte is the max attachment size in byte.
|
|
||||||
MessageMaxAttachmentByte = 2621440
|
|
||||||
)
|
|
||||||
|
|
||||||
// Message priorities
|
|
||||||
const (
|
|
||||||
PriorityLowest = -2
|
|
||||||
PriorityLow = -1
|
|
||||||
PriorityNormal = 0
|
|
||||||
PriorityHigh = 1
|
|
||||||
PriorityEmergency = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
// Sounds
|
|
||||||
const (
|
|
||||||
SoundPushover = "pushover"
|
|
||||||
SoundBike = "bike"
|
|
||||||
SoundBugle = "bugle"
|
|
||||||
SoundCashRegister = "cashregister"
|
|
||||||
SoundClassical = "classical"
|
|
||||||
SoundCosmic = "cosmic"
|
|
||||||
SoundFalling = "falling"
|
|
||||||
SoundGamelan = "gamelan"
|
|
||||||
SoundIncoming = "incoming"
|
|
||||||
SoundIntermission = "intermission"
|
|
||||||
SoundMagic = "magic"
|
|
||||||
SoundMechanical = "mechanical"
|
|
||||||
SoundPianobar = "pianobar"
|
|
||||||
SoundSiren = "siren"
|
|
||||||
SoundSpaceAlarm = "spacealarm"
|
|
||||||
SoundTugBoat = "tugboat"
|
|
||||||
SoundAlien = "alien"
|
|
||||||
SoundClimb = "climb"
|
|
||||||
SoundPersistent = "persistent"
|
|
||||||
SoundEcho = "echo"
|
|
||||||
SoundUpDown = "updown"
|
|
||||||
SoundVibrate = "vibrate"
|
|
||||||
SoundNone = "none"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Pushover is the representation of an app using the pushover API.
|
|
||||||
type Pushover struct {
|
|
||||||
token string
|
|
||||||
}
|
|
||||||
|
|
||||||
// New returns a new app to talk to the pushover API.
|
|
||||||
func New(token string) *Pushover {
|
|
||||||
return &Pushover{token}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate Pushover token.
|
|
||||||
func (p *Pushover) validate() error {
|
|
||||||
// Check empty token
|
|
||||||
if p.token == "" {
|
|
||||||
return ErrEmptyToken
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check invalid token
|
|
||||||
if !tokenRegexp.MatchString(p.token) {
|
|
||||||
return ErrInvalidToken
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendMessage is used to send message to a recipient.
|
|
||||||
func (p *Pushover) SendMessage(message *Message, recipient *Recipient) (*Response, error) {
|
|
||||||
// Validate pushover
|
|
||||||
if err := p.validate(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate recipient
|
|
||||||
if err := recipient.validate(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate message
|
|
||||||
if err := message.validate(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return message.send(p.token, recipient.token)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendGlanceUpdate is used to send glance updates to a recipient.
|
|
||||||
// It can be used to display widgets on a smart watch
|
|
||||||
func (p *Pushover) SendGlanceUpdate(msg *Glance, rec *Recipient) (*Response, error) {
|
|
||||||
// Validate pushover
|
|
||||||
if err := p.validate(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate rec
|
|
||||||
if err := rec.validate(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate msg
|
|
||||||
if err := msg.validate(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return msg.send(p.token, rec.token)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetReceiptDetails return detailed information about a receipt. This is used
|
|
||||||
// used to check the acknowledged status of an Emergency notification.
|
|
||||||
func (p *Pushover) GetReceiptDetails(receipt string) (*ReceiptDetails, error) {
|
|
||||||
url := fmt.Sprintf("%s/receipts/%s.json?token=%s", APIEndpoint, receipt, p.token)
|
|
||||||
|
|
||||||
if receipt == "" {
|
|
||||||
return nil, ErrEmptyReceipt
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send request
|
|
||||||
resp, err := http.Get(url)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode the JSON response
|
|
||||||
var details *ReceiptDetails
|
|
||||||
if err = json.NewDecoder(resp.Body).Decode(&details); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return details, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRecipientDetails allows to check if a recipient exists, if it's a group
|
|
||||||
// and the devices associated to this recipient. The Errors field of the
|
|
||||||
// RecipientDetails object will contain an error if the recipient is not valid
|
|
||||||
// in the Pushover API.
|
|
||||||
func (p *Pushover) GetRecipientDetails(recipient *Recipient) (*RecipientDetails, error) {
|
|
||||||
endpoint := fmt.Sprintf("%s/users/validate.json", APIEndpoint)
|
|
||||||
|
|
||||||
// Validate pushover
|
|
||||||
if err := p.validate(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate recipient
|
|
||||||
if err := recipient.validate(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := newURLEncodedRequest("POST", endpoint,
|
|
||||||
map[string]string{"token": p.token, "user": recipient.token})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var response RecipientDetails
|
|
||||||
if err := do(req, &response, false); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &response, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CancelEmergencyNotification helps stop a notification retry in case of a
|
|
||||||
// notification with an Emergency priority before reaching the expiration time.
|
|
||||||
// It requires the response receipt in order to stop the right notification.
|
|
||||||
func (p *Pushover) CancelEmergencyNotification(receipt string) (*Response, error) {
|
|
||||||
endpoint := fmt.Sprintf("%s/receipts/%s/cancel.json", APIEndpoint, receipt)
|
|
||||||
|
|
||||||
req, err := newURLEncodedRequest("POST", endpoint, map[string]string{"token": p.token})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
response := &Response{}
|
|
||||||
if err := do(req, response, false); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return response, nil
|
|
||||||
}
|
|
||||||
59
vendor/github.com/gregdel/pushover/receipt_details.go
generated
vendored
59
vendor/github.com/gregdel/pushover/receipt_details.go
generated
vendored
@@ -1,59 +0,0 @@
|
|||||||
package pushover
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ReceiptDetails represents the receipt informations in case of emergency
|
|
||||||
// priority.
|
|
||||||
type ReceiptDetails struct {
|
|
||||||
Status int
|
|
||||||
Acknowledged bool
|
|
||||||
AcknowledgedBy string
|
|
||||||
Expired bool
|
|
||||||
CalledBack bool
|
|
||||||
ID string
|
|
||||||
AcknowledgedAt *time.Time
|
|
||||||
LastDeliveredAt *time.Time
|
|
||||||
ExpiresAt *time.Time
|
|
||||||
CalledBackAt *time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON is a custom unmarshal function to handle timestamps and
|
|
||||||
// boolean as int and convert them to the right type.
|
|
||||||
func (r *ReceiptDetails) UnmarshalJSON(data []byte) error {
|
|
||||||
dataBytes := bytes.NewReader(data)
|
|
||||||
var aux struct {
|
|
||||||
ID string `json:"request"`
|
|
||||||
Status int `json:"status"`
|
|
||||||
Acknowledged intBool `json:"acknowledged"`
|
|
||||||
AcknowledgedBy string `json:"acknowledged_by"`
|
|
||||||
Expired intBool `json:"expired"`
|
|
||||||
CalledBack intBool `json:"called_back"`
|
|
||||||
AcknowledgedAt *timestamp `json:"acknowledged_at"`
|
|
||||||
LastDeliveredAt *timestamp `json:"last_delivered_at"`
|
|
||||||
ExpiresAt *timestamp `json:"expires_at"`
|
|
||||||
CalledBackAt *timestamp `json:"called_back_at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode json into the aux struct
|
|
||||||
if err := json.NewDecoder(dataBytes).Decode(&aux); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the RecipientDetails with the right types
|
|
||||||
r.Status = aux.Status
|
|
||||||
r.Acknowledged = bool(aux.Acknowledged)
|
|
||||||
r.AcknowledgedBy = aux.AcknowledgedBy
|
|
||||||
r.Expired = bool(aux.Expired)
|
|
||||||
r.CalledBack = bool(aux.CalledBack)
|
|
||||||
r.ID = aux.ID
|
|
||||||
r.AcknowledgedAt = aux.AcknowledgedAt.Time
|
|
||||||
r.LastDeliveredAt = aux.LastDeliveredAt.Time
|
|
||||||
r.ExpiresAt = aux.ExpiresAt.Time
|
|
||||||
r.CalledBackAt = aux.CalledBackAt.Time
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
43
vendor/github.com/gregdel/pushover/recipient.go
generated
vendored
43
vendor/github.com/gregdel/pushover/recipient.go
generated
vendored
@@ -1,43 +0,0 @@
|
|||||||
package pushover
|
|
||||||
|
|
||||||
import "regexp"
|
|
||||||
|
|
||||||
var recipientRegexp *regexp.Regexp
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
recipientRegexp = regexp.MustCompile(`^[A-Za-z0-9]{30}$`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recipient represents the a recipient to notify.
|
|
||||||
type Recipient struct {
|
|
||||||
token string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewRecipient is the representation of the recipient to notify.
|
|
||||||
func NewRecipient(token string) *Recipient {
|
|
||||||
return &Recipient{token}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validates recipient token.
|
|
||||||
func (r *Recipient) validate() error {
|
|
||||||
// Check empty token
|
|
||||||
if r.token == "" {
|
|
||||||
return ErrEmptyRecipientToken
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check invalid token
|
|
||||||
if !recipientRegexp.MatchString(r.token) {
|
|
||||||
return ErrInvalidRecipientToken
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RecipientDetails represents the receipt informations in case of emergency
|
|
||||||
// priority.
|
|
||||||
type RecipientDetails struct {
|
|
||||||
Status int `json:"status"`
|
|
||||||
Group int `json:"group"`
|
|
||||||
Devices []string `json:"devices"`
|
|
||||||
RequestID string `json:"request"`
|
|
||||||
Errors Errors `json:"errors"`
|
|
||||||
}
|
|
||||||
69
vendor/github.com/gregdel/pushover/request.go
generated
vendored
69
vendor/github.com/gregdel/pushover/request.go
generated
vendored
@@ -1,69 +0,0 @@
|
|||||||
package pushover
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// do is a generic function to send a request to the API.
|
|
||||||
func do(req *http.Request, resType interface{}, returnHeaders bool) error {
|
|
||||||
client := http.DefaultClient
|
|
||||||
|
|
||||||
// Send request
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
// Only 500 errors will not respond a readable result
|
|
||||||
if resp.StatusCode >= http.StatusInternalServerError {
|
|
||||||
return ErrHTTPPushover
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode the JSON response
|
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&resType); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the unmarshaled data is a response
|
|
||||||
r, ok := resType.(*Response)
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check response status
|
|
||||||
if r.Status != 1 {
|
|
||||||
return r.Errors
|
|
||||||
}
|
|
||||||
|
|
||||||
// The headers are only returned when posting a new notification
|
|
||||||
if returnHeaders {
|
|
||||||
// Get app limits from headers
|
|
||||||
appLimits, err := newLimit(resp.Header)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.Limit = appLimits
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// urlEncodedRequest returns a new url encoded request.
|
|
||||||
func newURLEncodedRequest(method, endpoint string, params map[string]string) (*http.Request, error) {
|
|
||||||
urlValues := url.Values{}
|
|
||||||
for k, v := range params {
|
|
||||||
urlValues.Add(k, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := http.NewRequest(method, endpoint, strings.NewReader(urlValues.Encode()))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
||||||
|
|
||||||
return req, nil
|
|
||||||
}
|
|
||||||
26
vendor/github.com/gregdel/pushover/response.go
generated
vendored
26
vendor/github.com/gregdel/pushover/response.go
generated
vendored
@@ -1,26 +0,0 @@
|
|||||||
package pushover
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
// Response represents a response from the API.
|
|
||||||
type Response struct {
|
|
||||||
Status int `json:"status"`
|
|
||||||
ID string `json:"request"`
|
|
||||||
Errors Errors `json:"errors"`
|
|
||||||
Receipt string `json:"receipt"`
|
|
||||||
Limit *Limit
|
|
||||||
}
|
|
||||||
|
|
||||||
// String represents a printable form of the response.
|
|
||||||
func (r Response) String() string {
|
|
||||||
ret := fmt.Sprintf("Status: %d\n", r.Status)
|
|
||||||
ret += fmt.Sprintf("Request id: %s\n", r.ID)
|
|
||||||
if r.Receipt != "" {
|
|
||||||
ret += fmt.Sprintf("Receipt: %s\n", r.Receipt)
|
|
||||||
}
|
|
||||||
if r.Limit != nil {
|
|
||||||
ret += fmt.Sprintf("Usage %d/%d messages\nNext reset : %s",
|
|
||||||
r.Limit.Remaining, r.Limit.Total, r.Limit.NextReset)
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
3
vendor/modules.txt
vendored
3
vendor/modules.txt
vendored
@@ -278,9 +278,6 @@ github.com/gorilla/mux
|
|||||||
# github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674
|
# github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674
|
||||||
## explicit; go 1.20
|
## explicit; go 1.20
|
||||||
github.com/gorilla/websocket
|
github.com/gorilla/websocket
|
||||||
# github.com/gregdel/pushover v1.3.1
|
|
||||||
## explicit; go 1.14
|
|
||||||
github.com/gregdel/pushover
|
|
||||||
# github.com/hashicorp/cronexpr v1.1.2
|
# github.com/hashicorp/cronexpr v1.1.2
|
||||||
## explicit
|
## explicit
|
||||||
github.com/hashicorp/cronexpr
|
github.com/hashicorp/cronexpr
|
||||||
|
|||||||
Reference in New Issue
Block a user