emojiupdate.go (7065B)
1 // GoToSocial 2 // Copyright (C) GoToSocial Authors admin@gotosocial.org 3 // SPDX-License-Identifier: AGPL-3.0-or-later 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package admin 19 20 import ( 21 "errors" 22 "fmt" 23 "net/http" 24 "strings" 25 26 "github.com/gin-gonic/gin" 27 apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" 28 apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" 29 "github.com/superseriousbusiness/gotosocial/internal/config" 30 "github.com/superseriousbusiness/gotosocial/internal/gtserror" 31 "github.com/superseriousbusiness/gotosocial/internal/oauth" 32 "github.com/superseriousbusiness/gotosocial/internal/validate" 33 ) 34 35 // EmojiPATCHHandler swagger:operation PATCH /api/v1/admin/custom_emojis/{id} emojiUpdate 36 // 37 // Perform admin action on a local or remote emoji known to this instance. 38 // 39 // Action performed depends upon the action `type` provided. 40 // 41 // `disable`: disable a REMOTE emoji from being used/displayed on this instance. Does not work for local emojis. 42 // 43 // `copy`: copy a REMOTE emoji to this instance. When doing this action, a shortcode MUST be provided, and it must 44 // be unique among emojis already present on this instance. A category MAY be provided, and the copied emoji will then 45 // be put into the provided category. 46 // 47 // `modify`: modify a LOCAL emoji. You can provide a new image for the emoji and/or update the category. 48 // 49 // Local emojis cannot be deleted using this endpoint. To delete a local emoji, check DELETE /api/v1/admin/custom_emojis/{id} instead. 50 // 51 // --- 52 // tags: 53 // - admin 54 // 55 // consumes: 56 // - multipart/form-data 57 // 58 // produces: 59 // - application/json 60 // 61 // parameters: 62 // - 63 // name: id 64 // type: string 65 // description: The id of the emoji. 66 // in: path 67 // required: true 68 // - 69 // name: type 70 // in: formData 71 // description: |- 72 // Type of action to be taken. One of: (`disable`, `copy`, `modify`). 73 // For REMOTE emojis, `copy` or `disable` are supported. 74 // For LOCAL emojis, only `modify` is supported. 75 // type: string 76 // required: true 77 // - 78 // name: shortcode 79 // in: formData 80 // description: >- 81 // The code to use for the emoji, which will be used by instance denizens to select it. 82 // This must be unique on the instance. Works for the `copy` action type only. 83 // type: string 84 // pattern: \w{2,30} 85 // - 86 // name: image 87 // in: formData 88 // description: >- 89 // A new png or gif image to use for the emoji. Animated pngs work too! 90 // To ensure compatibility with other fedi implementations, emoji size limit is 50kb by default. 91 // Works for LOCAL emojis only. 92 // type: file 93 // - 94 // name: category 95 // in: formData 96 // description: >- 97 // Category in which to place the emoji. 64 characters or less. 98 // If a category with the given name doesn't exist yet, it will be created. 99 // type: string 100 // 101 // security: 102 // - OAuth2 Bearer: 103 // - admin 104 // 105 // responses: 106 // '200': 107 // description: The updated emoji. 108 // schema: 109 // "$ref": "#/definitions/adminEmoji" 110 // '400': 111 // description: bad request 112 // '401': 113 // description: unauthorized 114 // '403': 115 // description: forbidden 116 // '404': 117 // description: not found 118 // '406': 119 // description: not acceptable 120 // '500': 121 // description: internal server error 122 func (m *Module) EmojiPATCHHandler(c *gin.Context) { 123 authed, err := oauth.Authed(c, true, true, true, true) 124 if err != nil { 125 apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1) 126 return 127 } 128 129 if !*authed.User.Admin { 130 err := fmt.Errorf("user %s not an admin", authed.User.ID) 131 apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGetV1) 132 return 133 } 134 135 if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { 136 apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1) 137 return 138 } 139 140 emojiID := c.Param(IDKey) 141 if emojiID == "" { 142 err := errors.New("no emoji id specified") 143 apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) 144 return 145 } 146 147 form := &apimodel.EmojiUpdateRequest{} 148 if err := c.ShouldBind(form); err != nil { 149 apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) 150 return 151 } 152 153 if err := validateUpdateEmoji(form); err != nil { 154 apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) 155 return 156 } 157 158 emoji, errWithCode := m.processor.Admin().EmojiUpdate(c.Request.Context(), emojiID, form) 159 if errWithCode != nil { 160 apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) 161 return 162 } 163 164 c.JSON(http.StatusOK, emoji) 165 } 166 167 // do a first pass on the form here 168 func validateUpdateEmoji(form *apimodel.EmojiUpdateRequest) error { 169 // check + normalize update type so we don't need 170 // to do this trimming + lowercasing again later 171 switch strings.TrimSpace(strings.ToLower(string(form.Type))) { 172 case string(apimodel.EmojiUpdateDisable): 173 // no params required for this one, so don't bother checking 174 form.Type = apimodel.EmojiUpdateDisable 175 case string(apimodel.EmojiUpdateCopy): 176 // need at least a valid shortcode when doing a copy 177 if form.Shortcode == nil { 178 return errors.New("emoji action type was 'copy' but no shortcode was provided") 179 } 180 181 if err := validate.EmojiShortcode(*form.Shortcode); err != nil { 182 return err 183 } 184 185 // category optional during copy 186 if form.CategoryName != nil { 187 if err := validate.EmojiCategory(*form.CategoryName); err != nil { 188 return err 189 } 190 } 191 192 form.Type = apimodel.EmojiUpdateCopy 193 case string(apimodel.EmojiUpdateModify): 194 // need either image or category name for modify 195 hasImage := form.Image != nil && form.Image.Size != 0 196 hasCategoryName := form.CategoryName != nil 197 if !hasImage && !hasCategoryName { 198 return errors.New("emoji action type was 'modify' but no image or category name was provided") 199 } 200 201 if hasImage { 202 maxSize := config.GetMediaEmojiLocalMaxSize() 203 if form.Image.Size > int64(maxSize) { 204 return fmt.Errorf("emoji image too large: image is %dKB but size limit for custom emojis is %dKB", form.Image.Size/1024, maxSize/1024) 205 } 206 } 207 208 if hasCategoryName { 209 if err := validate.EmojiCategory(*form.CategoryName); err != nil { 210 return err 211 } 212 } 213 214 form.Type = apimodel.EmojiUpdateModify 215 default: 216 return errors.New("emoji action type must be one of 'disable', 'copy', 'modify'") 217 } 218 219 return nil 220 }