From b3a6ee1e5dca37545cd2c8ce4e1cf614815207ea Mon Sep 17 00:00:00 2001 From: Bernhard B Date: Tue, 3 Jun 2025 22:18:29 +0200 Subject: [PATCH] added new endpoint to return a group's avatar --- src/api/api.go | 34 +++++++++++++++++++++++++++++ src/client/client.go | 51 +++++++++++++++++++++++++++++++++++++++++++ src/docs/docs.go | 48 ++++++++++++++++++++++++++++++++++++++++ src/docs/swagger.json | 48 ++++++++++++++++++++++++++++++++++++++++ src/docs/swagger.yaml | 32 +++++++++++++++++++++++++++ src/main.go | 1 + 6 files changed, 214 insertions(+) diff --git a/src/api/api.go b/src/api/api.go index e9ef782..bbb47e0 100644 --- a/src/api/api.go +++ b/src/api/api.go @@ -967,6 +967,40 @@ func (a *Api) GetGroup(c *gin.Context) { } } +// @Summary Returns the avatar of a Signal Group. +// @Tags Groups +// @Description Returns the avatar of a Signal Group. +// @Accept json +// @Produce json +// @Success 200 {string} string "Image" +// @Failure 400 {object} Error +// @Param number path string true "Registered Phone Number" +// @Param groupid path string true "Group ID" +// @Router /v1/groups/{number}/{groupid}/avatar [get] +func (a *Api) GetGroupAvatar(c *gin.Context) { + number, err := url.PathUnescape(c.Param("number")) + if err != nil { + c.JSON(400, Error{Msg: "Couldn't process request - malformed number"}) + return + } + groupId := c.Param("groupid") + + groupAvatar, err := a.signalClient.GetGroupAvatar(number, groupId) + if err != nil { + switch err.(type) { + case *client.NotFoundError: + c.JSON(404, Error{Msg: err.Error()}) + return + default: + c.JSON(400, Error{Msg: err.Error()}) + return + } + } + + mimeType := mimetype.Detect(groupAvatar) + c.Data(200, mimeType.String(), groupAvatar) +} + // @Summary Delete a Signal Group. // @Tags Groups // @Description Delete the specified Signal Group. diff --git a/src/client/client.go b/src/client/client.go index d577dae..abca490 100644 --- a/src/client/client.go +++ b/src/client/client.go @@ -1288,6 +1288,57 @@ func (s *SignalClient) GetGroup(number string, groupId string) (*GroupEntry, err return nil, nil } +func (s *SignalClient) GetGroupAvatar(number string, groupId string) ([]byte, error) { + var err error + var rawData string + + internalGroupId, err := ConvertGroupIdToInternalGroupId(groupId) + if err != nil { + return []byte{}, errors.New("Invalid group id") + } + + if s.signalCliMode == JsonRpc { + type Request struct { + GroupId string `json:"groupId"` + } + + request := Request{GroupId: internalGroupId} + + jsonRpc2Client, err := s.getJsonRpc2Client() + if err != nil { + return []byte{}, err + } + rawData, err = jsonRpc2Client.getRaw("getAvatar", &number, request) + if err != nil { + if err.Error() == "Could not find avatar" { + return []byte{},&NotFoundError{Description: "No avatar found."} + } + return []byte{}, err + } + } else { + rawData, err = s.cliClient.Execute(true, []string{"--config", s.signalCliConfig, "-o", "json", "-a", number, "getAvatar", "-g", internalGroupId}, "") + if err != nil { + return []byte{}, err + } + } + + type SignalCliResponse struct { + Data string `json:"data"` + } + var signalCliResponse SignalCliResponse + err = json.Unmarshal([]byte(rawData), &signalCliResponse) + if err != nil { + return []byte{}, errors.New("Couldn't unmarshal data: " + err.Error()) + } + + groupAvatarBytes, err := base64.StdEncoding.DecodeString(signalCliResponse.Data) + if err != nil { + return []byte{}, errors.New("Couldn't decode base64 encoded group avatar: " + err.Error()) + } + + return groupAvatarBytes, nil +} + func (s *SignalClient) DeleteGroup(number string, groupId string) error { if s.signalCliMode == JsonRpc { type Request struct { diff --git a/src/docs/docs.go b/src/docs/docs.go index 2d9225d..414bd98 100644 --- a/src/docs/docs.go +++ b/src/docs/docs.go @@ -1080,6 +1080,51 @@ const docTemplate = `{ } } }, + "/v1/groups/{number}/{groupid}/avatar": { + "get": { + "description": "Returns the avatar of a Signal Group.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Groups" + ], + "summary": "Returns the avatar of a Signal Group.", + "parameters": [ + { + "type": "string", + "description": "Registered Phone Number", + "name": "number", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Group ID", + "name": "groupid", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Image", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.Error" + } + } + } + } + }, "/v1/groups/{number}/{groupid}/block": { "post": { "description": "Block the specified Signal Group.", @@ -2720,6 +2765,9 @@ const docTemplate = `{ }, "status": { "type": "string" + }, + "uuid": { + "type": "string" } } }, diff --git a/src/docs/swagger.json b/src/docs/swagger.json index 52efc63..76515fc 100644 --- a/src/docs/swagger.json +++ b/src/docs/swagger.json @@ -1077,6 +1077,51 @@ } } }, + "/v1/groups/{number}/{groupid}/avatar": { + "get": { + "description": "Returns the avatar of a Signal Group.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Groups" + ], + "summary": "Returns the avatar of a Signal Group.", + "parameters": [ + { + "type": "string", + "description": "Registered Phone Number", + "name": "number", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Group ID", + "name": "groupid", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Image", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.Error" + } + } + } + } + }, "/v1/groups/{number}/{groupid}/block": { "post": { "description": "Block the specified Signal Group.", @@ -2717,6 +2762,9 @@ }, "status": { "type": "string" + }, + "uuid": { + "type": "string" } } }, diff --git a/src/docs/swagger.yaml b/src/docs/swagger.yaml index 6037705..b04baad 100644 --- a/src/docs/swagger.yaml +++ b/src/docs/swagger.yaml @@ -367,6 +367,8 @@ definitions: type: string status: type: string + uuid: + type: string type: object client.ListContactsResponse: properties: @@ -1178,6 +1180,36 @@ paths: summary: Add one or more admins to an existing Signal Group. tags: - Groups + /v1/groups/{number}/{groupid}/avatar: + get: + consumes: + - application/json + description: Returns the avatar of a Signal Group. + parameters: + - description: Registered Phone Number + in: path + name: number + required: true + type: string + - description: Group ID + in: path + name: groupid + required: true + type: string + produces: + - application/json + responses: + "200": + description: Image + schema: + type: string + "400": + description: Bad Request + schema: + $ref: '#/definitions/api.Error' + summary: Returns the avatar of a Signal Group. + tags: + - Groups /v1/groups/{number}/{groupid}/block: post: consumes: diff --git a/src/main.go b/src/main.go index 2c327b2..64e1f8d 100644 --- a/src/main.go +++ b/src/main.go @@ -210,6 +210,7 @@ func main() { groups.POST(":number", api.CreateGroup) groups.GET(":number", api.GetGroups) groups.GET(":number/:groupid", api.GetGroup) + groups.GET(":number/:groupid/avatar", api.GetGroupAvatar) groups.DELETE(":number/:groupid", api.DeleteGroup) groups.POST(":number/:groupid/block", api.BlockGroup) groups.POST(":number/:groupid/join", api.JoinGroup)