diff --git a/package-lock.json b/package-lock.json index a78696af..7fb9d817 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "frankerfacez", - "version": "4.20.17", + "version": "4.20.31", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -2851,9 +2851,9 @@ } }, "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.0.0.tgz", + "integrity": "sha512-6p1NII1Vm62wni/VR/cUMauVQoxmLVb9csqQlvLz+hO2gk8U2UYDfXHQSUYIBKmZwAKz867IDqG7B+u0mj+M6w==" }, "emojis-list": { "version": "2.1.0", diff --git a/package.json b/package.json index 36ff824e..73457907 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "frankerfacez", "author": "Dan Salvato LLC", - "version": "4.20.31", + "version": "4.20.32", "description": "FrankerFaceZ is a Twitch enhancement suite.", "license": "Apache-2.0", "scripts": { @@ -71,7 +71,7 @@ "crypto-js": "^3.1.9-1", "dayjs": "^1.8.29", "displacejs": "^1.4.1", - "emoji-regex": "^8.0.0", + "emoji-regex": "^9.0.0", "file-saver": "^2.0.1", "graphql": "^15.2.0", "graphql-tag": "^2.10.3", diff --git a/src/modules/chat/actions/index.jsx b/src/modules/chat/actions/index.jsx index 7fa7cec7..701bb6b4 100644 --- a/src/modules/chat/actions/index.jsx +++ b/src/modules/chat/actions/index.jsx @@ -257,7 +257,7 @@ export default class Actions extends Module { reason_elements.push(
  • {text} @@ -274,7 +274,7 @@ export default class Actions extends Module { reason_elements.push(
  • {rule} diff --git a/src/modules/chat/emoji.js b/src/modules/chat/emoji.js index 2cec6d2c..6f4b8515 100644 --- a/src/modules/chat/emoji.js +++ b/src/modules/chat/emoji.js @@ -12,14 +12,48 @@ import { getBuster } from 'utilities/time'; import splitter from 'emoji-regex/es2015/index'; -export const SIZES = { +/*export const SIZES = { apple: [64, 160], emojione: [64], facebook: [64, 96], google: [64, 136], messenger: [64, 128], twitter: [64, 72] -} +}*/ + +export const HIDDEN_CATEGORIES = [ + 'component' +]; + +export const CATEGORIES = { + 'smileys-emotion': 'Smileys & Emotions', + 'people-body': 'People', + 'component': 'Components', + 'animals-nature': 'Animals & Nature', + 'food-drink': 'Food & Drink', + 'travel-places': 'Travel & Places', + 'activities': 'Activities', + 'objects': 'Objects', + 'symbols': 'Symbols', + 'flags': 'Flags' +}; + +export const CATEGORY_SORT = Object.keys(CATEGORIES); + +export const SKIN_TONES = { + 1: '1f3fb', + 2: '1f3fc', + 3: '1f3fd', + 4: '1f3fe', + 5: '1f3ff' +}; + +export const IMAGE_PATHS = { + google: 'noto', + twitter: 'twemoji', + open: 'openmoji', + blob: 'blob' +}; export function codepoint_to_emoji(cp) { @@ -49,13 +83,11 @@ export default class Emoji extends Module { title: 'Emoji Style', component: 'setting-select-box', data: [ - {value: 0, title: 'Native'}, - {value: 'twitter', title: 'Twitter'}, - {value: 'google', title: 'Google'}, - //{value: 'apple', title: 'Apple'}, - {value: 'emojione', title: 'EmojiOne'}, - //{value: 'facebook', title: 'Facebook'}, - //{value: 'messenger', title: 'Messenger'} + {value: 'twitter', title: 'Twitter (Twemoji)'}, + {value: 'google', title: 'Google (Noto)'}, + {value: 'blob', title: 'Blob'}, + {value: 'open', title: 'OpenMoji'}, + {value: 0, title: 'Native'} ] } }); @@ -75,7 +107,7 @@ export default class Emoji extends Module { async loadEmojiData(tries = 0) { let data; try { - data = await fetch(`${SERVER}/script/emoji/v2-.json?_${getBuster(60)}`).then(r => + data = await fetch(`${SERVER}/script/emoji/v3.2.json?_${getBuster(60)}`).then(r => r.ok ? r.json() : null ); @@ -119,11 +151,43 @@ export default class Emoji extends Module { if ( raw[7] ) { const vars = emoji.variants = {}; for(const r of raw[7]) { + if ( Array.isArray(r[3]) || ! r[3] ) { + // The tone picker doesn't support multiple tones + // for a single emoji. Just make this variation a + // new emoji. + const em = Object.assign(hydrate_emoji(r), { + category: cats[raw[0]], + sort: raw[1], + names: r[5], + hidden: true + }); + + if ( ! Array.isArray(em.names) ) + em.names = [em.names]; + + em.name = em.names[0].replace(/_/g, ' '); + + out[em.code] = em; + chars.set(em.raw, [em.code, null]); + for(const name of em.names) + names[name] = em.code; + + continue; + } + + // We just have a normal tone. We need to look + // up the modifier and use it. + const tone = SKIN_TONES[r[3]]; + if ( ! tone ) { + console.warn('Unknown tone:', r[3], r, emoji); + continue; + } + const vari = Object.assign(hydrate_emoji(r), { - key: r[3].toLowerCase() + key: tone }); - vars[vari.key] = vari; + vars[tone] = vari; chars.set(vari.raw, [emoji.code, vari.key]); } } @@ -142,41 +206,53 @@ export default class Emoji extends Module { if ( ! style ) style = this.parent.context.get('chat.emoji.style'); - if ( ! has(SIZES, style) ) + if ( ! has(IMAGE_PATHS, style) ) style = 'twitter'; - return `${SERVER}/static/emoji/img-${style}-${SIZES[style][0]}/${image}`; + return `${SERVER}/static/emoji/images/${IMAGE_PATHS[style]}/${image}`; + + /*if ( ! has(SIZES, style) ) + style = 'twitter'; + + return `${SERVER}/static/emoji/img-${style}-${SIZES[style][0]}/${image}`;*/ } getFullImageSet(image, style) { if ( ! style ) style = this.parent.context.get('chat.emoji.style'); - if ( ! has(SIZES, style) ) + if ( ! has(IMAGE_PATHS, style) ) + style = 'twitter'; + + return `${SERVER}/static/emoji/images/${IMAGE_PATHS[style]}/${image} 72w`; + + /*if ( ! has(SIZES, style) ) style = 'twitter'; return SIZES[style].map(w => `${SERVER}/static/emoji/img-${style}-${w}/${image} ${w}w` - ).join(', '); + ).join(', ');*/ } } function hydrate_emoji(data) { + let code = data[0]; + if ( data[4] === 0 ) + code = `${code}-fe0f`; + return { - code: data[0], + code, image: `${data[0]}.png`, - raw: data[0].split('-').map(codepoint_to_emoji).join(''), + raw: code.split('-').map(codepoint_to_emoji).join(''), sheet_x: data[1][0], sheet_y: data[1][1], has: { - apple: !!(0b100000 & data[2]), - google: !!(0b010000 & data[2]), - twitter: !!(0b001000 & data[2]), - emojione: !!(0b000100 & data[2]), - facebook: !!(0b000010 & data[2]), - messenger: !!(0b000001 & data[2]) + google: !!(0b1000 & data[2]), + blob: !!(0b0100 & data[2]) || !!(0b1000 & data[2]), // Blob falls back to Noto + twitter: !!(0b0010 & data[2]), + open: !!(0b0001 & data[2]) } }; } \ No newline at end of file diff --git a/src/modules/chat/tokenizers.jsx b/src/modules/chat/tokenizers.jsx index efba64a8..9d029eb1 100644 --- a/src/modules/chat/tokenizers.jsx +++ b/src/modules/chat/tokenizers.jsx @@ -8,6 +8,7 @@ import {sanitize, createElement} from 'utilities/dom'; import {has, split_chars} from 'utilities/object'; import {TWITCH_EMOTE_BASE, EmoteTypes, REPLACEMENT_BASE, REPLACEMENTS} from 'utilities/constants'; +import {CATEGORIES} from './emoji'; const EMOTE_CLASS = 'chat-image chat-line__message--emote', @@ -1227,7 +1228,7 @@ export const AddonEmotes = { plain_name = true; name = `:${emoji.names[0]}:${vcode ? `:${vcode.names[0]}:` : ''}`; - const category = emoji.category ? this.i18n.t(`emoji.category.${emoji.category.toSnakeCase()}`, emoji.category) : null; + const category = emoji.category ? this.i18n.t(`emoji.category.${emoji.category.toSnakeCase()}`, CATEGORIES[emoji.category] || emoji.category) : null; source = this.i18n.t('tooltip.emoji', 'Emoji - {category}', {category}); } else diff --git a/src/modules/main_menu/components/chat-actions.vue b/src/modules/main_menu/components/chat-actions.vue index 1d912c5a..fc7daa52 100644 --- a/src/modules/main_menu/components/chat-actions.vue +++ b/src/modules/main_menu/components/chat-actions.vue @@ -245,7 +245,7 @@