Skip to content

Instantly share code, notes, and snippets.

@molenzwiebel
Last active February 12, 2020 09:49
Show Gist options
  • Save molenzwiebel/149ffc3bb214036f430820b613488f0b to your computer and use it in GitHub Desktop.
Save molenzwiebel/149ffc3bb214036f430820b613488f0b to your computer and use it in GitHub Desktop.
//META{"name":"EmojiTyperIntegration"}*//
const EmojiTyperIntegration = class EmojiTyperIntegration {
getName() { return "EmojiTyper Integration"; }
getShortName() { return "emojityper-integration"; }
getDescription() { return "Integrates https://emojityper.com with your emoji picker. Simply search with @<term> to query EmojiPicker, or type :@<query> in chat to autocomplete with EmojiPicker."; }
getVersion() { return "1.0.0"; }
getAuthor(){ return "molenzwiebel"; }
load() {}
unload() {}
start() {
const modules = this.getWebpackModules();
if (!modules) return this.failInitialization("Couldn't query Discord internals.");
const emojiSet = Object.keys(modules.c).map(x => modules.c[x]).find(x => x && x.exports && x.exports.__proto__ && x.exports.__proto__.getGuildEmoji);
if (!emojiSet || !emojiSet.exports) return this.failInitialization("Couldn't find Discord emoji storage.");
const allEmojis = emojiSet.exports.search({ getGuildId() { return -1; }, isPrivate() { return true; } }, "").filter(x => x.surrogates);
const pickerExport = Object.keys(modules.c).map(x => modules.c[x]).find(x => x.exports && x.exports.EmojiPicker);
if (!pickerExport || !pickerExport.exports || !pickerExport.exports.EmojiPicker) return this.failInitialization("Couldn't find EmojiPicker in Discord internals.");
this.emojiPicker = pickerExport.exports.EmojiPicker;
this.originalRender = this.emojiPicker.prototype.render;
// We override render() to inject our custom handleQueryChange.
const self = this;
this.emojiPicker.prototype.render = function() {
if (!this.handleQueryChange.injected) {
this.handleQueryChange = self.handleQueryChange.bind(this, this.handleQueryChange, allEmojis);
this.handleQueryChange.injected = true;
}
return self.originalRender.apply(this, arguments);
};
const messageBarExport = Object.keys(modules.c).map(x => modules.c[x]).find(x => x && x.exports && x.exports.default && x.exports.default.prototype && x.exports.default.prototype.handleStartFileUpload);
if (!messageBarExport || !messageBarExport.exports || !messageBarExport.exports.default) return; // no message bar injection I guess
this.messageBar = messageBarExport.exports.default;
const originalRenderAutocomplete = this.originalRenderAutocomplete = this.messageBar.prototype.renderAutocomplete;
let emojiAutocompleter;
this.messageBar.prototype.renderAutocomplete = function() {
// Remove original autocompleter for emojis, we replace and add functionalities to it.
if (this.props.autocompleteOptions.EMOJI) {
emojiAutocompleter = this.props.autocompleteOptions.EMOJI;
delete this.props.autocompleteOptions.EMOJI;
}
const self = this;
this.props.autocompleteOptions.EMOJITYPER = {
matches(firstChar, rest) {
return firstChar === ":" && rest.length > 1;
},
queryResults(text) {
if (!text.startsWith("@")) return emojiAutocompleter.queryResults(text);
if (self.state.emojiTyperQuery === text.substr(1)) return { emoji: self.state.emojiTyperResults };
self.setState({
emojiTyperQuery: text.substr(1),
emojiTyperResults: []
});
loadEmojiTyperResults(text.substr(1), allEmojis, results => {
if (self.state.emojiTyperQuery !== text.substr(1)) return;
self.setState({
emojiTyperResults: results
});
});
return { emoji: [] };
},
renderResults(query, selectedIndex, handleHover, handleClick, results) {
if (!query.startsWith("@")) return emojiAutocompleter.renderResults.call(this, query, selectedIndex, handleHover, handleClick, results);
const emojiResults = self.state.emojiTyperResults;
if (emojiResults && emojiResults.length) {
results.emoji = emojiResults;
return emojiAutocompleter.renderResults.call(this, "EmojiTyper Results for '" + query.substr(1) + "'", selectedIndex, handleHover, handleClick, { emoji: emojiResults });
}
return emojiAutocompleter.renderResults.call(this, "Loading EmojiTyper results...", selectedIndex, handleHover, handleClick, { emoji: allEmojis.filter(x => x.surrogates === "⏳") });
},
getText: emojiAutocompleter.getText
};
return originalRenderAutocomplete.apply(this, arguments);
};
}
stop() {
if (!this.enabled) return;
this.emojiPicker.prototype.render = this.originalRender;
this.messageBar.prototype.renderAutocomplete = this.originalRenderAutocomplete;
}
handleQueryChange(originalFn, allEmojis, query) {
// If we didn't search for @, just apply our original filtering.
if (query.indexOf("@") !== 0 || query === "@") return originalFn.call(this, query);
// Load results.
loadEmojiTyperResults(query.substr(1), allEmojis, result => {
this.setState({
searchResults: result,
metaData: this.computeMetaData(result)
});
});
// Display an hourglass as "loading indicator".
const loadingEmoji = allEmojis.filter(x => x.allNamesString === ":hourglass_flowing_sand:");
this.setState({
query,
searchResults: loadingEmoji,
metaData: this.computeMetaData(loadingEmoji),
selectedRow: -1,
selectedColumn: -1,
currentSection: null
});
}
failInitialization(message) {
alert("Failed to initialize " + this.getName() + ": " + message + " " + this.getName() + " will not function.");
this.enabled = false;
}
getWebpackModules() {
if (!webpackJsonp) return null;
const req = webpackJsonp([], {
[this.constructor.name]: (module, exports, req) => exports.default = req
}, [this.constructor.name]);
if (!req || !req.default) return null;
const modules = req.default;
if (modules.m) delete modules.m[this.constructor.name];
if (modules.c) delete modules.c[this.constructor.name];
return modules;
}
};
const loadEmojiTyperResults = debounce(function loadEmojiTyperResults(query, allEmojis, callback) {
fetch(`https://emojityper.appspot.com/query?query=${encodeURIComponent(query)}&prefix=true`).then(x => x.json()).then(result => {
const results = result.results;
const emojis = [].concat(...results.map(x => x.slice(1)));
callback(allEmojis.filter(x => emojis.indexOf(x.surrogates) !== -1));
});
}, 300);
function debounce(func, wait) {
let timeout;
return function() {
let context = this, args = arguments;
const later = function() {
timeout = null;
func.apply(context, args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment