meditime/vendor/github.com/gregdel/pushover/message.go

249 lines
5.2 KiB
Go
Raw Normal View History

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
// 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
}
}
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))
}