import data from 'emojibase-data/en/data.json'; import emojibaseShortcodes from 'emojibase-data/en/shortcodes/emojibase.json'; import joypixelsShortcodes from 'emojibase-data/en/shortcodes/joypixels.json'; // Map emojibase group numbers to our category keys const groupToCategory: Record = { 0: 'people', // smileys & emotion 1: 'people', // people & body 2: 'people', // component - skin tones etc (we skip these) 3: 'nature', // animals & nature 4: 'food', // food & drink 5: 'travel', // travel & places 6: 'activity', // activities 7: 'objects', // objects 8: 'symbols', // symbols 9: 'flags', // flags }; export type EmojiEntry = { name: string; uc_base: string; uc_output: string; uc_match: string; uc_greedy: string; shortnames: string[]; category: string; emojiPackage: string; unicode: string; }; function hexFromEmoji(emoji: string): string { return [...emoji].map((cp) => cp.codePointAt(0)!.toString(16)).join('-'); } function isRegionalIndicator(hexcode: string): boolean { if (hexcode.includes('-')) return false; const cp = parseInt(hexcode, 16); return cp >= 0x1f1e6 && cp <= 0x1f1ff; } function getShortcodes(hexcode: string): string[] { const entry = (joypixelsShortcodes as Record)[hexcode] ?? (emojibaseShortcodes as Record)[hexcode]; if (!entry) return []; return Array.isArray(entry) ? entry : [entry]; } function buildEmojiData() { const emojiList: Record = {}; const emojisByCategory: Record = { people: [], nature: [], food: [], activity: [], travel: [], objects: [], symbols: [], flags: [], }; const toneList: Record = {}; const bareAliases: [string, string][] = []; for (const emojiData of data) { // Skip component group (skin tones, hair styles) if (emojiData.group === 2) continue; const isRegional = isRegionalIndicator(emojiData.hexcode); const category = groupToCategory[emojiData.group ?? -1] ?? (isRegional ? 'flags' : undefined); if (!category) continue; const codes = getShortcodes(emojiData.hexcode); if (codes.length === 0) continue; const primaryShortcode = codes[0]; const altShortcodes = codes.slice(1).map((s) => `:${s}:`); const hex = hexFromEmoji(emojiData.emoji); const bare = emojiData.emoji.replace(/\uFE0F/g, ''); const entry: EmojiEntry = { name: primaryShortcode, uc_base: hex, uc_output: hex, uc_match: hex, uc_greedy: hex, shortnames: altShortcodes, category, emojiPackage: 'native', unicode: emojiData.emoji, }; const key = `:${primaryShortcode}:`; emojiList[key] = entry; if (emojiData.type === 1 && bare !== emojiData.emoji) { bareAliases.push([bare, key]); } // Only add to category if it's NOT a skin tone variant if (!emojiData.tone && !isRegional) { emojisByCategory[category].push(primaryShortcode); } // Register alternate shortcodes for (const alt of altShortcodes) { emojiList[alt] = entry; } // Track skin tone support if (emojiData.skins && emojiData.skins.length > 0) { toneList[primaryShortcode] = 1; // Register skin tone variants for (const skin of emojiData.skins) { if (!skin.tone) continue; const tones = Array.isArray(skin.tone) ? skin.tone : [skin.tone]; const skinHex = hexFromEmoji(skin.emoji); const skinEntry: EmojiEntry = { name: primaryShortcode, uc_base: skinHex, uc_output: skinHex, uc_match: skinHex, uc_greedy: skinHex, shortnames: [], category, emojiPackage: 'native', unicode: skin.emoji, }; const toneShortcodes = getShortcodes(skin.hexcode); const fallbackShortcode = `${primaryShortcode}_tone${tones.join('-')}`; for (const code of [...toneShortcodes, fallbackShortcode]) { emojiList[`:${code}:`] = skinEntry; } } } } return { emojiList, emojisByCategory, toneList, bareAliases }; } // Build once and cache let _cached: ReturnType | null = null; export function getEmojiData() { if (!_cached) { _cached = buildEmojiData(); } return _cached; }