Last active
July 7, 2024 21:16
-
-
Save nurdism/de2e7e42c89b72913773461f8a91fad7 to your computer and use it in GitHub Desktop.
Emoji CSS tile sheet and keywords generator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as fs from 'fs' | |
import * as path from 'path' | |
import sharp from 'sharp' | |
import { fileURLToPath } from 'url' | |
import emoji from 'emoji-datasource-twitter/emoji.json' | |
import converter from 'discord-emoji-converter' | |
import emojilib from 'emojilib' | |
export interface DataGroupList { | |
id: string | |
name: string | |
emojis: string[] | |
} | |
export interface DataEmoji { | |
unicode?: string | |
variants?: string[] | |
} | |
export interface EmojiData { | |
groups: DataGroupList[] | |
keywords: Record<string, string[]> | |
emojis: Record<string, DataEmoji> | |
} | |
const __filename = fileURLToPath(import.meta.url) | |
const __dirname = path.dirname(__filename) | |
const datasource: EmojiDatasource[] = emoji as EmojiDatasource[] | |
interface EmojiDatasource { | |
name: string | |
unified: string | |
non_qualified: string | null | |
docomo: string | null | |
au: string | null | |
softbank: string | null | |
google: string | null | |
image: string | |
sheet_x: number | |
sheet_y: number | |
short_name: string | |
short_names: string[] | |
text: string | null | |
texts: string | null | |
category: string | |
sort_order: number | |
added_in: string | |
has_img_apple: boolean | |
has_img_google: boolean | |
has_img_twitter: boolean | |
has_img_facebook: boolean | |
skin_variations: { | |
[id: string]: { | |
unified: string | |
image: string | |
sheet_x: number | |
sheet_y: number | |
added_in: string | |
has_img_apple: boolean | |
has_img_google: boolean | |
has_img_twitter: boolean | |
has_img_facebook: boolean | |
} | |
} | |
obsoletes: string | |
obsoleted_by: string | |
} | |
const SHEET_COLUMNS = 61 | |
const MULTIPLY = 100 / SHEET_COLUMNS | |
const css: string[] = [] | |
const keywords: Record<string, string[]> = {} | |
const emojis: Record<string, DataEmoji> = {} | |
const groups: Record<string, string[]> = {} | |
const bypass = ['skin_tone_2', 'skin_tone_3', 'skin_tone_4', 'skin_tone_5', 'skin_tone_6', 'eye_in_speech_bubble'] | |
for (const source of datasource) { | |
const unified = source.unified.split('-').map((v) => v.toLowerCase()) | |
let short_name = source.short_name.replace(/\-/g, '_').toLowerCase() | |
let char = String.fromCodePoint(...source.unified.split('-').map((v) => parseInt(v, 16))) | |
if (bypass.includes(short_name)) { | |
console.log(short_name, 'bypassing...') | |
continue | |
} | |
if (!source.has_img_twitter) { | |
console.log(short_name, 'not available for set twitter') | |
continue | |
} | |
let id = short_name | |
try { | |
let discord_id = converter.getShortcode(char).replace(/:/g, '') | |
if (discord_id) { | |
id = discord_id | |
} | |
} catch (err) { | |
console.log(`${char} :${id}: has no discord id!`) | |
} | |
// keywords | |
let words: string[] = [] | |
for (const id of Object.keys(emojilib)) { | |
if (unified.includes(id.codePointAt(0)!.toString(16))) { | |
words = [id, ...emojilib[id]] | |
break | |
} | |
} | |
if (words.length == 0) { | |
console.log(id, 'no keywords') | |
} | |
for (let name of source.short_names) { | |
name = name.replace('-', '_').toLowerCase() | |
if (!words.includes(name)) { | |
words.push(name) | |
} | |
} | |
keywords[id] = words | |
// keywords | |
let group = '' | |
switch (source.category) { | |
case 'Symbols': | |
group = 'symbols' | |
break | |
case 'Activities': | |
group = 'activity' | |
break | |
case 'Flags': | |
group = 'flags' | |
break | |
case 'Travel & Places': | |
group = 'travel' | |
break | |
case 'Food & Drink': | |
group = 'food' | |
break | |
case 'Animals & Nature': | |
group = 'nature' | |
break | |
case 'People & Body': | |
group = 'people' | |
break | |
case 'Smileys & Emotion': | |
group = 'emotion' | |
break | |
case 'Objects': | |
group = 'objects' | |
break | |
case 'Skin Tones': | |
continue | |
default: | |
console.log(`unknown category ${source.category}`) | |
continue | |
} | |
const emoji: DataEmoji = {} | |
if (source.unified) { | |
emoji.unicode = source.unified.toLowerCase() | |
} | |
if (source.skin_variations) { | |
emoji.variants = Object.values(source.skin_variations).map((v) => v.unified.toLowerCase()) | |
} | |
if (!groups[group]) { | |
groups[group] = [id] | |
} else { | |
groups[group].push(id) | |
} | |
if (!emoji.unicode || emojis[id] !== undefined) { | |
throw new Error('duplicate emoji') | |
} | |
// list | |
emojis[id] = emoji | |
// css | |
// prettier-ignore | |
css.push(`&[data-emoji='${id}'] {background-position:${MULTIPLY * source.sheet_x}% ${MULTIPLY * source.sheet_y}%;content-visibility:hidden;}`) | |
} | |
fs.writeFile( | |
path.join(__dirname, 'emoji.scss'), | |
` | |
.emoji { | |
display: inline-block; | |
background-size: ${SHEET_COLUMNS * 100}%; | |
background-image: url('sheet.webp'); | |
background-repeat: no-repeat; | |
vertical-align: bottom; | |
height: 40px; | |
width: 40px; | |
&.blank{background-image: none;} | |
${css.map((v) => ` ${v}`).join('\n')} | |
} | |
`, | |
() => { | |
console.log('_emoji.scss done') | |
}, | |
) | |
for (const k of Object.keys(groups)) { | |
groups[k] = groups[k].filter((item, pos) => groups[k].indexOf(item) == pos) | |
} | |
const people = [...groups['people'], ...groups['emotion']] | |
const data: EmojiData = { | |
groups: [ | |
/* | |
{ | |
id: 'emotion', | |
name: 'Emotion', | |
list: groups['emotion'] ? groups['emotion'] : [], | |
}, | |
*/ | |
{ | |
id: 'people', | |
name: 'People', | |
emojis: people.filter((item, pos) => people.indexOf(item) == pos), | |
}, | |
{ | |
id: 'nature', | |
name: 'Nature', | |
emojis: groups['nature'] ? groups['nature'] : [], | |
}, | |
{ | |
id: 'food', | |
name: 'Food', | |
emojis: groups['food'] ? groups['food'] : [], | |
}, | |
{ | |
id: 'activity', | |
name: 'Activity', | |
emojis: groups['activity'] ? groups['activity'] : [], | |
}, | |
{ | |
id: 'travel', | |
name: 'Travel', | |
emojis: groups['travel'] ? groups['travel'] : [], | |
}, | |
{ | |
id: 'objects', | |
name: 'Objects', | |
emojis: groups['objects'] ? groups['objects'] : [], | |
}, | |
{ | |
id: 'symbols', | |
name: 'Symbols', | |
emojis: groups['symbols'] ? groups['symbols'] : [], | |
}, | |
{ | |
id: 'flags', | |
name: 'Flags', | |
emojis: groups['flags'] ? groups['flags'] : [], | |
}, | |
], | |
emojis, | |
keywords, | |
} | |
sharp('node_modules/emoji-datasource-twitter/img/twitter/sheets/64.png').toFile(path.join(__dirname, 'sheet.webp'), (err, info) => { | |
if (err) { | |
console.error(err) | |
return | |
} | |
console.log('emoji.webp done') | |
}) | |
fs.writeFile(path.join(__dirname, 'data.json'), JSON.stringify(data), (err) => { | |
if (err) { | |
console.error(err) | |
return | |
} | |
console.log('emoji.json done') | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment