From 5be204dc19ae7ea2db74eeb18ec5194e4dadf834 Mon Sep 17 00:00:00 2001 From: Cubicroot Date: Wed, 2 Jun 2021 17:46:04 +0200 Subject: [PATCH 01/13] proof of concept --- internal/api/notification.go | 9 ++++++ internal/dispatcher/notification.go | 43 +++++++++++++++++++++++++++++ internal/model/notification.go | 11 ++++++++ internal/router/router.go | 2 ++ 4 files changed, 65 insertions(+) diff --git a/internal/api/notification.go b/internal/api/notification.go index bfb8af0..b4a2100 100644 --- a/internal/api/notification.go +++ b/internal/api/notification.go @@ -19,6 +19,7 @@ type NotificationDatabase interface { // The NotificationDispatcher interface for relaying notifications. type NotificationDispatcher interface { SendNotification(a *model.Application, n *model.Notification) error + SendDeleteNotification(a *model.Application, n *model.DeleteNotification) error } // NotificationHandler holds information for processing requests about notifications. @@ -51,3 +52,11 @@ func (h *NotificationHandler) CreateNotification(ctx *gin.Context) { ctx.JSON(http.StatusOK, ¬ification) } + +func (h *NotificationHandler) DeleteNotification(ctx *gin.Context) { + application := authentication.GetApplication(ctx) + + n := model.DeleteNotification{} + + h.DP.SendDeleteNotification(application, &n) +} diff --git a/internal/dispatcher/notification.go b/internal/dispatcher/notification.go index 2a59a6a..0eb15c3 100644 --- a/internal/dispatcher/notification.go +++ b/internal/dispatcher/notification.go @@ -1,6 +1,7 @@ package dispatcher import ( + "encoding/json" "fmt" "html" "log" @@ -10,6 +11,16 @@ import ( "github.com/pushbits/server/internal/model" ) +type ExampleEvent struct { + Body string `json:"body"` + Msgtype string `json:"msgtype"` + RelatesTo RelatesTo `json:"m.relates_to,omitempty"` +} + +type RelatesTo struct { + InReplyTo map[string]string `json:"m.in_reply_to"` +} + // SendNotification sends a notification to the specified user. func (d *Dispatcher) SendNotification(a *model.Application, n *model.Notification) error { log.Printf("Sending notification to room %s.", a.MatrixID) @@ -27,6 +38,38 @@ func (d *Dispatcher) SendNotification(a *model.Application, n *model.Notificatio return err } +func (d *Dispatcher) SendDeleteNotification(a *model.Application, n *model.DeleteNotification) error { + log.Printf("Sending delete notification to room %s", a.MatrixID) + event := ExampleEvent{ + Body: "Testmessage", + Msgtype: "m.text", + } + + irt := make(map[string]string) + + irt["event_id"] = "$uf5OLKPaefHTZhc2lxSIY7If7pLFcNHcMZLbMfS-7qw" + + rt := RelatesTo{ + InReplyTo: irt, + } + + event.RelatesTo = rt + + _, err := d.client.SendMessageEvent(a.MatrixID, "m.room.message", event) + + if err != nil { + log.Println(err) + } + + messages, _ := d.client.Messages(a.MatrixID, "", "", 'b', 10) + + js, _ := json.Marshal(messages) + + log.Println(string(js)) + + return nil +} + // HTML-formats the title func (d *Dispatcher) getFormattedTitle(n *model.Notification) string { trimmedTitle := strings.TrimSpace(n.Title) diff --git a/internal/model/notification.go b/internal/model/notification.go index ef8d6ed..0b229cf 100644 --- a/internal/model/notification.go +++ b/internal/model/notification.go @@ -14,3 +14,14 @@ type Notification struct { Extras map[string]interface{} `json:"extras,omitempty" form:"-" query:"-"` Date time.Time `json:"date"` } + +// DeleteNotification holds information like the message, the reply to message id and the priority of a deletion notification. +type DeleteNotification struct { + ID uint `json:"id"` + DeleteID uint `json:"deleteid"` + ApplicationID uint `json:"appid"` + Message string `json:"message" form:"message" query:"message" binding:"required"` + Priority int `json:"priority" form:"priority" query:"priority"` + Extras map[string]interface{} `json:"extras,omitempty" form:"-" query:"-"` + Date time.Time `json:"date"` +} diff --git a/internal/router/router.go b/internal/router/router.go index 587c256..9dd5e87 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -47,6 +47,8 @@ func Create(debug bool, cm *credentials.Manager, db *database.Database, dp *disp r.POST("/message", auth.RequireApplicationToken(), notificationHandler.CreateNotification) + r.GET("/test", auth.RequireApplicationToken(), notificationHandler.DeleteNotification) + userGroup := r.Group("/user") userGroup.Use(auth.RequireAdmin()) { From b392ea1b44fa25f86e6070aeb028866f28cee57d Mon Sep 17 00:00:00 2001 From: Cubicroot Date: Sun, 6 Jun 2021 19:30:25 +0200 Subject: [PATCH 02/13] add endpoint for delete --- internal/api/context.go | 11 +++++++++ internal/api/middleware.go | 17 ++++++++++++++ internal/api/notification.go | 24 +++++++++++++++----- internal/dispatcher/notification.go | 35 +++++++++++++---------------- internal/model/notification.go | 11 +++------ internal/router/router.go | 1 + 6 files changed, 65 insertions(+), 34 deletions(-) diff --git a/internal/api/context.go b/internal/api/context.go index c408515..90a5ec8 100644 --- a/internal/api/context.go +++ b/internal/api/context.go @@ -20,6 +20,17 @@ func getID(ctx *gin.Context) (uint, error) { return id, nil } +func getMessageID(ctx *gin.Context) (string, error) { + id, ok := ctx.MustGet("messageid").(string) + if !ok { + err := errors.New("an error occured while retrieving messageID from context") + ctx.AbortWithError(http.StatusInternalServerError, err) + return "", err + } + + return id, nil +} + func getApplication(ctx *gin.Context, db Database) (*model.Application, error) { id, err := getID(ctx) if err != nil { diff --git a/internal/api/middleware.go b/internal/api/middleware.go index a6507e1..3d84840 100644 --- a/internal/api/middleware.go +++ b/internal/api/middleware.go @@ -8,6 +8,10 @@ type idInURI struct { ID uint `uri:"id" binding:"required"` } +type messageIdInURI struct { + MessageID string `uri:"messageid" binding:"required"` +} + // RequireIDInURI returns a Gin middleware which requires an ID to be supplied in the URI of the request. func RequireIDInURI() gin.HandlerFunc { return func(ctx *gin.Context) { @@ -20,3 +24,16 @@ func RequireIDInURI() gin.HandlerFunc { ctx.Set("id", requestModel.ID) } } + +// RequireMessageIDInURI returns a Gin middleware which requires an messageID to be supplied in the URI of the request. +func RequireMessageIDInURI() gin.HandlerFunc { + return func(ctx *gin.Context) { + var requestModel messageIdInURI + + if err := ctx.BindUri(&requestModel); err != nil { + return + } + + ctx.Set("messageid", requestModel.MessageID) + } +} diff --git a/internal/api/notification.go b/internal/api/notification.go index b4a2100..9cd51d0 100644 --- a/internal/api/notification.go +++ b/internal/api/notification.go @@ -18,8 +18,8 @@ type NotificationDatabase interface { // The NotificationDispatcher interface for relaying notifications. type NotificationDispatcher interface { - SendNotification(a *model.Application, n *model.Notification) error - SendDeleteNotification(a *model.Application, n *model.DeleteNotification) error + SendNotification(a *model.Application, n *model.Notification) (id string, err error) + DeleteNotification(a *model.Application, n *model.DeleteNotification) error } // NotificationHandler holds information for processing requests about notifications. @@ -39,24 +39,36 @@ func (h *NotificationHandler) CreateNotification(ctx *gin.Context) { application := authentication.GetApplication(ctx) log.Printf("Sending notification for application %s.", application.Name) - notification.ID = 0 notification.ApplicationID = application.ID if strings.TrimSpace(notification.Title) == "" { notification.Title = application.Name } notification.Date = time.Now() - if success := successOrAbort(ctx, http.StatusInternalServerError, h.DP.SendNotification(application, ¬ification)); !success { + messageID, err := h.DP.SendNotification(application, ¬ification) + + if success := successOrAbort(ctx, http.StatusInternalServerError, err); !success { return } + notification.ID = messageID + ctx.JSON(http.StatusOK, ¬ification) } +// DeleteNotification is used to delete (or mark as deleted) a notification for a user func (h *NotificationHandler) DeleteNotification(ctx *gin.Context) { application := authentication.GetApplication(ctx) + id, err := getMessageID(ctx) - n := model.DeleteNotification{} + if success := successOrAbort(ctx, http.StatusUnprocessableEntity, err); !success { + return + } - h.DP.SendDeleteNotification(application, &n) + n := model.DeleteNotification{ + ID: id, + Date: time.Now(), + } + + h.DP.DeleteNotification(application, &n) } diff --git a/internal/dispatcher/notification.go b/internal/dispatcher/notification.go index 0eb15c3..edc9c97 100644 --- a/internal/dispatcher/notification.go +++ b/internal/dispatcher/notification.go @@ -1,7 +1,6 @@ package dispatcher import ( - "encoding/json" "fmt" "html" "log" @@ -11,7 +10,7 @@ import ( "github.com/pushbits/server/internal/model" ) -type ExampleEvent struct { +type ReplyEvent struct { Body string `json:"body"` Msgtype string `json:"msgtype"` RelatesTo RelatesTo `json:"m.relates_to,omitempty"` @@ -22,7 +21,7 @@ type RelatesTo struct { } // SendNotification sends a notification to the specified user. -func (d *Dispatcher) SendNotification(a *model.Application, n *model.Notification) error { +func (d *Dispatcher) SendNotification(a *model.Application, n *model.Notification) (id string, err error) { log.Printf("Sending notification to room %s.", a.MatrixID) plainMessage := strings.TrimSpace(n.Message) @@ -33,41 +32,37 @@ func (d *Dispatcher) SendNotification(a *model.Application, n *model.Notificatio text := fmt.Sprintf("%s\n\n%s", plainTitle, plainMessage) formattedText := fmt.Sprintf("%s %s", title, message) - _, err := d.client.SendFormattedText(a.MatrixID, text, formattedText) + respSendEvent, err := d.client.SendFormattedText(a.MatrixID, text, formattedText) - return err + return respSendEvent.EventID, err } -func (d *Dispatcher) SendDeleteNotification(a *model.Application, n *model.DeleteNotification) error { +// DeleteNotification sends a notification to the specified user that another notificaion is deleted +func (d *Dispatcher) DeleteNotification(a *model.Application, n *model.DeleteNotification) error { log.Printf("Sending delete notification to room %s", a.MatrixID) - event := ExampleEvent{ - Body: "Testmessage", + event := ReplyEvent{ + Body: "This message got deleted.", Msgtype: "m.text", } irt := make(map[string]string) - - irt["event_id"] = "$uf5OLKPaefHTZhc2lxSIY7If7pLFcNHcMZLbMfS-7qw" - + irt["event_id"] = n.ID rt := RelatesTo{ InReplyTo: irt, } - event.RelatesTo = rt _, err := d.client.SendMessageEvent(a.MatrixID, "m.room.message", event) - if err != nil { - log.Println(err) - } + return err - messages, _ := d.client.Messages(a.MatrixID, "", "", 'b', 10) + /* + messages, _ := d.client.Messages(a.MatrixID, "", "", 'b', 10) - js, _ := json.Marshal(messages) + js, _ := json.Marshal(messages) - log.Println(string(js)) - - return nil + log.Println(string(js)) + */ } // HTML-formats the title diff --git a/internal/model/notification.go b/internal/model/notification.go index 0b229cf..7bb64b7 100644 --- a/internal/model/notification.go +++ b/internal/model/notification.go @@ -6,7 +6,7 @@ import ( // Notification holds information like the message, the title, and the priority of a notification. type Notification struct { - ID uint `json:"id"` + ID string `json:"id"` ApplicationID uint `json:"appid"` Message string `json:"message" form:"message" query:"message" binding:"required"` Title string `json:"title" form:"title" query:"title"` @@ -17,11 +17,6 @@ type Notification struct { // DeleteNotification holds information like the message, the reply to message id and the priority of a deletion notification. type DeleteNotification struct { - ID uint `json:"id"` - DeleteID uint `json:"deleteid"` - ApplicationID uint `json:"appid"` - Message string `json:"message" form:"message" query:"message" binding:"required"` - Priority int `json:"priority" form:"priority" query:"priority"` - Extras map[string]interface{} `json:"extras,omitempty" form:"-" query:"-"` - Date time.Time `json:"date"` + ID string `json:"id" form:"id"` + Date time.Time `json:"date"` } diff --git a/internal/router/router.go b/internal/router/router.go index 9dd5e87..1debc4a 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -46,6 +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.GET("/test", auth.RequireApplicationToken(), notificationHandler.DeleteNotification) From eebc7f7e316d3fd8a2bd696879f3d116039e2526 Mon Sep 17 00:00:00 2001 From: Cubicroot Date: Sun, 6 Jun 2021 21:13:07 +0200 Subject: [PATCH 03/13] get delete messages to work --- README.md | 12 ++++ internal/api/errors.go | 5 ++ internal/api/notification.go | 8 ++- internal/api/util.go | 8 ++- internal/dispatcher/notification.go | 85 ++++++++++++++++++++++++----- internal/model/notification.go | 1 + internal/router/router.go | 2 +- 7 files changed, 103 insertions(+), 18 deletions(-) create mode 100644 internal/api/errors.go 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
\n
This 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) From 60277386d942e684cf3157b4444cef3204fd6d41 Mon Sep 17 00:00:00 2001 From: Cubicroot Date: Tue, 8 Jun 2021 13:00:31 +0200 Subject: [PATCH 04/13] polish implementation --- internal/dispatcher/notification.go | 105 +++++++++++++++++++++------- 1 file changed, 79 insertions(+), 26 deletions(-) diff --git a/internal/dispatcher/notification.go b/internal/dispatcher/notification.go index c17c634..9b974d0 100644 --- a/internal/dispatcher/notification.go +++ b/internal/dispatcher/notification.go @@ -1,7 +1,6 @@ package dispatcher import ( - "errors" "fmt" "html" "log" @@ -13,22 +12,41 @@ import ( "github.com/pushbits/server/internal/model" ) +// MessageFormat is a matrix message format type MessageFormat string +// MsgType is a matrix msgtype +type MsgType string + +// Define matrix constants const ( - FormatHTML = MessageFormat("org.matrix.custom.html") + MessageFormatHTML = MessageFormat("org.matrix.custom.html") + MsgTypeText = MsgType("m.text") ) -type ReplyEvent struct { +// MessageEvent is the content of a matrix message event +type MessageEvent struct { Body string `json:"body"` FormattedBody string `json:"formatted_body"` - Msgtype string `json:"msgtype"` + MsgType MsgType `json:"msgtype"` RelatesTo RelatesTo `json:"m.relates_to,omitempty"` Format MessageFormat `json:"format"` + NewContent NewContent `json:"m.new_content,omitempty"` } +// RelatesTo holds information about relations to other message events type RelatesTo struct { - InReplyTo map[string]string `json:"m.in_reply_to"` + InReplyTo map[string]string `json:"m.in_reply_to,omitempty"` + RelType string `json:"rel_type,omitempty"` + EventID string `json:"event_id,omitempty"` +} + +// NewContent holds information about an updated message event +type NewContent struct { + Body string `json:"body"` + FormattedBody string `json:"formatted_body"` + MsgType MsgType `json:"msgtype"` + Format MessageFormat `json:"format"` } // SendNotification sends a notification to the specified user. @@ -50,10 +68,11 @@ 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) + var oldFormattedBody string + var oldBody string + // get the message we want to delete deleteMessage, err := d.getMessage(a, n.ID) if err != nil { @@ -64,8 +83,8 @@ func (d *Dispatcher) DeleteNotification(a *model.Application, n *model.DeleteNot if val, ok := deleteMessage.Content["body"]; ok { body, ok := val.(string) if ok { - deleteMessageBody = body - deleteMessageFormattedBody = body + oldBody = body + oldFormattedBody = body } } else { log.Println("Message to delete has wrong format") @@ -75,29 +94,63 @@ func (d *Dispatcher) DeleteNotification(a *model.Application, n *model.DeleteNot if val, ok := deleteMessage.Content["formatted_body"]; ok { body, ok := val.(string) if ok { - deleteMessageFormattedBody = body + oldFormattedBody = body } } + // update the message with strikethrough + newBody := fmt.Sprintf("%s\n- deleted", oldBody) + newFormattedBody := fmt.Sprintf("%s
- deleted", oldFormattedBody) + + newMessage := NewContent{ + Body: newBody, + FormattedBody: newFormattedBody, + MsgType: MsgTypeText, + Format: MessageFormatHTML, + } + + replaceRelation := RelatesTo{ + RelType: "m.replace", + EventID: deleteMessage.ID, + } + + replaceEvent := MessageEvent{ + Body: oldBody, + FormattedBody: oldFormattedBody, + MsgType: MsgTypeText, + NewContent: newMessage, + RelatesTo: replaceRelation, + Format: MessageFormatHTML, + } + + _, err = d.client.SendMessageEvent(a.MatrixID, "m.room.message", replaceEvent) + + if err != nil { + log.Println(err) + return err + } + + // send a notification about the deletion // formatting according to https://matrix.org/docs/spec/client_server/latest#fallbacks-and-event-representation - formattedBody := fmt.Sprintf("
In reply to %s
%s
\n
This 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) + notificationFormattedBody := fmt.Sprintf("
In reply to %s
%s
\n
This message got deleted.", deleteMessage.RoomID, deleteMessage.ID, deleteMessage.Sender, deleteMessage.Sender, oldFormattedBody) + notificationBody := fmt.Sprintf("> <%s>%s\n\nThis message got deleted", deleteMessage.Sender, oldBody) - event := ReplyEvent{ - FormattedBody: formattedBody, - Body: body, - Msgtype: "m.text", - Format: FormatHTML, + notificationEvent := MessageEvent{ + FormattedBody: notificationFormattedBody, + Body: notificationBody, + MsgType: MsgTypeText, + Format: MessageFormatHTML, } - irt := make(map[string]string) - rt := RelatesTo{ - InReplyTo: irt, - } - event.RelatesTo = rt - irt["event_id"] = deleteMessage.ID + notificationReply := make(map[string]string) + notificationReply["event_id"] = deleteMessage.ID - _, err = d.client.SendMessageEvent(a.MatrixID, "m.room.message", event) + notificationRelation := RelatesTo{ + InReplyTo: notificationReply, + } + notificationEvent.RelatesTo = notificationRelation + + _, err = d.client.SendMessageEvent(a.MatrixID, "m.room.message", notificationEvent) return err } @@ -168,7 +221,7 @@ func (d *Dispatcher) coloredText(color string, text string) string { return "" + text + "" } -// TODO cubicroot: find a way to only request on specific event +// Searches in the messages list for the given id func (d *Dispatcher) getMessage(a *model.Application, id string) (gomatrix.Event, error) { start := "" end := "" @@ -183,5 +236,5 @@ func (d *Dispatcher) getMessage(a *model.Application, id string) (gomatrix.Event } start = messages.End } - return gomatrix.Event{}, errors.New("Message not found") + return gomatrix.Event{}, api.ErrorMessageNotFound } From 975c0a246b9c808df7a45422b64b27940783830f Mon Sep 17 00:00:00 2001 From: Cubicroot Date: Tue, 8 Jun 2021 13:10:59 +0200 Subject: [PATCH 05/13] remove debug --- internal/router/router.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/internal/router/router.go b/internal/router/router.go index 6392973..68f61ca 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -48,8 +48,6 @@ func Create(debug bool, cm *credentials.Manager, db *database.Database, dp *disp r.POST("/message", auth.RequireApplicationToken(), notificationHandler.CreateNotification) r.DELETE("/message/:messageid", api.RequireMessageIDInURI(), auth.RequireApplicationToken(), notificationHandler.DeleteNotification) - r.GET("/test", auth.RequireApplicationToken(), notificationHandler.DeleteNotification) - userGroup := r.Group("/user") userGroup.Use(auth.RequireAdmin()) { From 27c65921d960644aa27ed8c25d1f50777e655d20 Mon Sep 17 00:00:00 2001 From: eikendev Date: Fri, 11 Jun 2021 00:00:50 +0200 Subject: [PATCH 06/13] Replace golint with staticcheck --- .github/workflows/main.yml | 3 ++- Makefile | 7 ++----- go.mod | 5 ++++- go.sum | 37 +++++++++++++++++++++++++++++++++++++ 4 files changed, 45 insertions(+), 7 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 93e6fc3..d185008 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -5,7 +5,8 @@ jobs: name: Test and publish runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - name: Checkout code + uses: actions/checkout@v2 - name: Install dependencies run: make setup - name: Run tests diff --git a/Makefile b/Makefile index 56aeeed..b540007 100644 --- a/Makefile +++ b/Makefile @@ -12,16 +12,13 @@ test: fi go vet ./... gocyclo -over 10 $(shell find . -iname '*.go' -type f) + staticcheck ./... go test -v -cover ./... - stdout=$$(golint ./... 2>&1); \ - if [ "$$stdout" ]; then \ - exit 1; \ - fi .PHONY: setup setup: go get -u github.com/fzipp/gocyclo/cmd/gocyclo - go get -u golang.org/x/lint/golint + go get -u honnef.co/go/tools/cmd/staticcheck .PHONY: build_image build_image: diff --git a/go.mod b/go.mod index e5c9e72..28b463c 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.14 require ( github.com/alexedwards/argon2id v0.0.0-20201228115903-cf543ebc1f7b + github.com/fzipp/gocyclo v0.3.1 // indirect github.com/gin-contrib/location v0.0.2 github.com/gin-gonic/gin v1.6.3 github.com/go-playground/validator/v10 v10.3.0 // indirect @@ -18,9 +19,11 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.1 // indirect github.com/ugorji/go v1.2.4 // indirect - golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect + golang.org/x/sys v0.0.0-20210608053332-aa57babbf139 // indirect + golang.org/x/tools v0.1.3 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gorm.io/driver/mysql v1.0.4 gorm.io/driver/sqlite v1.1.4 gorm.io/gorm v1.20.12 + honnef.co/go/tools v0.2.0 // indirect ) diff --git a/go.sum b/go.sum index c30c298..35c2598 100644 --- a/go.sum +++ b/go.sum @@ -5,6 +5,8 @@ github.com/alexedwards/argon2id v0.0.0-20201228115903-cf543ebc1f7b/go.mod h1:Kmn github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fzipp/gocyclo v0.3.1 h1:A9UeX3HJSXTBzvHzhqoYVuE0eAhe+aM8XBCCwsPMZOc= +github.com/fzipp/gocyclo v0.3.1/go.mod h1:DJHO6AUmbdqj2ET4Z9iArSuwWgYDRryYt2wASxc7x3E= github.com/gin-contrib/location v0.0.2 h1:QZKh1+K/LLR4KG/61eIO3b7MLuKi8tytQhV6texLgP4= github.com/gin-contrib/location v0.0.2/go.mod h1:NGoidiRlf0BlA/VKSVp+g3cuSMeTmip/63PhEjRhUAc= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= @@ -85,22 +87,55 @@ github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ugorji/go/codec v1.2.4 h1:C5VurWRRCKjuENsbM6GYVw8W++WVW9rSxoACKIvxzz8= github.com/ugorji/go/codec v1.2.4/go.mod h1:bWBu1+kIRWcF8uMklKaJrR6fTWQOwAlrIzX22pHwryA= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= golang.org/dl v0.0.0-20190829154251-82a15e2f2ead/go.mod h1:IUMfjQLJQd4UTqG1Z90tenwKoCX93Gn3MAQJMOSBsDQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210608053332-aa57babbf139 h1:C+AwYEtBp/VQwoLntUmQ/yx3MS9vmZaKNdw5eOpoQe8= +golang.org/x/sys v0.0.0-20210608053332-aa57babbf139/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.3 h1:L69ShwSZEyCsLKoAxDKeMvLDZkumEe8gXUZAjab0tX8= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -124,3 +159,5 @@ gorm.io/driver/sqlite v1.1.4/go.mod h1:mJCeTFr7+crvS+TRnWc5Z3UvwxUN1BGBLMrf5LA9D gorm.io/gorm v1.20.7/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= gorm.io/gorm v1.20.12 h1:ebZ5KrSHzet+sqOCVdH9mTjW91L298nX3v5lVxAzSUY= gorm.io/gorm v1.20.12/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= +honnef.co/go/tools v0.2.0 h1:ws8AfbgTX3oIczLPNPCu5166oBg9ST2vNs0rcht+mDE= +honnef.co/go/tools v0.2.0/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY= From 628d96bdb76144566627af54509e3301378da786 Mon Sep 17 00:00:00 2001 From: eikendev Date: Fri, 11 Jun 2021 00:11:30 +0200 Subject: [PATCH 07/13] Fix linting errors --- .gitignore | 2 +- Makefile | 3 ++- cmd/pushbits/main.go | 2 +- internal/authentication/credentials/password.go | 2 +- internal/dispatcher/dispatcher.go | 7 +------ 5 files changed, 6 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index 4471252..f5f8b98 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -app +out/ *.db config.yml diff --git a/Makefile b/Makefile index b540007..a45cbe8 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,8 @@ IMAGE := eikendev/pushbits .PHONY: build build: - go build -ldflags="-w -s" -o app ./cmd/pushbits + mkdir -p ./out + go build -ldflags="-w -s" -o ./out/pushbits ./cmd/pushbits .PHONY: test test: diff --git a/cmd/pushbits/main.go b/cmd/pushbits/main.go index 424816a..f2965e6 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, c.Formatting) + dp, err := dispatcher.Create(c.Matrix.Homeserver, c.Matrix.Username, c.Matrix.Password, c.Formatting) if err != nil { log.Fatal(err) } diff --git a/internal/authentication/credentials/password.go b/internal/authentication/credentials/password.go index 729838a..f5ab29c 100644 --- a/internal/authentication/credentials/password.go +++ b/internal/authentication/credentials/password.go @@ -14,7 +14,7 @@ func (m *Manager) CreatePasswordHash(password string) ([]byte, error) { if err != nil { return []byte{}, errors.New("HIBP is not available, please wait until service is available again") } else if pwned { - return []byte{}, errors.New("Password is pwned, please choose another one") + return []byte{}, errors.New("password is pwned, please choose another one") } } diff --git a/internal/dispatcher/dispatcher.go b/internal/dispatcher/dispatcher.go index a9128ff..735c072 100644 --- a/internal/dispatcher/dispatcher.go +++ b/internal/dispatcher/dispatcher.go @@ -11,19 +11,14 @@ var ( loginType = "m.login.password" ) -// The Database interface for encapsulating database access. -type Database interface { -} - // Dispatcher holds information for sending notifications to clients. type Dispatcher struct { - db Database client *gomatrix.Client formatting configuration.Formatting } // Create instanciates a dispatcher connection. -func Create(db Database, homeserver, username, password string, formatting configuration.Formatting) (*Dispatcher, error) { +func Create(homeserver, username, password string, formatting configuration.Formatting) (*Dispatcher, error) { log.Println("Setting up dispatcher.") client, err := gomatrix.NewClient(homeserver, "", "") From 28e3927290dce32efdf79654fd3a05e2c8ef5c15 Mon Sep 17 00:00:00 2001 From: eikendev Date: Fri, 11 Jun 2021 00:16:01 +0200 Subject: [PATCH 08/13] Update Dockerfile to reflect filename changes --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3f22476..4dfd8dc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,7 +9,7 @@ RUN set -ex \ && go mod download \ && go mod verify \ && make build \ - && chmod +x /build/app + && chmod +x /build/out/pushbits FROM alpine @@ -22,7 +22,7 @@ EXPOSE 8080 WORKDIR /app COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ -COPY --from=builder /build/app ./run +COPY --from=builder /build/out/pushbits ./run RUN set -ex \ && apk add --no-cache ca-certificates curl \ From 8e5a43e5a4524751849dbbd693f7b2214e485284 Mon Sep 17 00:00:00 2001 From: eikendev Date: Fri, 11 Jun 2021 00:50:06 +0200 Subject: [PATCH 09/13] Run code linters for pull requests --- .github/workflows/main.yml | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d185008..9700373 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,26 +1,33 @@ name: Main -on: push +on: [push, pull_request] jobs: - test_publish: - name: Test and publish + test_build_publish: + name: Test, build, and publish runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v2 - name: Install dependencies run: make setup + - name: Export GOBIN + uses: actions/setup-go@v2 + with: + go-version: 1.16 - name: Run tests - run: | - export PATH="$PATH:$(go env GOPATH)/bin" # Currently the path needs to be set manually. - make test + run: make test - name: Build image run: make build_image + - name: Get Branch + run: | + raw=$(git branch -r --contains ${{ github.ref }}) + branch=${raw##*/} + echo "BRANCH=$branch" >> $GITHUB_ENV - name: Login to Docker Hub + if: ${{ startsWith(github.ref, 'refs/tags/v') && github.event_name == 'push' && env.BRANCH == 'master' }} # Only publish for tagged commits pushed to master. uses: docker/login-action@v1 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Publish image - if: startsWith(github.ref, 'refs/tags/') # Only publish for tagged commits. - run: | - make push_image + if: ${{ startsWith(github.ref, 'refs/tags/v') && github.event_name == 'push' && env.BRANCH == 'master' }} # Only publish for tagged commits pushed to master. + run: make push_image From c5e4669fdab01f12abd6b220d07acfb347900c8b Mon Sep 17 00:00:00 2001 From: Cubicroot Date: Fri, 11 Jun 2021 10:21:48 +0200 Subject: [PATCH 10/13] clean up --- internal/dispatcher/notification.go | 11 ++++++++--- internal/model/notification.go | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/internal/dispatcher/notification.go b/internal/dispatcher/notification.go index 9b974d0..6571d8d 100644 --- a/internal/dispatcher/notification.go +++ b/internal/dispatcher/notification.go @@ -82,10 +82,15 @@ func (d *Dispatcher) DeleteNotification(a *model.Application, n *model.DeleteNot if val, ok := deleteMessage.Content["body"]; ok { body, ok := val.(string) - if ok { - oldBody = body - oldFormattedBody = body + + if !ok { + log.Println("Event does not have a body") + return api.ErrorMessageNotFound } + + oldBody = body + oldFormattedBody = body + } else { log.Println("Message to delete has wrong format") return api.ErrorMessageNotFound diff --git a/internal/model/notification.go b/internal/model/notification.go index 569e8ee..fc46221 100644 --- a/internal/model/notification.go +++ b/internal/model/notification.go @@ -16,7 +16,7 @@ type Notification struct { Date time.Time `json:"date"` } -// DeleteNotification holds information like the message, the reply to message id and the priority of a deletion notification. +// DeleteNotification holds information like the message ID of a deletion notification. type DeleteNotification struct { ID string `json:"id" form:"id"` Date time.Time `json:"date"` From ac5819dfc93a228d57f1d03b95267076ab30f47e Mon Sep 17 00:00:00 2001 From: Cubicroot Date: Fri, 11 Jun 2021 10:47:11 +0200 Subject: [PATCH 11/13] move respond/replace logic to separate functions --- internal/api/errors.go | 2 +- internal/dispatcher/notification.go | 157 ++++++++++++++++------------ 2 files changed, 94 insertions(+), 65 deletions(-) diff --git a/internal/api/errors.go b/internal/api/errors.go index db296e3..64c744a 100644 --- a/internal/api/errors.go +++ b/internal/api/errors.go @@ -2,4 +2,4 @@ package api import "errors" -var ErrorMessageNotFound = errors.New("Message not found") +var ErrorMessageNotFound = errors.New("message not found") diff --git a/internal/dispatcher/notification.go b/internal/dispatcher/notification.go index 6571d8d..16248de 100644 --- a/internal/dispatcher/notification.go +++ b/internal/dispatcher/notification.go @@ -80,82 +80,23 @@ func (d *Dispatcher) DeleteNotification(a *model.Application, n *model.DeleteNot return api.ErrorMessageNotFound } - if val, ok := deleteMessage.Content["body"]; ok { - body, ok := val.(string) + oldBody, oldFormattedBody, err = bodiesFromMessage(deleteMessage) - if !ok { - log.Println("Event does not have a body") - return api.ErrorMessageNotFound - } - - oldBody = body - oldFormattedBody = 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 { - oldFormattedBody = body - } + if err != nil { + return err } // update the message with strikethrough newBody := fmt.Sprintf("%s\n- deleted", oldBody) newFormattedBody := fmt.Sprintf("%s
- deleted", oldFormattedBody) - newMessage := NewContent{ - Body: newBody, - FormattedBody: newFormattedBody, - MsgType: MsgTypeText, - Format: MessageFormatHTML, - } - - replaceRelation := RelatesTo{ - RelType: "m.replace", - EventID: deleteMessage.ID, - } - - replaceEvent := MessageEvent{ - Body: oldBody, - FormattedBody: oldFormattedBody, - MsgType: MsgTypeText, - NewContent: newMessage, - RelatesTo: replaceRelation, - Format: MessageFormatHTML, - } - - _, err = d.client.SendMessageEvent(a.MatrixID, "m.room.message", replaceEvent) + _, err = d.replaceMessage(a, newBody, newFormattedBody, deleteMessage.ID, oldBody, oldFormattedBody) if err != nil { - log.Println(err) return err } - // send a notification about the deletion - // formatting according to https://matrix.org/docs/spec/client_server/latest#fallbacks-and-event-representation - notificationFormattedBody := fmt.Sprintf("
In reply to %s
%s
\n
This message got deleted.", deleteMessage.RoomID, deleteMessage.ID, deleteMessage.Sender, deleteMessage.Sender, oldFormattedBody) - notificationBody := fmt.Sprintf("> <%s>%s\n\nThis message got deleted", deleteMessage.Sender, oldBody) - - notificationEvent := MessageEvent{ - FormattedBody: notificationFormattedBody, - Body: notificationBody, - MsgType: MsgTypeText, - Format: MessageFormatHTML, - } - - notificationReply := make(map[string]string) - notificationReply["event_id"] = deleteMessage.ID - - notificationRelation := RelatesTo{ - InReplyTo: notificationReply, - } - notificationEvent.RelatesTo = notificationRelation - - _, err = d.client.SendMessageEvent(a.MatrixID, "m.room.message", notificationEvent) + _, err = d.respondToMessage(a, "This message got deleted", "This message got deleted.", deleteMessage) return err } @@ -243,3 +184,91 @@ func (d *Dispatcher) getMessage(a *model.Application, id string) (gomatrix.Event } return gomatrix.Event{}, api.ErrorMessageNotFound } + +// Replaces the content of a matrix message +func (d *Dispatcher) replaceMessage(a *model.Application, newBody, newFormattedBody string, messageID string, oldBody, oldFormattedBody string) (*gomatrix.RespSendEvent, error) { + newMessage := NewContent{ + Body: newBody, + FormattedBody: newFormattedBody, + MsgType: MsgTypeText, + Format: MessageFormatHTML, + } + + replaceRelation := RelatesTo{ + RelType: "m.replace", + EventID: messageID, + } + + replaceEvent := MessageEvent{ + Body: oldBody, + FormattedBody: oldFormattedBody, + MsgType: MsgTypeText, + NewContent: newMessage, + RelatesTo: replaceRelation, + Format: MessageFormatHTML, + } + + sendEvent, err := d.client.SendMessageEvent(a.MatrixID, "m.room.message", replaceEvent) + + if err != nil { + log.Println(err) + return nil, err + } + + return sendEvent, nil +} + +// Sends a notification in response to another matrix message event +func (d *Dispatcher) respondToMessage(a *model.Application, body, formattedBody string, respondMessage gomatrix.Event) (*gomatrix.RespSendEvent, error) { + oldBody, oldFormattedBody, err := bodiesFromMessage(respondMessage) + + if err != nil { + return nil, err + } + + // formatting according to https://matrix.org/docs/spec/client_server/latest#fallbacks-and-event-representation + newFormattedBody := fmt.Sprintf("
In reply to %s
%s
\n
%s", respondMessage.RoomID, respondMessage.ID, respondMessage.Sender, respondMessage.Sender, oldFormattedBody, formattedBody) + newBody := fmt.Sprintf("> <%s>%s\n\n%s", respondMessage.Sender, oldBody, body) + + notificationEvent := MessageEvent{ + FormattedBody: newFormattedBody, + Body: newBody, + MsgType: MsgTypeText, + Format: MessageFormatHTML, + } + + notificationReply := make(map[string]string) + notificationReply["event_id"] = respondMessage.ID + + notificationRelation := RelatesTo{ + InReplyTo: notificationReply, + } + notificationEvent.RelatesTo = notificationRelation + + return d.client.SendMessageEvent(a.MatrixID, "m.room.message", notificationEvent) +} + +// Extracts body and formatted body from a matrix message event +func bodiesFromMessage(message gomatrix.Event) (body, formattedBody string, err error) { + if val, ok := message.Content["body"]; ok { + body, ok := val.(string) + + if !ok { + return "", "", api.ErrorMessageNotFound + } + + formattedBody = body + + } else { + return "", "", api.ErrorMessageNotFound + } + + if val, ok := message.Content["formatted_body"]; ok { + body, ok := val.(string) + if ok { + formattedBody = body + } + } + + return body, formattedBody, nil +} From 5450d44f53b97544aa4b68b5ad7594aa053a879a Mon Sep 17 00:00:00 2001 From: eikendev Date: Fri, 11 Jun 2021 23:18:29 +0200 Subject: [PATCH 12/13] Fix CI for pull requests --- .github/workflows/main.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9700373..f46a74b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -17,15 +17,16 @@ jobs: run: make test - name: Build image run: make build_image - - name: Get Branch + - name: Get Branch # Needed to evaluate env.BRANCH. + if: ${{ startsWith(github.ref, 'refs/tags/v') && github.event_name == 'push' }} # Otherwise will fail on pull requests. run: | raw=$(git branch -r --contains ${{ github.ref }}) branch=${raw##*/} echo "BRANCH=$branch" >> $GITHUB_ENV - name: Login to Docker Hub - if: ${{ startsWith(github.ref, 'refs/tags/v') && github.event_name == 'push' && env.BRANCH == 'master' }} # Only publish for tagged commits pushed to master. + if: ${{ startsWith(github.ref, 'refs/tags/v') && github.event_name == 'push' && env.BRANCH == 'master' }} # Only login for tagged commits pushed to master. uses: docker/login-action@v1 - with: + with: # Secrets are not exposed to pull request contexts. username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Publish image From c1cc369d1e741195320137253c190d60dad1343e Mon Sep 17 00:00:00 2001 From: Cubicroot Date: Thu, 17 Jun 2021 17:37:24 +0200 Subject: [PATCH 13/13] more strict type handling --- internal/dispatcher/notification.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/internal/dispatcher/notification.go b/internal/dispatcher/notification.go index 16248de..9fce7a0 100644 --- a/internal/dispatcher/notification.go +++ b/internal/dispatcher/notification.go @@ -265,9 +265,11 @@ func bodiesFromMessage(message gomatrix.Event) (body, formattedBody string, err if val, ok := message.Content["formatted_body"]; ok { body, ok := val.(string) - if ok { - formattedBody = body + if !ok { + return "", "", api.ErrorMessageNotFound } + + formattedBody = body } return body, formattedBody, nil