From 7687c5240b2c7aed8936d227f497c1a02f499bd4 Mon Sep 17 00:00:00 2001 From: Bernhard B Date: Fri, 27 Mar 2026 21:15:17 +0100 Subject: [PATCH] added API endpoints for pinning/unpinning messages in groups see #820 --- src/api/api.go | 108 +++++++++++++++++++++++++++++++++++++++++++ src/client/client.go | 59 +++++++++++++++++++++++ src/main.go | 2 + 3 files changed, 169 insertions(+) diff --git a/src/api/api.go b/src/api/api.go index d12efdd..daf7639 100644 --- a/src/api/api.go +++ b/src/api/api.go @@ -56,6 +56,17 @@ type UpdateGroupRequest struct { Permissions *ds.GroupPermissions `json:"permissions"` } +type PinMessageInGroupRequest struct { + TargetAuthor string `json:"target_author"` + Timestamp int64 `json:"timestamp"` + Duration *int `json:"duration"` +} + +type UnpinMessageInGroupRequest struct { + TargetAuthor string `json:"target_author"` + Timestamp int64 `json:"timestamp"` +} + type ChangeGroupMembersRequest struct { Members []string `json:"members"` } @@ -1173,6 +1184,103 @@ func (a *Api) DeleteGroup(c *gin.Context) { } } +// @Summary Pin a message in a Signal Group. +// @Tags Groups +// @Description Pin a message in a Signal Group. +// @Accept json +// @Produce json +// @Success 200 {string} string "OK" +// @Failure 400 {object} Error +// @Param data body PinMessageInGroupRequest true "Pin" +// @Param number path string true "Registered Phone Number" +// @Param groupid path string true "Group Id" +// @Router /v1/groups/{number}/{groupid}/pin-message [post] +func (a *Api) PinMessageInGroup(c *gin.Context) { + base64EncodedGroupId := c.Param("groupid") + number, err := url.PathUnescape(c.Param("number")) + if err != nil { + c.JSON(400, Error{Msg: "Couldn't process request - malformed number"}) + return + } + + if base64EncodedGroupId == "" { + c.JSON(400, Error{Msg: "Please specify a group id"}) + return + } + + groupId, err := client.ConvertGroupIdToInternalGroupId(base64EncodedGroupId) + if err != nil { + c.JSON(400, Error{Msg: err.Error()}) + return + } + + var req PinMessageInGroupRequest + err = c.BindJSON(&req) + if err != nil { + c.JSON(400, Error{Msg: "Couldn't process request - invalid request"}) + return + } + + duration := -1 + if req.Duration != nil { + duration = *req.Duration + } + + err = a.signalClient.PinMessageInGroup(number, groupId, req.TargetAuthor, req.Timestamp, duration) + if err != nil { + c.JSON(400, Error{Msg: err.Error()}) + return + } + + c.Status(201) +} + +// @Summary Unpin a message in a Signal Group. +// @Tags Groups +// @Description Unpin a message in a Signal Group. +// @Accept json +// @Produce json +// @Success 200 {string} string "OK" +// @Failure 400 {object} Error +// @Param data body UnpinMessageInGroupRequest true "Unpin" +// @Param number path string true "Registered Phone Number" +// @Param groupid path string true "Group Id" +// @Router /v1/groups/{number}/{groupid}/pin-message [delete] +func (a *Api) UnpinMessageInGroup(c *gin.Context) { + base64EncodedGroupId := c.Param("groupid") + number, err := url.PathUnescape(c.Param("number")) + if err != nil { + c.JSON(400, Error{Msg: "Couldn't process request - malformed number"}) + return + } + + if base64EncodedGroupId == "" { + c.JSON(400, Error{Msg: "Please specify a group id"}) + return + } + + groupId, err := client.ConvertGroupIdToInternalGroupId(base64EncodedGroupId) + if err != nil { + c.JSON(400, Error{Msg: err.Error()}) + return + } + + var req UnpinMessageInGroupRequest + err = c.BindJSON(&req) + if err != nil { + c.JSON(400, Error{Msg: "Couldn't process request - invalid request"}) + return + } + + err = a.signalClient.UnpinMessageInGroup(number, groupId, req.TargetAuthor, req.Timestamp) + if err != nil { + c.JSON(400, Error{Msg: err.Error()}) + return + } + + c.Status(201) +} + // @Summary Link device and generate QR code. // @Tags Devices // @Description Link device and generate QR code diff --git a/src/client/client.go b/src/client/client.go index 5bf2fea..ad8e900 100644 --- a/src/client/client.go +++ b/src/client/client.go @@ -1456,6 +1456,65 @@ func (s *SignalClient) GetGroupExpanded(number string, groupId string) (*Expande return nil, nil } +func (s *SignalClient) PinMessageInGroup(number string, groupId string, targetAuthor string, timestamp int64, duration int) error { + if s.signalCliMode == JsonRpc { + type Request struct { + TargetAuthor string `json:"target-author"` + TargetTimestamp int64 `json:"target-timestamp"` + PinDuration int `json:"pin-duration"` + GroupId string `json:"group-id"` + } + + req := Request{TargetAuthor: targetAuthor, TargetTimestamp: timestamp, PinDuration: duration, GroupId: groupId} + jsonRpc2Client, err := s.getJsonRpc2Client() + if err != nil { + return err + } + _, err = jsonRpc2Client.getRaw("sendPinMessage", &number, req) + if err != nil { + return err + } + } else { + cmd := []string{"--config", s.signalCliConfig, "-o", "json", "-a", number, "sendPinMessage", "-g", groupId, + "-a", targetAuthor, "-t", strconv.FormatInt(timestamp, 10), "-d", strconv.Itoa(duration)} + + _, err := s.cliClient.Execute(true, cmd, "") + if err != nil { + return err + } + } + return nil +} + +func (s *SignalClient) UnpinMessageInGroup(number string, groupId string, targetAuthor string, timestamp int64) error { + if s.signalCliMode == JsonRpc { + type Request struct { + TargetAuthor string `json:"target-author"` + TargetTimestamp int64 `json:"target-timestamp"` + GroupId string `json:"group-id"` + } + + req := Request{TargetAuthor: targetAuthor, TargetTimestamp: timestamp, GroupId: groupId} + jsonRpc2Client, err := s.getJsonRpc2Client() + if err != nil { + return err + } + _, err = jsonRpc2Client.getRaw("sendUnpinMessage", &number, req) + if err != nil { + return err + } + } else { + cmd := []string{"--config", s.signalCliConfig, "-o", "json", "-a", number, "sendUnpinMessage", "-g", groupId, + "-a", targetAuthor, "-t", strconv.FormatInt(timestamp, 10)} + + _, err := s.cliClient.Execute(true, cmd, "") + if err != nil { + return err + } + } + return nil +} + func (s *SignalClient) GetAvatar(number string, id string, avatarType AvatarType) ([]byte, error) { var err error var rawData string diff --git a/src/main.go b/src/main.go index b71ab2d..caa58f6 100644 --- a/src/main.go +++ b/src/main.go @@ -225,6 +225,8 @@ func main() { groups.DELETE(":number/:groupid/members", api.RemoveMembersFromGroup) groups.POST(":number/:groupid/admins", api.AddAdminsToGroup) groups.DELETE(":number/:groupid/admins", api.RemoveAdminsFromGroup) + groups.POST(":number/:groupid/pin-message", api.PinMessageInGroup) + groups.DELETE(":number/:groupid/pin-message", api.UnpinMessageInGroup) } link := v1.Group("qrcodelink")