2019-06-04 21:17:23 +02:00
|
|
|
import Builtin from "../structs/builtin";
|
2019-06-03 22:25:08 +02:00
|
|
|
|
2019-06-10 01:04:39 +02:00
|
|
|
import {Config, EmoteInfo, EmoteConfig} from "data";
|
2019-06-25 22:36:34 +02:00
|
|
|
import {Utilities, WebpackModules, DataStore, DiscordModules, Events, Settings, Strings} from "modules";
|
2019-06-03 22:25:08 +02:00
|
|
|
import BDEmote from "../ui/emote";
|
2019-06-23 06:11:50 +02:00
|
|
|
import Toasts from "../ui/toasts";
|
2019-06-24 21:47:24 +02:00
|
|
|
// import EmoteMenu from "./emotemenu";
|
2019-06-28 01:50:20 +02:00
|
|
|
const request = require("request");
|
2019-06-03 22:25:08 +02:00
|
|
|
|
2019-06-09 22:30:33 +02:00
|
|
|
const Emotes = {
|
|
|
|
TwitchGlobal: {},
|
|
|
|
TwitchSubscriber: {},
|
|
|
|
BTTV: {},
|
|
|
|
FrankerFaceZ: {},
|
|
|
|
BTTV2: {}
|
|
|
|
};
|
|
|
|
|
2019-06-03 22:25:08 +02:00
|
|
|
const bdEmoteSettingIDs = {
|
2019-06-06 21:57:25 +02:00
|
|
|
TwitchGlobal: "twitch",
|
|
|
|
TwitchSubscriber: "twitch",
|
|
|
|
BTTV: "bttv",
|
|
|
|
FrankerFaceZ: "ffz",
|
|
|
|
BTTV2: "bttv"
|
2019-06-03 22:25:08 +02:00
|
|
|
};
|
|
|
|
|
2019-06-09 22:30:33 +02:00
|
|
|
const blacklist = [];
|
|
|
|
const overrides = ["twitch", "bttv", "ffz"];
|
|
|
|
const modifiers = ["flip", "spin", "pulse", "spin2", "spin3", "1spin", "2spin", "3spin", "tr", "bl", "br", "shake", "shake2", "shake3", "flap"];
|
|
|
|
|
2019-06-03 22:25:08 +02:00
|
|
|
export default new class EmoteModule extends Builtin {
|
|
|
|
get name() {return "Emotes";}
|
2019-06-06 21:57:25 +02:00
|
|
|
get collection() {return "settings";}
|
2019-06-06 06:28:43 +02:00
|
|
|
get category() {return "general";}
|
2019-06-06 21:57:25 +02:00
|
|
|
get id() {return "emotes";}
|
|
|
|
get categories() { return Object.keys(bdEmoteSettingIDs).filter(k => this.isCategoryEnabled(bdEmoteSettingIDs[k])); }
|
2019-06-03 22:25:08 +02:00
|
|
|
|
2019-06-24 21:47:24 +02:00
|
|
|
isCategoryEnabled(id) {return super.get("emotes", "categories", id);}
|
2019-06-06 21:57:25 +02:00
|
|
|
|
2019-06-24 21:47:24 +02:00
|
|
|
get(id) {return super.get("emotes", "general", id);}
|
2019-06-06 21:57:25 +02:00
|
|
|
|
2019-06-09 22:30:33 +02:00
|
|
|
get MessageContentComponent() {return WebpackModules.getModule(m => m.defaultProps && m.defaultProps.hasOwnProperty("disableButtons"));}
|
|
|
|
|
|
|
|
get Emotes() {return Emotes;}
|
|
|
|
get TwitchGlobal() {return Emotes.TwitchGlobal;}
|
|
|
|
get TwitchSubscriber() {return Emotes.TwitchSubscriber;}
|
|
|
|
get BTTV() {return Emotes.BTTV;}
|
|
|
|
get FrankerFaceZ() {return Emotes.FrankerFaceZ;}
|
|
|
|
get BTTV2() {return Emotes.BTTV2;}
|
|
|
|
get blacklist() {return blacklist;}
|
2019-06-24 21:47:24 +02:00
|
|
|
get favorites() {return this.favoriteEmotes;}
|
2019-06-09 22:30:33 +02:00
|
|
|
|
|
|
|
getCategory(category) {
|
|
|
|
return Emotes[category];
|
|
|
|
}
|
|
|
|
|
|
|
|
initialize() {
|
|
|
|
super.initialize();
|
2019-06-24 21:47:24 +02:00
|
|
|
this.favoriteEmotes = {};
|
|
|
|
const fe = DataStore.getBDData("bdfavemotes");
|
|
|
|
if (fe !== "" && fe !== null) this.favoriteEmotes = JSON.parse(window.atob(fe));
|
|
|
|
this.saveFavorites();
|
|
|
|
this.addFavorite = this.addFavorite.bind(this);
|
|
|
|
this.removeFavorite = this.removeFavorite.bind(this);
|
2019-06-09 22:30:33 +02:00
|
|
|
// EmoteConfig;
|
|
|
|
// emoteCollection.button = {title: "Clear Emote Cache", onClick: () => { this.clearEmoteData(); this.loadEmoteData(EmoteInfo); }};
|
|
|
|
}
|
|
|
|
|
2019-06-06 21:57:25 +02:00
|
|
|
async enabled() {
|
2019-06-25 22:36:34 +02:00
|
|
|
Settings.registerCollection("emotes", "Emotes", EmoteConfig, {title: Strings.Emotes.clearEmotes, onClick: () => { this.clearEmoteData(); this.loadEmoteData(EmoteInfo); }});
|
2019-06-04 21:17:23 +02:00
|
|
|
// Disable emote module for now because it's annoying and slow
|
2019-06-28 01:50:20 +02:00
|
|
|
await this.getBlacklist();
|
|
|
|
await this.loadEmoteData(EmoteInfo);
|
2019-06-03 22:25:08 +02:00
|
|
|
|
2019-06-28 01:50:20 +02:00
|
|
|
while (!this.MessageContentComponent) await new Promise(resolve => setTimeout(resolve, 100));
|
|
|
|
this.patchMessageContent();
|
2019-06-24 21:47:24 +02:00
|
|
|
Events.on("emotes-favorite-added", this.addFavorite);
|
|
|
|
Events.on("emotes-favorite-removed", this.removeFavorite);
|
2019-06-03 22:25:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
disabled() {
|
2019-06-24 21:47:24 +02:00
|
|
|
Events.off("emotes-favorite-added", this.addFavorite);
|
|
|
|
Events.off("emotes-favorite-removed", this.removeFavorite);
|
2019-06-09 22:30:33 +02:00
|
|
|
Settings.removeCollection("emotes");
|
2019-06-06 21:57:25 +02:00
|
|
|
this.emptyEmotes();
|
2019-06-09 22:30:33 +02:00
|
|
|
if (!this.cancelEmoteRender) return;
|
2019-06-03 22:25:08 +02:00
|
|
|
this.cancelEmoteRender();
|
|
|
|
delete this.cancelEmoteRender;
|
|
|
|
}
|
|
|
|
|
2019-06-24 21:47:24 +02:00
|
|
|
addFavorite(name, url) {
|
|
|
|
if (!this.favoriteEmotes.hasOwnProperty(name)) this.favoriteEmotes[name] = url;
|
|
|
|
this.saveFavorites();
|
|
|
|
}
|
|
|
|
|
|
|
|
removeFavorite(name) {
|
|
|
|
if (!this.favoriteEmotes.hasOwnProperty(name)) return;
|
|
|
|
delete this.favoriteEmotes[name];
|
|
|
|
this.saveFavorites();
|
|
|
|
}
|
|
|
|
|
|
|
|
isFavorite(name) {
|
|
|
|
return this.favoriteEmotes.hasOwnProperty(name);
|
|
|
|
}
|
|
|
|
|
|
|
|
saveFavorites() {
|
|
|
|
DataStore.setBDData("bdfavemotes", window.btoa(JSON.stringify(this.favoriteEmotes)));
|
|
|
|
}
|
|
|
|
|
2019-06-06 21:57:25 +02:00
|
|
|
emptyEmotes() {
|
|
|
|
for (const cat in Emotes) Object.assign(Emotes, {[cat]: {}});
|
|
|
|
}
|
|
|
|
|
2019-06-03 22:25:08 +02:00
|
|
|
patchMessageContent() {
|
|
|
|
if (this.cancelEmoteRender) return;
|
2019-06-20 04:19:34 +02:00
|
|
|
this.cancelEmoteRender = this.after(this.MessageContentComponent.prototype, "render", (thisObj, args, retVal) => {
|
|
|
|
this.after(retVal.props, "children", (t, a, returnValue) => {
|
2019-06-03 22:25:08 +02:00
|
|
|
if (this.categories.length == 0) return;
|
|
|
|
const markup = returnValue.props.children[1];
|
|
|
|
if (!markup.props.children) return;
|
|
|
|
const nodes = markup.props.children[1];
|
|
|
|
if (!nodes || !nodes.length) return;
|
|
|
|
for (let n = 0; n < nodes.length; n++) {
|
|
|
|
const node = nodes[n];
|
|
|
|
if (typeof(node) !== "string") continue;
|
|
|
|
const words = node.split(/([^\s]+)([\s]|$)/g);
|
|
|
|
for (let c = 0, clen = this.categories.length; c < clen; c++) {
|
|
|
|
for (let w = 0, wlen = words.length; w < wlen; w++) {
|
|
|
|
const emote = words[w];
|
|
|
|
const emoteSplit = emote.split(":");
|
|
|
|
const emoteName = emoteSplit[0];
|
|
|
|
let emoteModifier = emoteSplit[1] ? emoteSplit[1] : "";
|
|
|
|
let emoteOverride = emoteModifier.slice(0);
|
|
|
|
|
2019-06-09 22:30:33 +02:00
|
|
|
if (emoteName.length < 4 || blacklist.includes(emoteName)) continue;
|
|
|
|
if (!modifiers.includes(emoteModifier) || !Settings.get(this.category, "general", "modifiers")) emoteModifier = "";
|
|
|
|
if (!overrides.includes(emoteOverride)) emoteOverride = "";
|
2019-06-03 22:25:08 +02:00
|
|
|
else emoteModifier = emoteOverride;
|
|
|
|
|
|
|
|
let current = this.categories[c];
|
|
|
|
if (emoteOverride === "twitch") {
|
|
|
|
if (Emotes.TwitchGlobal[emoteName]) current = "TwitchGlobal";
|
|
|
|
else if (Emotes.TwitchSubscriber[emoteName]) current = "TwitchSubscriber";
|
|
|
|
}
|
|
|
|
else if (emoteOverride === "bttv") {
|
|
|
|
if (Emotes.BTTV[emoteName]) current = "BTTV";
|
|
|
|
else if (Emotes.BTTV2[emoteName]) current = "BTTV2";
|
|
|
|
}
|
|
|
|
else if (emoteOverride === "ffz") {
|
|
|
|
if (Emotes.FrankerFaceZ[emoteName]) current = "FrankerFaceZ";
|
|
|
|
}
|
|
|
|
|
2019-06-08 08:35:43 +02:00
|
|
|
if (!Emotes[current][emoteName] || !Settings.get(this.category, "categories", bdEmoteSettingIDs[current])) continue;
|
2019-06-03 22:25:08 +02:00
|
|
|
const results = nodes[n].match(new RegExp(`([\\s]|^)${Utilities.escape(emoteModifier ? emoteName + ":" + emoteModifier : emoteName)}([\\s]|$)`));
|
|
|
|
if (!results) continue;
|
|
|
|
const pre = nodes[n].substring(0, results.index + results[1].length);
|
|
|
|
const post = nodes[n].substring(results.index + results[0].length - results[2].length);
|
|
|
|
nodes[n] = pre;
|
2019-06-24 21:47:24 +02:00
|
|
|
const emoteComponent = DiscordModules.React.createElement(BDEmote, {name: emoteName, url: Emotes[current][emoteName], modifier: emoteModifier, isFavorite: this.isFavorite(emoteName)});
|
2019-06-03 22:25:08 +02:00
|
|
|
nodes.splice(n + 1, 0, post);
|
|
|
|
nodes.splice(n + 1, 0, emoteComponent);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const onlyEmotes = nodes.every(r => {
|
|
|
|
if (typeof(r) == "string" && r.replace(/\s*/, "") == "") return true;
|
|
|
|
else if (r.type && r.type.name == "BDEmote") return true;
|
|
|
|
else if (r.props && r.props.children && r.props.children.props && r.props.children.props.emojiName) return true;
|
|
|
|
return false;
|
|
|
|
});
|
|
|
|
if (!onlyEmotes) return;
|
|
|
|
|
|
|
|
for (const node of nodes) {
|
|
|
|
if (typeof(node) != "object") continue;
|
|
|
|
if (node.type.name == "BDEmote") node.props.jumboable = true;
|
|
|
|
else if (node.props && node.props.children && node.props.children.props && node.props.children.props.emojiName) node.props.children.props.jumboable = true;
|
|
|
|
}
|
2019-06-20 04:19:34 +02:00
|
|
|
});
|
|
|
|
});
|
2019-06-03 22:25:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
async loadEmoteData(emoteInfo) {
|
2019-06-10 01:04:39 +02:00
|
|
|
this.emotesLoaded = false;
|
2019-06-03 22:25:08 +02:00
|
|
|
const _fs = require("fs");
|
|
|
|
const emoteFile = "emote_data.json";
|
|
|
|
const file = Config.dataPath + emoteFile;
|
|
|
|
const exists = _fs.existsSync(file);
|
|
|
|
|
|
|
|
if (exists && this.isCacheValid()) {
|
|
|
|
Toasts.show("Loading emotes from cache.", {type: "info"});
|
2019-06-19 21:24:05 +02:00
|
|
|
this.log("Loading emotes from local cache.");
|
2019-06-03 22:25:08 +02:00
|
|
|
|
|
|
|
const data = await new Promise(resolve => {
|
2019-06-19 05:09:49 +02:00
|
|
|
_fs.readFile(file, "utf8", (err, content) => {
|
2019-06-19 21:24:05 +02:00
|
|
|
this.log("Emotes loaded from cache.");
|
2019-06-19 05:09:49 +02:00
|
|
|
if (err) content = {};
|
|
|
|
resolve(content);
|
2019-06-03 22:25:08 +02:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2019-06-19 21:24:05 +02:00
|
|
|
const parsed = Utilities.testJSON(data);
|
|
|
|
let isValid = !!parsed;
|
|
|
|
if (isValid) Object.assign(Emotes, parsed);
|
2019-06-03 22:25:08 +02:00
|
|
|
|
|
|
|
for (const e in emoteInfo) {
|
|
|
|
isValid = Object.keys(Emotes[emoteInfo[e].variable]).length > 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isValid) {
|
|
|
|
Toasts.show("Emotes successfully loaded.", {type: "success"});
|
2019-06-10 01:04:39 +02:00
|
|
|
this.emotesLoaded = true;
|
2019-06-03 22:25:08 +02:00
|
|
|
Events.dispatch("emotes-loaded");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-06-19 21:24:05 +02:00
|
|
|
this.log("Cache was corrupt, downloading...");
|
2019-06-03 22:25:08 +02:00
|
|
|
_fs.unlinkSync(file);
|
|
|
|
}
|
|
|
|
|
2019-06-08 08:35:43 +02:00
|
|
|
if (!Settings.get(this.category, "general", "download")) return;
|
2019-06-25 22:36:34 +02:00
|
|
|
Toasts.show(Strings.Emotes.downloading, {type: "info"});
|
2019-06-03 22:25:08 +02:00
|
|
|
|
|
|
|
for (const e in emoteInfo) {
|
|
|
|
await new Promise(r => setTimeout(r, 1000));
|
|
|
|
const data = await this.downloadEmotes(emoteInfo[e]);
|
|
|
|
Emotes[emoteInfo[e].variable] = data;
|
|
|
|
}
|
|
|
|
|
2019-06-25 22:36:34 +02:00
|
|
|
Toasts.show(Strings.Emotes.downloaded, {type: "success"});
|
2019-06-03 22:25:08 +02:00
|
|
|
|
|
|
|
try { _fs.writeFileSync(file, JSON.stringify(Emotes), "utf8"); }
|
2019-06-19 21:24:05 +02:00
|
|
|
catch (err) { this.stacktrace("Could not save emote data.", err); }
|
2019-06-03 22:25:08 +02:00
|
|
|
|
2019-06-10 01:04:39 +02:00
|
|
|
this.emotesLoaded = true;
|
2019-06-03 22:25:08 +02:00
|
|
|
Events.dispatch("emotes-loaded");
|
|
|
|
}
|
|
|
|
|
|
|
|
downloadEmotes(emoteMeta) {
|
2019-06-28 01:50:20 +02:00
|
|
|
const repoFile = Utilities.repoUrl(`data/emotes/${emoteMeta.variable.toLowerCase()}.json`);
|
|
|
|
if (emoteMeta.url && !emoteMeta.backup) emoteMeta.backup = repoFile;
|
|
|
|
if (!emoteMeta.url) emoteMeta.url = repoFile;
|
|
|
|
|
2019-06-03 22:25:08 +02:00
|
|
|
const options = {
|
|
|
|
url: emoteMeta.url,
|
2019-06-19 21:24:05 +02:00
|
|
|
timeout: emoteMeta.timeout ? emoteMeta.timeout : 5000,
|
|
|
|
json: true
|
2019-06-03 22:25:08 +02:00
|
|
|
};
|
|
|
|
|
2019-06-19 21:24:05 +02:00
|
|
|
this.log(`Downloading: ${emoteMeta.variable} (${emoteMeta.url})`);
|
2019-06-03 22:25:08 +02:00
|
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
2019-06-28 01:50:20 +02:00
|
|
|
request.get(options, (error, response, parsedData) => {
|
2019-06-03 22:25:08 +02:00
|
|
|
if (error) {
|
2019-06-19 21:24:05 +02:00
|
|
|
this.stacktrace("Could not download " + emoteMeta.variable, error);
|
2019-06-28 01:50:20 +02:00
|
|
|
if (emoteMeta.backup || emoteMeta.url) {
|
2019-06-03 22:25:08 +02:00
|
|
|
emoteMeta.url = emoteMeta.backup;
|
|
|
|
emoteMeta.backup = null;
|
|
|
|
if (emoteMeta.backupParser) emoteMeta.parser = emoteMeta.backupParser;
|
|
|
|
return resolve(this.downloadEmotes(emoteMeta));
|
|
|
|
}
|
|
|
|
return reject({});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof(emoteMeta.parser) === "function") parsedData = emoteMeta.parser(parsedData);
|
|
|
|
|
|
|
|
for (const emote in parsedData) {
|
2019-06-09 22:30:33 +02:00
|
|
|
if (emote.length < 4 || blacklist.includes(emote)) {
|
2019-06-03 22:25:08 +02:00
|
|
|
delete parsedData[emote];
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
parsedData[emote] = emoteMeta.getEmoteURL(parsedData[emote]);
|
|
|
|
}
|
|
|
|
resolve(parsedData);
|
2019-06-19 21:24:05 +02:00
|
|
|
this.log("Downloaded: " + emoteMeta.variable);
|
2019-06-03 22:25:08 +02:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
getBlacklist() {
|
|
|
|
return new Promise(resolve => {
|
2019-06-28 01:50:20 +02:00
|
|
|
request.get({url: Utilities.repoUrl(`data/emotes/blacklist.json`), json: true}, (err, resp, data) => {
|
|
|
|
if (err || resp.statusCode != 200) return resolve();
|
|
|
|
resolve(blacklist.push(...data));
|
2019-06-03 22:25:08 +02:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
isCacheValid() {
|
|
|
|
const cacheLength = DataStore.getBDData("emoteCacheDays") || DataStore.setBDData("emoteCacheDays", 7) || 7;
|
|
|
|
const cacheDate = new Date(DataStore.getBDData("emoteCacheDate") || null);
|
|
|
|
const currentDate = new Date();
|
|
|
|
const daysBetween = Math.round(Math.abs((currentDate.getTime() - cacheDate.getTime()) / (24 * 60 * 60 * 1000)));
|
|
|
|
if (daysBetween > cacheLength) {
|
|
|
|
DataStore.setBDData("emoteCacheDate", currentDate.toJSON());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
clearEmoteData() {
|
|
|
|
const _fs = require("fs");
|
|
|
|
const emoteFile = "emote_data.json";
|
|
|
|
const file = Config.dataPath + emoteFile;
|
|
|
|
const exists = _fs.existsSync(file);
|
|
|
|
if (exists) _fs.unlinkSync(file);
|
|
|
|
DataStore.setBDData("emoteCacheDate", (new Date()).toJSON());
|
|
|
|
for (const category in Emotes) Object.assign(Emotes, {[category]: {}});
|
|
|
|
}
|
|
|
|
};
|