diff --git a/README.md b/README.md
index 8bb3256..48b7570 100644
--- a/README.md
+++ b/README.md
@@ -142,6 +142,18 @@ curl \
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.
+### Deleting a Message
+
+You can delete a message, this will send a notification in response to the original message informing you that the message is "deleted".
+
+You need the message ID for deleting a message. As it might contain characters not valid in uris we provide an additional `id_url_encoded` field for messages, use that value for deleting a message.
+
+```bash
+curl \
+ --request DELETE \
+ "https://pushbits.example.com/message/${MESSAGE_ID}?token=$PB_TOKEN"
+```
+
## 👮 Acknowledgments
The idea for this software and most parts of the initial source are heavily inspired by [Gotify](https://gotify.net/).
diff --git a/internal/api/errors.go b/internal/api/errors.go
new file mode 100644
index 0000000..db296e3
--- /dev/null
+++ b/internal/api/errors.go
@@ -0,0 +1,5 @@
+package api
+
+import "errors"
+
+var ErrorMessageNotFound = errors.New("Message not found")
diff --git a/internal/api/notification.go b/internal/api/notification.go
index 9cd51d0..064c716 100644
--- a/internal/api/notification.go
+++ b/internal/api/notification.go
@@ -3,6 +3,7 @@ package api
import (
"log"
"net/http"
+ "net/url"
"strings"
"time"
@@ -52,6 +53,7 @@ func (h *NotificationHandler) CreateNotification(ctx *gin.Context) {
}
notification.ID = messageID
+ notification.UrlEncodedID = url.QueryEscape(messageID)
ctx.JSON(http.StatusOK, ¬ification)
}
@@ -70,5 +72,9 @@ func (h *NotificationHandler) DeleteNotification(ctx *gin.Context) {
Date: time.Now(),
}
- h.DP.DeleteNotification(application, &n)
+ if success := successOrAbort(ctx, http.StatusInternalServerError, h.DP.DeleteNotification(application, &n)); !success {
+ return
+ }
+
+ ctx.Status(http.StatusOK)
}
diff --git a/internal/api/util.go b/internal/api/util.go
index 177f537..d045139 100644
--- a/internal/api/util.go
+++ b/internal/api/util.go
@@ -11,7 +11,13 @@ import (
func successOrAbort(ctx *gin.Context, code int, err error) bool {
if err != nil {
- ctx.AbortWithError(code, err)
+ // If we know the error force error code
+ switch err {
+ case ErrorMessageNotFound:
+ ctx.AbortWithError(http.StatusNotFound, err)
+ default:
+ ctx.AbortWithError(code, err)
+ }
}
return err == nil
diff --git a/internal/dispatcher/notification.go b/internal/dispatcher/notification.go
index edc9c97..c17c634 100644
--- a/internal/dispatcher/notification.go
+++ b/internal/dispatcher/notification.go
@@ -1,19 +1,30 @@
package dispatcher
import (
+ "errors"
"fmt"
"html"
"log"
"strings"
"github.com/gomarkdown/markdown"
+ "github.com/matrix-org/gomatrix"
+ "github.com/pushbits/server/internal/api"
"github.com/pushbits/server/internal/model"
)
+type MessageFormat string
+
+const (
+ FormatHTML = MessageFormat("org.matrix.custom.html")
+)
+
type ReplyEvent struct {
- Body string `json:"body"`
- Msgtype string `json:"msgtype"`
- RelatesTo RelatesTo `json:"m.relates_to,omitempty"`
+ Body string `json:"body"`
+ FormattedBody string `json:"formatted_body"`
+ Msgtype string `json:"msgtype"`
+ RelatesTo RelatesTo `json:"m.relates_to,omitempty"`
+ Format MessageFormat `json:"format"`
}
type RelatesTo struct {
@@ -39,30 +50,56 @@ func (d *Dispatcher) SendNotification(a *model.Application, n *model.Notificatio
// DeleteNotification sends a notification to the specified user that another notificaion is deleted
func (d *Dispatcher) DeleteNotification(a *model.Application, n *model.DeleteNotification) error {
+ var deleteMessageFormattedBody string
+ var deleteMessageBody string
log.Printf("Sending delete notification to room %s", a.MatrixID)
+
+ deleteMessage, err := d.getMessage(a, n.ID)
+
+ if err != nil {
+ log.Println(err)
+ return api.ErrorMessageNotFound
+ }
+
+ if val, ok := deleteMessage.Content["body"]; ok {
+ body, ok := val.(string)
+ if ok {
+ deleteMessageBody = body
+ deleteMessageFormattedBody = body
+ }
+ } else {
+ log.Println("Message to delete has wrong format")
+ return api.ErrorMessageNotFound
+ }
+
+ if val, ok := deleteMessage.Content["formatted_body"]; ok {
+ body, ok := val.(string)
+ if ok {
+ deleteMessageFormattedBody = body
+ }
+ }
+
+ // formatting according to https://matrix.org/docs/spec/client_server/latest#fallbacks-and-event-representation
+ formattedBody := fmt.Sprintf("In reply to %s
%s
\nThis message got deleted.", deleteMessage.RoomID, deleteMessage.ID, deleteMessage.Sender, deleteMessage.Sender, deleteMessageFormattedBody)
+ body := fmt.Sprintf("> <%s>%s\n\nThis message got deleted", deleteMessage.Sender, deleteMessageBody)
+
event := ReplyEvent{
- Body: "This message got deleted.",
- Msgtype: "m.text",
+ FormattedBody: formattedBody,
+ Body: body,
+ Msgtype: "m.text",
+ Format: FormatHTML,
}
irt := make(map[string]string)
- irt["event_id"] = n.ID
rt := RelatesTo{
InReplyTo: irt,
}
event.RelatesTo = rt
+ irt["event_id"] = deleteMessage.ID
- _, err := d.client.SendMessageEvent(a.MatrixID, "m.room.message", event)
+ _, err = d.client.SendMessageEvent(a.MatrixID, "m.room.message", event)
return err
-
- /*
- messages, _ := d.client.Messages(a.MatrixID, "", "", 'b', 10)
-
- js, _ := json.Marshal(messages)
-
- log.Println(string(js))
- */
}
// HTML-formats the title
@@ -130,3 +167,21 @@ func (d *Dispatcher) coloredText(color string, text string) string {
return "" + text + ""
}
+
+// TODO cubicroot: find a way to only request on specific event
+func (d *Dispatcher) getMessage(a *model.Application, id string) (gomatrix.Event, error) {
+ start := ""
+ end := ""
+ maxPages := 10 // maximum pages to request (10 messages per page)
+
+ for i := 0; i < maxPages; i++ {
+ messages, _ := d.client.Messages(a.MatrixID, start, end, 'b', 10)
+ for _, event := range messages.Chunk {
+ if event.ID == id {
+ return event, nil
+ }
+ }
+ start = messages.End
+ }
+ return gomatrix.Event{}, errors.New("Message not found")
+}
diff --git a/internal/model/notification.go b/internal/model/notification.go
index 7bb64b7..569e8ee 100644
--- a/internal/model/notification.go
+++ b/internal/model/notification.go
@@ -7,6 +7,7 @@ import (
// Notification holds information like the message, the title, and the priority of a notification.
type Notification struct {
ID string `json:"id"`
+ UrlEncodedID string `json:"id_url_encoded"`
ApplicationID uint `json:"appid"`
Message string `json:"message" form:"message" query:"message" binding:"required"`
Title string `json:"title" form:"title" query:"title"`
diff --git a/internal/router/router.go b/internal/router/router.go
index 1debc4a..6392973 100644
--- a/internal/router/router.go
+++ b/internal/router/router.go
@@ -46,7 +46,7 @@ func Create(debug bool, cm *credentials.Manager, db *database.Database, dp *disp
r.GET("/health", healthHandler.Health)
r.POST("/message", auth.RequireApplicationToken(), notificationHandler.CreateNotification)
- r.DELETE("/message/:messageid", api.RequireIDInURI(), auth.RequireApplicationToken(), notificationHandler.DeleteNotification)
+ r.DELETE("/message/:messageid", api.RequireMessageIDInURI(), auth.RequireApplicationToken(), notificationHandler.DeleteNotification)
r.GET("/test", auth.RequireApplicationToken(), notificationHandler.DeleteNotification)