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; }