Store emote database in a map

This commit is contained in:
Samuel Elliott 2018-03-31 04:37:26 +01:00
parent 1bde3b4ec9
commit b3442ee108
No known key found for this signature in database
GPG Key ID: 8420C7CDE43DC4D6
2 changed files with 72 additions and 73 deletions

View File

@ -14,17 +14,16 @@ import { Utils, FileUtils, ClientLogger as Logger } from 'common';
import path from 'path';
import EmoteComponent from './EmoteComponent.vue';
let emotes = null;
const emotesEnabled = true;
const enforceWrapperFrom = (new Date('2018-05-01')).valueOf();
export default new class EmoteModule {
constructor() {
this.emotes = new Map();
this.favourite_emotes = [];
}
init() {
async init() {
this.enabledSetting = Settings.getSetting('emotes', 'default', 'enable');
this.enabledSetting.on('setting-updated', event => {
// Rerender all messages (or if we're disabling emotes, those that have emotes)
@ -33,7 +32,25 @@ export default new class EmoteModule {
}
});
return this.observe();
const dataPath = Globals.getPath('data');
try {
const emotes = await FileUtils.readJsonFromFile(path.join(dataPath, 'emotes.json'));
for (let emote of emotes) {
const uri = emote.type === 2 ? 'https://cdn.betterttv.net/emote/:id/1x' : emote.type === 1 ? 'https://cdn.frankerfacez.com/emoticon/:id/1' : 'https://static-cdn.jtvnw.net/emoticons/v1/:id/1.0';
emote.name = emote.id;
emote.src = uri.replace(':id', emote.value.id || emote.value);
this.emotes.set(emote.id, emote);
}
} catch (err) {
Logger.err('EmoteModule', [`Failed to load emote data. Make sure you've downloaded the emote data and placed it in ${dataPath}:`, err]);
return;
}
try {
await this.observe();
} catch (err) {
Logger.err('EmoteModule', ['Error patching Message', err]);
}
}
/**
@ -65,10 +82,6 @@ export default new class EmoteModule {
return this._searchCache || (this._searchCache = {});
}
get emoteDb() {
return emotes;
}
get React() {
return WebpackModules.getModuleByName('React');
}
@ -97,16 +110,16 @@ export default new class EmoteModule {
if (!words) continue;
let text = null;
for (const [wordIndex, word] of words.entries()) {
const isEmote = this.isEmote(word);
if (isEmote) {
const emote = this.getEmote(word);
if (emote) {
if (text !== null) {
newMarkup.push(text);
text = null;
}
newMarkup.push(this.React.createElement('span', {
className: 'bd-emote-outer',
'data-bdemote-name': isEmote.name,
'data-bdemote-src': isEmote.src,
'data-bdemote-name': emote.name,
'data-bdemote-src': emote.src,
'data-has-wrapper': /;[\w]+;/gmi.test(word)
}));
continue;
@ -129,7 +142,8 @@ export default new class EmoteModule {
}
injectAll() {
if (!emotesEnabled) return;
if (!this.enabledSetting.value) return;
const all = document.getElementsByClassName('bd-emote-outer');
for (const ec of all) {
if (ec.children.length) continue;
@ -150,48 +164,36 @@ export default new class EmoteModule {
}
async observe() {
const dataPath = Globals.getPath('data');
try {
emotes = await FileUtils.readJsonFromFile(path.join(dataPath, 'emotes.json'));
} catch (err) {
Logger.err('EmoteModule', [`Failed to load emote data. Make sure you've downloaded the emote data and placed it in ${dataPath}:`, err]);
return;
}
try {
const Message = await ReactComponents.getComponent('Message');
this.unpatchRender = MonkeyPatch('BD:EmoteModule', Message.component.prototype).after('render', (component, args, retVal) => {
try {
// First child has all the actual text content, second is the edited timestamp
const markup = this.findByProp(retVal, 'className', 'markup');
if (!markup) return;
Logger.log('EmoteModule', ['Message :', retVal, component]);
markup.children[0] = this.processMarkup(markup.children[0], component.props.message.editedTimestamp || component.props.message.timestamp);
} catch (err) {
Logger.err('EmoteModule', err);
}
});
for (const message of document.querySelectorAll('.message')) {
Reflection(message).forceUpdate();
const Message = await ReactComponents.getComponent('Message');
this.unpatchRender = MonkeyPatch('BD:EmoteModule', Message.component.prototype).after('render', (component, args, retVal) => {
try {
// First child has all the actual text content, second is the edited timestamp
const markup = this.findByProp(retVal, 'className', 'markup');
if (!markup) return;
markup.children[0] = this.processMarkup(markup.children[0], component.props.message.editedTimestamp || component.props.message.timestamp);
} catch (err) {
Logger.err('EmoteModule', err);
}
this.injectAll();
this.unpatchMount = MonkeyPatch('BD:EmoteModule', Message.component.prototype).after('componentDidMount', component => {
const element = this.ReactDOM.findDOMNode(component);
if (!element) return;
this.injectEmotes(element);
});
this.unpatchUpdate = MonkeyPatch('BD:EmoteModule', Message.component.prototype).after('componentDidUpdate', component => {
const element = this.ReactDOM.findDOMNode(component);
if (!element) return;
this.injectEmotes(element);
});
} catch (err) {
Logger.err('EmoteModule', err);
});
for (const message of document.querySelectorAll('.message')) {
Reflection(message).forceUpdate();
}
this.injectAll();
this.unpatchMount = MonkeyPatch('BD:EmoteModule', Message.component.prototype).after('componentDidMount', component => {
const element = this.ReactDOM.findDOMNode(component);
if (!element) return;
this.injectEmotes(element);
});
this.unpatchUpdate = MonkeyPatch('BD:EmoteModule', Message.component.prototype).after('componentDidUpdate', component => {
const element = this.ReactDOM.findDOMNode(component);
if (!element) return;
this.injectEmotes(element);
});
}
injectEmote(root) {
if (!emotesEnabled) return;
if (!this.enabledSetting.value) return;
while (root.firstChild) {
root.removeChild(root.firstChild);
}
@ -206,25 +208,14 @@ export default new class EmoteModule {
}
injectEmotes(element) {
if (!emotesEnabled || !element) return;
if (!this.enabledSetting.value || !element) return;
for (const beo of element.getElementsByClassName('bd-emote-outer')) this.injectEmote(beo);
}
isEmote(word) {
if (!emotes) return null;
getEmote(word) {
const name = word.replace(/;/g, '');
const emote = emotes.find(emote => emote.id === name);
if (!emote) return null;
let { id, value } = emote;
if (value.id) value = value.id;
const uri = emote.type === 2 ? 'https://cdn.betterttv.net/emote/:id/1x' : emote.type === 1 ? 'https://cdn.frankerfacez.com/emoticon/:id/1' : 'https://static-cdn.jtvnw.net/emoticons/v1/:id/1.0';
return { name, src: uri.replace(':id', value) };
}
filterTest() {
const re = new RegExp('Kappa', 'i');
const filtered = emotes.filter(emote => re.test(emote.id));
return filtered.slice(0, 10);
return this.emotes.get(name);
}
filter(regex, limit, start = 0) {
@ -232,17 +223,21 @@ export default new class EmoteModule {
if (this.searchCache.hasOwnProperty(key)) return this.searchCache[key];
let index = 0;
let startIndex = 0;
return this.searchCache[key] = emotes.filter(emote => {
if (index >= limit) return false;
const matching = this.searchCache[key] = [];
for (let emote of this.emotes.values()) {
if (index >= limit) break;
if (regex.test(emote.id)) {
if (startIndex < start) {
startIndex++;
return false;
continue;
}
index++;
return true;
matching.push(emote);
}
});
}
return matching;
}
}

View File

@ -25,10 +25,14 @@
&.bd-selectable {
cursor: pointer;
}
&.bd-selected {
background-color: #36393f;
&:hover {
background-color: darken(#36393f, 5%);
}
&.bd-selected {
background-color: #36393f;
}
}
.bd-autocomplete-title {