BetterDiscordApp-rauenzi/src/builtins/emotes.js

299 lines
13 KiB
JavaScript
Raw Normal View History

2019-06-04 21:17:23 +02:00
import Builtin from "../structs/builtin";
2019-06-03 22:25:08 +02:00
2019-06-28 07:36:05 +02:00
import {Config, 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";
import Toasts from "../ui/toasts";
2019-06-28 07:36:05 +02:00
import FormattableString from "../structs/string";
2019-06-28 01:50:20 +02:00
const request = require("request");
2019-06-28 07:36:05 +02:00
// const fs = require("fs");
const EmoteURLs = {
TwitchGlobal: new FormattableString(`https://static-cdn.jtvnw.net/emoticons/v1/{{id}}/1.0`),
TwitchSubscriber: new FormattableString(`https://static-cdn.jtvnw.net/emoticons/v1/{{id}}/1.0`),
FrankerFaceZ: new FormattableString(`https://cdn.frankerfacez.com/emoticon/{{id}}/1`),
BTTV: new FormattableString(`https://cdn.betterttv.net/emote/{{id}}/1x`),
BTTV2: new FormattableString(`https://cdn.betterttv.net/emote/{{id}}/1x`)
};
const EmoteMetaInfo = {
TwitchGlobal: {},
TwitchSubscriber: {},
BTTV: {},
FrankerFaceZ: {},
BTTV2: {}
};
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";}
2019-06-28 07:36:05 +02:00
get categories() {return Object.keys(bdEmoteSettingIDs).filter(k => this.isCategoryEnabled(bdEmoteSettingIDs[k]));}
get shouldDownload() {return Settings.get("emotes", this.category, "download");}
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-28 08:19:36 +02:00
getUrl(category, name) {return EmoteURLs[category].format({id: Emotes[category][name]});}
2019-06-09 22:30:33 +02:00
2019-06-28 07:36:05 +02:00
getCategory(category) {return Emotes[category];}
2019-06-28 07:58:10 +02:00
getRemoteFile(category) {return Utilities.repoUrl(`data/emotes/${category.toLowerCase()}.json`);}
2019-06-09 22:30:33 +02:00
initialize() {
super.initialize();
2019-06-28 08:19:36 +02:00
window.emoteModule = this;
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-28 07:36:05 +02:00
Settings.registerCollection("emotes", "Emotes", EmoteConfig, {title: Strings.Emotes.clearEmotes, onClick: () => {this.clearEmoteData(); this.loadEmoteData();}});
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();
2019-06-28 07:36:05 +02:00
await this.loadEmoteData();
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;
2019-06-28 08:19:36 +02:00
if (!modifiers.includes(emoteModifier) || !Settings.get("emotes", "general", "modifiers")) emoteModifier = "";
2019-06-09 22:30:33 +02:00
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-28 08:19:36 +02:00
if (!Emotes[current][emoteName] || !Settings.get("emotes", "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-28 08:19:36 +02:00
const emoteComponent = DiscordModules.React.createElement(BDEmote, {name: emoteName, url: EmoteURLs[current].format({id: 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
}
2019-06-28 07:36:05 +02:00
getBlacklist() {
return new Promise(resolve => {
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
});
2019-06-28 07:36:05 +02:00
});
}
2019-06-03 22:25:08 +02:00
2019-06-28 07:36:05 +02:00
isCacheValid(category) {
return new Promise(resolve => {
const etag = DataStore.getCacheHash("emotes", category);
if (!etag) return resolve(false);
request.head({url: this.getRemoteFile(category), headers: {"If-None-Match": etag}}, (err, resp) => {
resolve(resp.statusCode == 304);
});
});
}
2019-06-03 22:25:08 +02:00
2019-06-28 07:36:05 +02:00
async loadEmoteData() {
this.emotesLoaded = false;
2019-06-03 22:25:08 +02:00
2019-06-28 07:36:05 +02:00
for (const category in Emotes) {
const exists = DataStore.emotesExist(category);
const valid = await this.isCacheValid(category);
const useCache = (valid) || (!valid && exists && !this.shouldDownload);
let data = null;
if (useCache) {
this.log(`Loading ${category} emotes from local cache.`);
const cachedData = DataStore.getEmoteData(category);
2019-06-28 07:58:10 +02:00
const hasData = Object.keys(cachedData).length > 0;
2019-06-28 07:36:05 +02:00
if (hasData) data = cachedData;
2019-06-03 22:25:08 +02:00
}
2019-06-28 07:36:05 +02:00
if (!data) data = await this.downloadEmotes(category);
2019-06-28 08:19:36 +02:00
// for (const emote in data) data[emote] = EmoteURLs[category].format({id: data[emote]});
2019-06-28 07:36:05 +02:00
Object.assign(Emotes[category], data);
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");
}
2019-06-28 07:36:05 +02:00
downloadEmotes(category) {
2019-06-28 07:58:10 +02:00
// Toasts.show(Strings.Emotes.downloading, {type: "info"});
2019-06-28 07:36:05 +02:00
const url = this.getRemoteFile(category);
this.log(`Downloading ${category} from ${url}`);
2019-06-28 07:58:10 +02:00
const options = {url: url, timeout: 8000, json: true};
2019-06-28 07:36:05 +02:00
return new Promise(resolve => {
2019-06-28 01:50:20 +02:00
request.get(options, (error, response, parsedData) => {
2019-06-28 07:36:05 +02:00
if (error || response.statusCode != 200) {
this.stacktrace(`Could not download ${category} emotes.`, error);
return resolve({});
2019-06-03 22:25:08 +02:00
}
for (const emote in parsedData) {
2019-06-28 07:36:05 +02:00
if (emote.length < 4 || blacklist.includes(emote) || !parsedData[emote]) {
2019-06-03 22:25:08 +02:00
delete parsedData[emote];
continue;
}
2019-06-28 07:58:10 +02:00
// parsedData[emote] = EmoteURLs[category].format({id: parsedData[emote]});
2019-06-03 22:25:08 +02:00
}
2019-06-28 07:36:05 +02:00
DataStore.saveEmoteData(category, parsedData);
2019-06-28 07:58:10 +02:00
DataStore.setCacheHash("emotes", category, response.headers.etag);
2019-06-03 22:25:08 +02:00
resolve(parsedData);
2019-06-28 07:36:05 +02:00
this.log(`Downloaded ${category}`);
2019-06-28 07:58:10 +02:00
// Toasts.show(Strings.Emotes.downloaded, {type: "success"});
2019-06-03 22:25:08 +02:00
});
});
}
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]: {}});
}
2019-06-28 07:36:05 +02:00
};
// (async () => {
// const emoteData = await new Promise(resolve => {
// const req = require("request");
// req.get({url: "https://twitchemotes.com/api_cache/v3/global.json", json: true}, (err, resp, parsedData) => {
// for (const emote in parsedData) {
// if (emote.length < 4 || window.bemotes.includes(emote)) {
// delete parsedData[emote];
// continue;
// }
// parsedData[emote] = parsedData[emote].id;
// }
// resolve(parsedData);
// });
// });
// const fs = require("fs");
// fs.writeFileSync("Z:\\Programming\\BetterDiscordStuff\\BetterDiscordApp\\data\\emotes\\global.json", JSON.stringify(emoteData));
// return emoteData;
// })();