185 lines
5.7 KiB
JavaScript
185 lines
5.7 KiB
JavaScript
import { emojiRegex } from "./emoji-regex.js";
|
|
import { fetchMeta } from "./fetch-meta.js";
|
|
import { Emojis, UserEmojis, UserGroups, Users } from "../models/index.js";
|
|
import { toPunyNullable } from "./convert-host.js";
|
|
import { IsNull } from "typeorm";
|
|
import { resolveUser } from "../remote/resolve-user.js";
|
|
const legacies = new Map([
|
|
[
|
|
"like",
|
|
"👍"
|
|
],
|
|
[
|
|
"love",
|
|
"❤️"
|
|
],
|
|
[
|
|
"laugh",
|
|
"😆"
|
|
],
|
|
[
|
|
"hmm",
|
|
"🤔"
|
|
],
|
|
[
|
|
"surprise",
|
|
"😮"
|
|
],
|
|
[
|
|
"congrats",
|
|
"🎉"
|
|
],
|
|
[
|
|
"angry",
|
|
"💢"
|
|
],
|
|
[
|
|
"confused",
|
|
"😥"
|
|
],
|
|
[
|
|
"rip",
|
|
"😇"
|
|
],
|
|
[
|
|
"pudding",
|
|
"🍮"
|
|
],
|
|
[
|
|
"star",
|
|
"⭐"
|
|
]
|
|
]);
|
|
export async function getFallbackReaction() {
|
|
const meta = await fetchMeta();
|
|
return meta.defaultReaction;
|
|
}
|
|
export function convertLegacyReactions(reactions) {
|
|
const _reactions = new Map();
|
|
const decodedReactions = new Map();
|
|
for(const reaction in reactions){
|
|
if (reactions[reaction] <= 0) continue;
|
|
let decodedReaction;
|
|
if (decodedReactions.has(reaction)) {
|
|
decodedReaction = decodedReactions.get(reaction);
|
|
} else {
|
|
decodedReaction = decodeReaction(reaction);
|
|
decodedReactions.set(reaction, decodedReaction);
|
|
}
|
|
let emoji = legacies.get(decodedReaction.reaction);
|
|
if (emoji) {
|
|
_reactions.set(emoji, (_reactions.get(emoji) || 0) + reactions[reaction]);
|
|
} else {
|
|
_reactions.set(reaction, (_reactions.get(reaction) || 0) + reactions[reaction]);
|
|
}
|
|
}
|
|
const _reactions2 = new Map();
|
|
for (const [reaction, count] of _reactions){
|
|
const decodedReaction = decodedReactions.get(reaction);
|
|
_reactions2.set(decodedReaction.reaction, count);
|
|
}
|
|
return Object.fromEntries(_reactions2);
|
|
}
|
|
export async function toDbReaction(reaction, reacterHost, recurse = true) {
|
|
if (!reaction) return await getFallbackReaction();
|
|
reacterHost = toPunyNullable(reacterHost);
|
|
// Convert string-type reactions to unicode
|
|
const emoji = legacies.get(reaction) || (reaction === "♥️" ? "❤️" : null);
|
|
if (emoji) return emoji;
|
|
// Allow unicode reactions
|
|
const match = emojiRegex.exec(reaction);
|
|
if (match) {
|
|
const unicode = match[0];
|
|
return unicode;
|
|
}
|
|
const custom = reaction.match(/^:([^:\s]+):$/);
|
|
if (custom) {
|
|
const decoded = decodeReaction(reaction);
|
|
if (decoded.name) {
|
|
const groupCustom = decoded.name.match(/^([a-z0-9_]{1,64})@@([a-zA-Z0-9_]{1,64})$/);
|
|
if (groupCustom) {
|
|
const group = await UserGroups.findOneBy({
|
|
username: groupCustom[2].toLowerCase()
|
|
});
|
|
const groupEmoji = group ? await UserEmojis.findOneBy({
|
|
name: groupCustom[1],
|
|
userGroupId: group.id,
|
|
glyph: false
|
|
}) : null;
|
|
if (groupEmoji) return `:${groupCustom[1]}@@${group.username}:`;
|
|
}
|
|
const emoji = await Emojis.findOneBy({
|
|
host: decoded.host ?? reacterHost ?? IsNull(),
|
|
name: decoded.name
|
|
});
|
|
if (emoji) return emoji.host ? `:${emoji.name}@${emoji.host}:` : `:${emoji.name}:`;
|
|
}
|
|
if (await isUserReaction(reaction)) return reaction;
|
|
}
|
|
return recurse && reacterHost == null && reaction !== null ? await toDbReaction(`:${reaction}:`, reacterHost, false) : await getFallbackReaction();
|
|
}
|
|
export function decodeReaction(str) {
|
|
const custom = str.match(/^:([^:\s]+):$/);
|
|
if (custom) {
|
|
const body = custom[1];
|
|
const parts = body.split("@");
|
|
const name = parts[0];
|
|
const host = parts.length === 2 ? parts[1] || null : null;
|
|
return {
|
|
reaction: host ? `:${name}@${host}:` : str,
|
|
name,
|
|
host
|
|
};
|
|
}
|
|
return {
|
|
reaction: str,
|
|
name: undefined,
|
|
host: undefined
|
|
};
|
|
}
|
|
async function isUserReaction(reaction) {
|
|
const body = reaction.slice(1, -1);
|
|
const icon = body.match(/^@([^@:\s]+)(?:@([^@:\s]+))?$/);
|
|
if (icon) {
|
|
return await findUser(icon[1], icon[2] ?? null) != null;
|
|
}
|
|
if (/^@@[a-zA-Z0-9_]{1,64}$/.test(body)) return false;
|
|
const groupCustom = body.match(/^([a-z0-9_]{1,64})@@([a-zA-Z0-9_]{1,64})$/);
|
|
if (groupCustom) {
|
|
const group = await UserGroups.findOneBy({
|
|
username: groupCustom[2].toLowerCase()
|
|
});
|
|
if (!group) return false;
|
|
return await UserEmojis.findOneBy({
|
|
name: groupCustom[1],
|
|
userGroupId: group.id,
|
|
glyph: false
|
|
}) != null;
|
|
}
|
|
const parts = body.split("@");
|
|
if (parts.length !== 2 && parts.length !== 3) return false;
|
|
const [name, username, host] = parts;
|
|
if (!name || !username) return false;
|
|
const user = await findUser(username, host ?? null);
|
|
if (!user) return false;
|
|
return await UserEmojis.findOneBy({
|
|
name,
|
|
userId: user.id
|
|
}) != null;
|
|
}
|
|
async function findUser(username, host) {
|
|
const normalizedHost = toPunyNullable(host);
|
|
const user = await Users.findOneBy({
|
|
usernameLower: username.toLowerCase(),
|
|
host: normalizedHost ?? IsNull()
|
|
});
|
|
if (user) return user;
|
|
if (normalizedHost == null) return null;
|
|
return resolveUser(username, normalizedHost).catch(()=>null);
|
|
}
|
|
export function convertLegacyReaction(reaction) {
|
|
const decoded = decodeReaction(reaction).reaction;
|
|
if (legacies.has(decoded)) return legacies.get(decoded);
|
|
return decoded;
|
|
}
|