diff --git a/README.md b/README.md index 27fe483..bb2f704 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,26 @@ You can retrieve the token using [pbcli](https://github.com/PushBits/cli) by run pbcli application show $PB_APPLICATION --url https://pushbits.example.com --username $PB_USERNAME ``` +### Message options + +Messages are supporting thre different syntaxes: + +* text/plain +* text/html +* text/markdown + +To set a specific syntax you need to set the `extras`: + +```bash +curl \ + --header "Content-Type: application/json" \ + --request POST \ + --data '{"message":"my message with\n\n**Markdown** _support_.","title":"my title","extras":{"client::display":{"contentType": "text/html"}}}' \ + "https://pushbits.example.com/message?token=$PB_TOKEN" +``` + +HTML-Content might not be fully rendered in your Matrix-Client - see the corresponding [Matrix specs](https://spec.matrix.org/unstable/client-server-api/#mroommessage-msgtypes). This also holds for Markdown, as it is transfered to the corresponding HTML-syntax. + ## Acknowledgments The idea for this software and most parts of the initial source are heavily inspired by [Gotify](https://gotify.net/). diff --git a/cmd/pushbits/main.go b/cmd/pushbits/main.go index 66bcaae..89d9f87 100644 --- a/cmd/pushbits/main.go +++ b/cmd/pushbits/main.go @@ -47,7 +47,7 @@ func main() { log.Fatal(err) } - dp, err := dispatcher.Create(db, c.Matrix.Homeserver, c.Matrix.Username, c.Matrix.Password) + dp, err := dispatcher.Create(db, c.Matrix.Homeserver, c.Matrix.Username, c.Matrix.Password, c.Message) if err != nil { log.Fatal(err) } diff --git a/config.example.yml b/config.example.yml index f67ed72..057489d 100644 --- a/config.example.yml +++ b/config.example.yml @@ -56,3 +56,7 @@ crypto: parallelism: 4 saltlength: 16 keylength: 32 + +message: + # add coloring to the title according to syslog priorities + # coloredtitle: true diff --git a/internal/api/notification.go b/internal/api/notification.go index bfb8af0..52714b7 100644 --- a/internal/api/notification.go +++ b/internal/api/notification.go @@ -30,6 +30,7 @@ type NotificationHandler struct { // CreateNotification is used to create a new notification for a user. func (h *NotificationHandler) CreateNotification(ctx *gin.Context) { var notification model.Notification + notification.Priority = 8 // set a default value if err := ctx.Bind(¬ification); err != nil { return diff --git a/internal/configuration/configuration.go b/internal/configuration/configuration.go index b74e7f9..496036b 100644 --- a/internal/configuration/configuration.go +++ b/internal/configuration/configuration.go @@ -42,7 +42,8 @@ type Configuration struct { Security struct { CheckHIBP bool `default:"false"` } - Crypto CryptoConfig + Crypto CryptoConfig + Message map[string]interface{} } func configFiles() []string { diff --git a/internal/dispatcher/dispatcher.go b/internal/dispatcher/dispatcher.go index 631a5f6..c4c7e5e 100644 --- a/internal/dispatcher/dispatcher.go +++ b/internal/dispatcher/dispatcher.go @@ -16,12 +16,13 @@ type Database interface { // Dispatcher holds information for sending notifications to clients. type Dispatcher struct { - db Database - client *gomatrix.Client + db Database + client *gomatrix.Client + settings map[string]interface{} } // Create instanciates a dispatcher connection. -func Create(db Database, homeserver, username, password string) (*Dispatcher, error) { +func Create(db Database, homeserver, username, password string, settings map[string]interface{}) (*Dispatcher, error) { log.Println("Setting up dispatcher.") client, err := gomatrix.NewClient(homeserver, "", "") @@ -40,7 +41,7 @@ func Create(db Database, homeserver, username, password string) (*Dispatcher, er client.SetCredentials(response.UserID, response.AccessToken) - return &Dispatcher{client: client}, nil + return &Dispatcher{client: client, settings: settings}, nil } // Close closes the dispatcher connection. diff --git a/internal/dispatcher/notification.go b/internal/dispatcher/notification.go index ba7f612..1ddaeb0 100644 --- a/internal/dispatcher/notification.go +++ b/internal/dispatcher/notification.go @@ -14,10 +14,39 @@ import ( func (d *Dispatcher) SendNotification(a *model.Application, n *model.Notification) error { log.Printf("Sending notification to room %s.", a.MatrixID) - plainTitle := strings.TrimSpace(n.Title) plainMessage := strings.TrimSpace(n.Message) - escapedTitle := html.EscapeString(plainTitle) - message := html.EscapeString(plainMessage) // default to text/plain + plainTitle := strings.TrimSpace(n.Title) + message := d.getFormattedMessage(n) + title := d.getFormattedTitle(n) + + text := fmt.Sprintf("%s\n\n%s", plainTitle, plainMessage) + formattedText := fmt.Sprintf("%s %s", title, message) + + _, err := d.client.SendFormattedText(a.MatrixID, text, formattedText) + + return err +} + +// HTML-formats the title +func (d *Dispatcher) getFormattedTitle(n *model.Notification) string { + trimmedTitle := strings.TrimSpace(n.Title) + title := html.EscapeString(trimmedTitle) + + if valueRaw, ok := d.settings["coloredtitle"]; ok { + value, ok := valueRaw.(bool) + + if ok && value { + title = d.coloredText(d.priorityToColor(n.Priority), title) + } + } + + return "" + title + "

" +} + +// Converts different syntaxes to a HTML-formatted message +func (d *Dispatcher) getFormattedMessage(n *model.Notification) string { + trimmedMessage := strings.TrimSpace(n.Message) + message := strings.Replace(html.EscapeString(trimmedMessage), "\n", "
", -1) // default to text/plain if optionsDisplayRaw, ok := n.Extras["client::display"]; ok { optionsDisplay, ok := optionsDisplayRaw.(map[string]interface{}) @@ -30,25 +59,47 @@ func (d *Dispatcher) SendNotification(a *model.Application, n *model.Notificatio switch contentType { case "html", "text/html": - message = plainMessage + message = strings.Replace(trimmedMessage, "\n", "
", -1) case "markdown", "md", "text/md", "text/markdown": - message = string(markdown.ToHTML([]byte(plainMessage), nil, nil)) + // allow HTML in Markdown + message = string(markdown.ToHTML([]byte(trimmedMessage), nil, nil)) } } } } - // TODO cubicroot: add colors for priority https://spec.matrix.org/unstable/client-server-api/#mroommessage-msgtypes - // maybe make this optional in the settings or so - - // TODO cubicroot: check if we somehow can handle \n or other methods of line breaks - - // TODO cubicroot: add docu - - text := fmt.Sprintf("%s\n\n%s", plainTitle, plainMessage) - formattedText := fmt.Sprintf("%s

%s", escapedTitle, message) - - _, err := d.client.SendFormattedText(a.MatrixID, text, formattedText) - - return err + return message +} + +// Maps priorities to hex colors +func (d *Dispatcher) priorityToColor(prio int) string { + switch prio { + case 0: // emergency - dark red + return "#cc0000" + case 1: // alert - red + return "#ed1f11" + case 2: // critical - dark orange + return "#ed6d11" + case 3: // error - orange + return "#edab11" + case 4: // warning - yellow + return "#edd711" + case 5: // notice - green + return "#70ed11" + case 6: // informational - blue + return "#118eed" + case 7: // debug - grey + return "#828282" + } + + return "" +} + +// Maps a priority to a color tag +func (d *Dispatcher) coloredText(color string, text string) string { + if color == "" { + return text + } + + return "" + text + "" }