2019-06-04 21:17:23 +02:00
|
|
|
import Builtin from "../structs/builtin";
|
2019-06-03 22:25:08 +02:00
|
|
|
|
2019-06-28 22:44:08 +02:00
|
|
|
import {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-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 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`),
|
|
|
|
};
|
2019-06-03 22:25:08 +02:00
|
|
|
|
2019-06-09 22:30:33 +02:00
|
|
|
const Emotes = {
|
|
|
|
TwitchGlobal: {},
|
|
|
|
TwitchSubscriber: {},
|
|
|
|
BTTV: {},
|
2019-06-28 22:44:08 +02:00
|
|
|
FrankerFaceZ: {}
|
2019-06-03 22:25:08 +02:00
|
|
|
};
|
|
|
|
|
2019-06-09 22:30:33 +02:00
|
|
|
const blacklist = [];
|
2019-06-28 22:44:08 +02:00
|
|
|
const overrides = ["twitch", "subscriber", "bttv", "ffz"];
|
2019-06-09 22:30:33 +02:00
|
|
|
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 22:44:08 +02:00
|
|
|
get categories() {return Object.keys(Emotes).filter(k => this.isCategoryEnabled(k));}
|
2019-06-28 07:36:05 +02:00
|
|
|
get shouldDownload() {return Settings.get("emotes", this.category, "download");}
|
2019-06-03 22:25:08 +02:00
|
|
|
|
2019-06-28 22:44:08 +02:00
|
|
|
isCategoryEnabled(id) {return super.get("emotes", "categories", id.toLowerCase());}
|
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 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-28 22:44:08 +02:00
|
|
|
const storedFavorites = DataStore.getBDData("favoriteEmotes");
|
|
|
|
this.favoriteEmotes = storedFavorites || {};
|
2019-06-24 21:47:24 +02:00
|
|
|
this.addFavorite = this.addFavorite.bind(this);
|
|
|
|
this.removeFavorite = this.removeFavorite.bind(this);
|
2019-06-28 22:44:08 +02:00
|
|
|
this.onCategoryToggle = this.onCategoryToggle.bind(this);
|
|
|
|
this.resetEmotes = this.resetEmotes.bind(this);
|
2019-06-09 22:30:33 +02:00
|
|
|
}
|
|
|
|
|
2019-06-06 21:57:25 +02:00
|
|
|
async enabled() {
|
2019-06-28 22:44:08 +02:00
|
|
|
Settings.registerCollection("emotes", "Emotes", EmoteConfig, {title: Strings.Emotes.clearEmotes, onClick: this.resetEmotes});
|
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
|
|
|
|
2020-02-28 01:00:12 +01: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-28 22:44:08 +02:00
|
|
|
Events.on("setting-updated", this.onCategoryToggle);
|
2019-06-03 22:25:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
disabled() {
|
2020-02-28 01:00:12 +01:00
|
|
|
console.log("DISABLED");
|
2019-06-28 22:44:08 +02:00
|
|
|
Events.off("setting-updated", this.onCategoryToggle);
|
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-28 22:44:08 +02:00
|
|
|
onCategoryToggle(collection, cat, category, enabled) {
|
|
|
|
if (collection != "emotes" || cat != "categories") return;
|
|
|
|
if (enabled) return this.loadEmoteData(category);
|
|
|
|
return this.unloadEmoteData(category);
|
|
|
|
}
|
|
|
|
|
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() {
|
2019-06-28 22:44:08 +02:00
|
|
|
DataStore.setBDData("favoriteEmotes", this.favoriteEmotes);
|
2019-06-24 21:47:24 +02:00
|
|
|
}
|
|
|
|
|
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";
|
|
|
|
}
|
2019-06-28 22:44:08 +02:00
|
|
|
else if (emoteOverride === "subscriber") {
|
|
|
|
if (Emotes.TwitchSubscriber[emoteName]) current = "TwitchSubscriber";
|
|
|
|
}
|
2019-06-03 22:25:08 +02:00
|
|
|
else if (emoteOverride === "bttv") {
|
|
|
|
if (Emotes.BTTV[emoteName]) current = "BTTV";
|
|
|
|
}
|
|
|
|
else if (emoteOverride === "ffz") {
|
|
|
|
if (Emotes.FrankerFaceZ[emoteName]) current = "FrankerFaceZ";
|
|
|
|
}
|
|
|
|
|
2019-06-28 22:44:08 +02:00
|
|
|
if (!Emotes[current][emoteName]) 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 20:45:10 +02:00
|
|
|
async getBlacklist() {
|
2020-02-28 01:00:12 +01:00
|
|
|
try {
|
|
|
|
const category = "Blacklist";
|
|
|
|
const exists = DataStore.emotesExist(category);
|
|
|
|
const valid = await this.isCacheValid(category);
|
|
|
|
const useCache = (valid) || (!valid && exists && !this.shouldDownload);
|
|
|
|
const list = useCache ? DataStore.getEmoteData(category) : await this.downloadEmotes(category);
|
|
|
|
blacklist.push(...list);
|
|
|
|
}
|
|
|
|
catch (err) {
|
|
|
|
// TODO: Log this
|
|
|
|
}
|
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 22:44:08 +02:00
|
|
|
async loadEmoteData(categories) {
|
|
|
|
if (!categories) categories = this.categories;
|
|
|
|
if (!Array.isArray(categories)) categories = [categories];
|
2019-06-29 06:47:56 +02:00
|
|
|
const all = Object.keys(Emotes);
|
|
|
|
categories = categories.map(k => all.find(c => c.toLowerCase() == k.toLowerCase()));
|
2019-06-28 20:45:10 +02:00
|
|
|
Toasts.show(Strings.Emotes.loading, {type: "info"});
|
2019-06-28 07:36:05 +02:00
|
|
|
this.emotesLoaded = false;
|
2019-06-03 22:25:08 +02:00
|
|
|
|
2019-06-28 22:44:08 +02:00
|
|
|
for (const category of categories) {
|
2019-06-28 07:36:05 +02:00
|
|
|
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);
|
|
|
|
Object.assign(Emotes[category], data);
|
2019-06-28 20:45:10 +02:00
|
|
|
await new Promise(r => setTimeout(r, 1000));
|
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 20:45:10 +02:00
|
|
|
Toasts.show(Strings.Emotes.loaded, {type: "success"});
|
2019-06-03 22:25:08 +02:00
|
|
|
}
|
|
|
|
|
2019-06-28 22:44:08 +02:00
|
|
|
unloadEmoteData(categories) {
|
|
|
|
if (!categories) categories = this.categories;
|
|
|
|
if (!Array.isArray(categories)) categories = [categories];
|
2019-06-29 06:47:56 +02:00
|
|
|
const all = Object.keys(Emotes);
|
|
|
|
categories = categories.map(k => all.find(c => c.toLowerCase() == k.toLowerCase()));
|
2019-06-28 22:44:08 +02:00
|
|
|
for (const category of categories) {
|
|
|
|
delete Emotes[category];
|
|
|
|
Emotes[category] = {};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-28 07:36:05 +02:00
|
|
|
downloadEmotes(category) {
|
|
|
|
const url = this.getRemoteFile(category);
|
|
|
|
this.log(`Downloading ${category} from ${url}`);
|
2019-06-29 06:47:56 +02:00
|
|
|
const options = {url: url, timeout: 10000, 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-03 22:25:08 +02:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-06-28 22:44:08 +02:00
|
|
|
resetEmotes() {
|
|
|
|
const categories = Object.keys(Emotes);
|
|
|
|
this.unloadEmoteData(categories);
|
|
|
|
for (const cat of categories) DataStore.invalidateCache("emotes", cat);
|
|
|
|
this.loadEmoteData();
|
2019-06-03 22:25:08 +02:00
|
|
|
}
|
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;
|
|
|
|
// })();
|