diff --git a/README.md b/README.md index 9566bc7..8b7f193 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ At the moment, the following functionality is exposed via REST: - Receive messages - Link devices - Create/Liste/Remove groups +- List/Serve/Delete attachments +- Update profile ## Examples diff --git a/src/api/api.go b/src/api/api.go index 30a08ca..f7aa643 100644 --- a/src/api/api.go +++ b/src/api/api.go @@ -72,6 +72,11 @@ type CreateGroup struct { Id string `json:"id"` } +type UpdateProfileRequest struct { + Name string `json:"name"` + Base64Avatar string `json:"base64_avatar"` +} + func convertInternalGroupIdToGroupId(internalId string) string { return groupPrefix + base64.StdEncoding.EncodeToString([]byte(internalId)) } @@ -289,12 +294,14 @@ func runSignalCli(wait bool, args []string, stdin string) (string, error) { type Api struct { signalCliConfig string attachmentTmpDir string + avatarTmpDir string } -func NewApi(signalCliConfig string, attachmentTmpDir string) *Api { +func NewApi(signalCliConfig string, attachmentTmpDir string, avatarTmpDir string) *Api { return &Api{ signalCliConfig: signalCliConfig, attachmentTmpDir: attachmentTmpDir, + avatarTmpDir: avatarTmpDir, } } @@ -694,7 +701,7 @@ func (a *Api) GetAttachments(c *gin.Context) { // @Tags Attachments // @Description Remove the attachment with the given id from filesystem. // @Produce json -// @Success 200 {string} OK +// @Success 204 {string} OK // @Failure 400 {object} Error // @Param attachment path string true "Attachment ID" // @Router /v1/attachments/{attachment} [delete] @@ -759,3 +766,87 @@ func (a *Api) ServeAttachment(c *gin.Context) { return } } + +// @Summary Update Profile. +// @Tags Profiles +// @Description Set your name and optional an avatar. +// @Produce json +// @Success 204 {string} OK +// @Failure 400 {object} Error +// @Param data body UpdateProfileRequest true "Profile Data" +// @Param number path string true "Registered Phone Number" +// @Router /v1/profiles/{number} [put] +func (a *Api) UpdateProfile(c *gin.Context) { + number := c.Param("number") + + var req UpdateProfileRequest + err := c.BindJSON(&req) + if err != nil { + c.JSON(400, Error{Msg: "Couldn't process request - invalid request"}) + log.Error(err.Error()) + return + } + + if req.Name == "" { + c.JSON(400, Error{Msg: "Couldn't process request - profile name missing"}) + return + } + cmd := []string{"--config", a.signalCliConfig, "-u", number, "updateProfile", "--name", req.Name} + + avatarTmpPaths := []string{} + if req.Base64Avatar == "" { + cmd = append(cmd, "--remove-avatar") + } else { + u, err := uuid.NewV4() + if err != nil { + c.JSON(400, Error{Msg: err.Error()}) + return + } + + avatarBytes, err := base64.StdEncoding.DecodeString(req.Base64Avatar) + if err != nil { + c.JSON(400, Error{Msg: "Couldn't decode base64 encoded avatar"}) + return + } + + fType, err := filetype.Get(avatarBytes) + if err != nil { + c.JSON(400, Error{Msg: err.Error()}) + return + } + + avatarTmpPath := a.avatarTmpDir + u.String() + "." + fType.Extension + + f, err := os.Create(avatarTmpPath) + if err != nil { + c.JSON(400, Error{Msg: err.Error()}) + return + } + defer f.Close() + + if _, err := f.Write(avatarBytes); err != nil { + cleanupTmpFiles(avatarTmpPaths) + c.JSON(400, Error{Msg: err.Error()}) + return + } + if err := f.Sync(); err != nil { + cleanupTmpFiles(avatarTmpPaths) + c.JSON(400, Error{Msg: err.Error()}) + return + } + f.Close() + + cmd = append(cmd, []string{"--avatar", avatarTmpPath}...) + avatarTmpPaths = append(avatarTmpPaths, avatarTmpPath) + } + + _, err = runSignalCli(true, cmd, "") + if err != nil { + cleanupTmpFiles(avatarTmpPaths) + c.JSON(400, Error{Msg: err.Error()}) + return + } + + cleanupTmpFiles(avatarTmpPaths) + c.Status(http.StatusNoContent) +} diff --git a/src/docs/docs.go b/src/docs/docs.go index 3028120..d592327 100644 --- a/src/docs/docs.go +++ b/src/docs/docs.go @@ -127,8 +127,8 @@ var doc = `{ } ], "responses": { - "200": { - "description": "OK", + "204": { + "description": "No Content", "schema": { "type": "string" } @@ -264,6 +264,50 @@ var doc = `{ } } }, + "/v1/profiles/{number}": { + "put": { + "description": "Set your name and optional an avatar.", + "produces": [ + "application/json" + ], + "tags": [ + "Profiles" + ], + "summary": "Update Profile.", + "parameters": [ + { + "description": "Profile Data", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api.UpdateProfileRequest" + } + }, + { + "type": "string", + "description": "Registered Phone Number", + "name": "number", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.Error" + } + } + } + } + }, "/v1/qrcodelink": { "get": { "description": "Link device and generate QR code", @@ -609,6 +653,17 @@ var doc = `{ } } }, + "api.UpdateProfileRequest": { + "type": "object", + "properties": { + "base64_avatar": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, "api.VerifyNumberSettings": { "type": "object", "properties": { @@ -638,6 +693,10 @@ var doc = `{ { "description": "List and Delete Attachments.", "name": "Attachments" + }, + { + "description": "Update Profile.", + "name": "Profiles" } ] }` diff --git a/src/docs/swagger.json b/src/docs/swagger.json index 2f1c557..7a1dd58 100644 --- a/src/docs/swagger.json +++ b/src/docs/swagger.json @@ -112,8 +112,8 @@ } ], "responses": { - "200": { - "description": "OK", + "204": { + "description": "No Content", "schema": { "type": "string" } @@ -249,6 +249,50 @@ } } }, + "/v1/profiles/{number}": { + "put": { + "description": "Set your name and optional an avatar.", + "produces": [ + "application/json" + ], + "tags": [ + "Profiles" + ], + "summary": "Update Profile.", + "parameters": [ + { + "description": "Profile Data", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api.UpdateProfileRequest" + } + }, + { + "type": "string", + "description": "Registered Phone Number", + "name": "number", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/api.Error" + } + } + } + } + }, "/v1/qrcodelink": { "get": { "description": "Link device and generate QR code", @@ -594,6 +638,17 @@ } } }, + "api.UpdateProfileRequest": { + "type": "object", + "properties": { + "base64_avatar": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, "api.VerifyNumberSettings": { "type": "object", "properties": { @@ -623,6 +678,10 @@ { "description": "List and Delete Attachments.", "name": "Attachments" + }, + { + "description": "Update Profile.", + "name": "Profiles" } ] } \ No newline at end of file diff --git a/src/docs/swagger.yaml b/src/docs/swagger.yaml index 05184cf..4ec235d 100644 --- a/src/docs/swagger.yaml +++ b/src/docs/swagger.yaml @@ -66,6 +66,13 @@ definitions: type: string type: array type: object + api.UpdateProfileRequest: + properties: + base64_avatar: + type: string + name: + type: string + type: object api.VerifyNumberSettings: properties: pin: @@ -123,8 +130,8 @@ paths: produces: - application/json responses: - "200": - description: OK + "204": + description: No Content schema: type: string "400": @@ -237,6 +244,35 @@ paths: summary: Delete a Signal Group. tags: - Groups + /v1/profiles/{number}: + put: + description: Set your name and optional an avatar. + parameters: + - description: Profile Data + in: body + name: data + required: true + schema: + $ref: '#/definitions/api.UpdateProfileRequest' + - description: Registered Phone Number + in: path + name: number + required: true + type: string + produces: + - application/json + responses: + "204": + description: No Content + schema: + type: string + "400": + description: Bad Request + schema: + $ref: '#/definitions/api.Error' + summary: Update Profile. + tags: + - Profiles /v1/qrcodelink: get: description: Link device and generate QR code @@ -408,3 +444,5 @@ tags: name: Messages - description: List and Delete Attachments. name: Attachments +- description: Update Profile. + name: Profiles diff --git a/src/main.go b/src/main.go index 2d7c0ed..75c299b 100644 --- a/src/main.go +++ b/src/main.go @@ -34,17 +34,21 @@ import ( // @tag.name Attachments // @tag.description List and Delete Attachments. +// @tag.name Profiles +// @tag.description Update Profile. + // @host 127.0.0.1:8080 // @BasePath / func main() { signalCliConfig := flag.String("signal-cli-config", "/home/.local/share/signal-cli/", "Config directory where signal-cli config is stored") attachmentTmpDir := flag.String("attachment-tmp-dir", "/tmp/", "Attachment tmp directory") + avatarTmpDir := flag.String("avatar-tmp-dir", "/tmp/", "Avatar tmp directory") flag.Parse() router := gin.Default() log.Info("Started Signal Messenger REST API") - api := api.NewApi(*signalCliConfig, *attachmentTmpDir) + api := api.NewApi(*signalCliConfig, *attachmentTmpDir, *avatarTmpDir) v1 := router.Group("/v1") { about := v1.Group("/about") @@ -86,6 +90,11 @@ func main() { attachments.DELETE(":attachment", api.RemoveAttachment) attachments.GET(":attachment", api.ServeAttachment) } + + profiles := v1.Group("profiles") + { + profiles.PUT(":number", api.UpdateProfile) + } } v2 := router.Group("/v2")