custom-emoji.js (4883B)
1 /* 2 GoToSocial 3 Copyright (C) GoToSocial Authors admin@gotosocial.org 4 SPDX-License-Identifier: AGPL-3.0-or-later 5 6 This program is free software: you can redistribute it and/or modify 7 it under the terms of the GNU Affero General Public License as published by 8 the Free Software Foundation, either version 3 of the License, or 9 (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful, 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU Affero General Public License for more details. 15 16 You should have received a copy of the GNU Affero General Public License 17 along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 "use strict"; 21 22 const Promise = require("bluebird"); 23 24 const { unwrapRes } = require("../lib"); 25 26 module.exports = (build) => ({ 27 listEmoji: build.query({ 28 query: (params = {}) => ({ 29 url: "/api/v1/admin/custom_emojis", 30 params: { 31 limit: 0, 32 ...params 33 } 34 }), 35 providesTags: (res) => 36 res 37 ? [...res.map((emoji) => ({ type: "Emoji", id: emoji.id })), { type: "Emoji", id: "LIST" }] 38 : [{ type: "Emoji", id: "LIST" }] 39 }), 40 41 getEmoji: build.query({ 42 query: (id) => ({ 43 url: `/api/v1/admin/custom_emojis/${id}` 44 }), 45 providesTags: (res, error, id) => [{ type: "Emoji", id }] 46 }), 47 48 addEmoji: build.mutation({ 49 query: (form) => { 50 return { 51 method: "POST", 52 url: `/api/v1/admin/custom_emojis`, 53 asForm: true, 54 body: form, 55 discardEmpty: true 56 }; 57 }, 58 invalidatesTags: (res) => 59 res 60 ? [{ type: "Emoji", id: "LIST" }, { type: "Emoji", id: res.id }] 61 : [{ type: "Emoji", id: "LIST" }] 62 }), 63 64 editEmoji: build.mutation({ 65 query: ({ id, ...patch }) => { 66 return { 67 method: "PATCH", 68 url: `/api/v1/admin/custom_emojis/${id}`, 69 asForm: true, 70 body: { 71 type: "modify", 72 ...patch 73 } 74 }; 75 }, 76 invalidatesTags: (res) => 77 res 78 ? [{ type: "Emoji", id: "LIST" }, { type: "Emoji", id: res.id }] 79 : [{ type: "Emoji", id: "LIST" }] 80 }), 81 82 deleteEmoji: build.mutation({ 83 query: (id) => ({ 84 method: "DELETE", 85 url: `/api/v1/admin/custom_emojis/${id}` 86 }), 87 invalidatesTags: (res, error, id) => [{ type: "Emoji", id }] 88 }), 89 90 searchStatusForEmoji: build.mutation({ 91 queryFn: (url, api, _extraOpts, baseQuery) => { 92 return Promise.try(() => { 93 return baseQuery({ 94 url: `/api/v2/search?q=${encodeURIComponent(url)}&resolve=true&limit=1` 95 }).then(unwrapRes); 96 }).then((searchRes) => { 97 return emojiFromSearchResult(searchRes); 98 }).then(({ type, domain, list }) => { 99 const state = api.getState(); 100 if (domain == new URL(state.oauth.instance).host) { 101 throw "LOCAL_INSTANCE"; 102 } 103 104 // search for every mentioned emoji with the admin api to get their ID 105 return Promise.map(list, (emoji) => { 106 return baseQuery({ 107 url: `/api/v1/admin/custom_emojis`, 108 params: { 109 filter: `domain:${domain},shortcode:${emoji.shortcode}`, 110 limit: 1 111 } 112 }).then((unwrapRes)).then((list) => list[0]); 113 }, { concurrency: 5 }).then((listWithIDs) => { 114 return { 115 data: { 116 type, 117 domain, 118 list: listWithIDs 119 } 120 }; 121 }); 122 }).catch((e) => { 123 return { error: e }; 124 }); 125 } 126 }), 127 128 patchRemoteEmojis: build.mutation({ 129 queryFn: ({ action, ...formData }, _api, _extraOpts, baseQuery) => { 130 const data = []; 131 const errors = []; 132 133 return Promise.each(formData.selectedEmoji, (emoji) => { 134 return Promise.try(() => { 135 let body = { 136 type: action 137 }; 138 139 if (action == "copy") { 140 body.shortcode = emoji.shortcode; 141 if (formData.category.trim().length != 0) { 142 body.category = formData.category; 143 } 144 } 145 146 return baseQuery({ 147 method: "PATCH", 148 url: `/api/v1/admin/custom_emojis/${emoji.id}`, 149 asForm: true, 150 body: body 151 }).then(unwrapRes); 152 }).then((res) => { 153 data.push([emoji.id, res]); 154 }).catch((e) => { 155 let msg = e.message ?? e; 156 if (e.data.error) { 157 msg = e.data.error; 158 } 159 errors.push([emoji.shortcode, msg]); 160 }); 161 }).then(() => { 162 if (errors.length == 0) { 163 return { data }; 164 } else { 165 return { 166 error: errors 167 }; 168 } 169 }); 170 }, 171 invalidatesTags: () => [{ type: "Emoji", id: "LIST" }] 172 }) 173 }); 174 175 function emojiFromSearchResult(searchRes) { 176 /* Parses the search response, prioritizing a toot result, 177 and returns referenced custom emoji 178 */ 179 let type; 180 181 if (searchRes.statuses.length > 0) { 182 type = "statuses"; 183 } else if (searchRes.accounts.length > 0) { 184 type = "accounts"; 185 } else { 186 throw "NONE_FOUND"; 187 } 188 189 let data = searchRes[type][0]; 190 191 return { 192 type, 193 domain: (new URL(data.url)).host, // to get WEB_DOMAIN, see https://github.com/superseriousbusiness/gotosocial/issues/1225 194 list: data.emojis 195 }; 196 }