gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

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 }