generate-emoji-data.js 2.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
  1. #!/usr/bin/env node
  2. /**
  3. * Writes packages/app/lib/emoji/emoji-data.ts for the emoji picker from the
  4. * unicode-emoji-json package (a devDependency): every base emoji (no skin-tone
  5. * variants) up to MAX_EMOJI_VERSION that UNA can store as a reaction, with its
  6. * name, grouped as in Unicode.
  7. *
  8. * node scripts/generate-emoji-data.js
  9. *
  10. * Newer emoji show as empty boxes on phones that haven't updated their OS, so
  11. * raise MAX_EMOJI_VERSION only when most devices support that version.
  12. */
  13. const fs = require('fs');
  14. const path = require('path');
  15. const MAX_EMOJI_VERSION = 15.0;
  16. const OUT = path.join(__dirname, '..', 'packages', 'app', 'lib', 'emoji', 'emoji-data.ts');
  17. /**
  18. * UNA keeps a picked emoji as `e_<hex code points>` in a varchar(32) column and takes one pictograph (with its
  19. * variation selector, skin tone and ZWJ parts), a keycap or a flag: BxDolVoteReactions::isEmoji and normalizeReaction.
  20. * Emoji it would refuse (families, couples, England and Scotland flags) stay out of the picker.
  21. */
  22. const PART = '(?:\\p{Extended_Pictographic}(?:\\u{FE0F}|[\\u{1F3FB}-\\u{1F3FF}])?)';
  23. const UNA_EMOJI = new RegExp(`^(?:${PART}(?:\\u{200D}${PART})*|[#*0-9]\\u{FE0F}?\\u{20E3}|\\p{Regional_Indicator}{2})$`, 'u');
  24. const UNA_CODE_MAX = 32;
  25. const fitsUna = (emoji) =>
  26. UNA_EMOJI.test(emoji) && ('e_' + Array.from(emoji, (c) => c.codePointAt(0).toString(16)).join('_')).length <= UNA_CODE_MAX;
  27. const groups = require('unicode-emoji-json/data-by-group.json');
  28. const { version } = require('unicode-emoji-json/package.json');
  29. const out = groups
  30. .filter((group) => group.slug !== 'component')
  31. .map((group) => ({
  32. id: group.slug,
  33. title: group.name,
  34. emojis: group.emojis
  35. .filter((e) => parseFloat(e.emoji_version) <= MAX_EMOJI_VERSION && fitsUna(e.emoji))
  36. .map((e) => [e.emoji, e.name]),
  37. }))
  38. .filter((group) => group.emojis.length > 0);
  39. const count = out.reduce((n, group) => n + group.emojis.length, 0);
  40. /** One emoji per line, so a data update reads as a readable diff. */
  41. function formatGroup(group) {
  42. const emojis = group.emojis.map((entry) => ` ${JSON.stringify(entry)},`).join('\n');
  43. return ` {\n id: ${JSON.stringify(group.id)},\n title: ${JSON.stringify(group.title)},\n emojis: [\n${emojis}\n ],\n },`;
  44. }
  45. const body = `// Generated by scripts/generate-emoji-data.js from unicode-emoji-json ${version}. Do not edit.
  46. // ${count} emoji up to Emoji ${MAX_EMOJI_VERSION.toFixed(1)}, without skin-tone variants.
  47. export type EmojiEntry = readonly [emoji: string, name: string];
  48. export type EmojiGroup = {
  49. id: string;
  50. title: string;
  51. emojis: readonly EmojiEntry[];
  52. };
  53. export const EMOJI_GROUPS: readonly EmojiGroup[] = [
  54. ${out.map(formatGroup).join('\n')}
  55. ];
  56. `;
  57. fs.mkdirSync(path.dirname(OUT), { recursive: true });
  58. fs.writeFileSync(OUT, body);
  59. console.log(`${path.relative(process.cwd(), OUT)}: ${out.length} groups, ${count} emoji, ${Math.round(body.length / 1024)} KB`);