add some comments

This commit is contained in:
Jiiks 2018-08-20 03:26:56 +03:00
parent 533566fde8
commit 3fa848bb5a
1 changed files with 150 additions and 82 deletions

View File

@ -29,23 +29,37 @@ const EMOTE_SOURCES = [
export default new class EmoteModule extends BuiltinModule {
/**
* @returns {String} Path to local emote database
*/
get dbpath() { return path.join(Globals.getPath('data'), 'emotes.json') }
/**
* @returns {Map} Cached raw emote database
*/
get database() { return this._db || (this._db = new Map()) }
/**
* @returns {Array} Favourite emotes
*/
get favourites() { return this._favourites || (this._favourites = []) }
/**
* @returns {Array} Most used emotes
*/
get mostUsed() { return this._mostUsed || (this._mostUsed = []) }
get settingPath() { return ['emotes', 'default', 'enable'] }
async enabled() {
// Add ; prefix for autocomplete
GlobalAc.add(';', this);
if (!this.database.size) {
await this.loadLocalDb();
}
// Read favourites and most used from database
const userData = await Database.findOne({ 'id': 'EmoteModule' });
if (userData) {
if (userData.hasOwnProperty('favourites')) this._favourites = userData.favourites;
@ -53,15 +67,121 @@ export default new class EmoteModule extends BuiltinModule {
}
this.patchMessageContent();
this.patchSendAndEdit();
}
async disabled() {
// Unpatch all aptches
for (const patch of Patcher.getPatchesByCaller('BD:EMOTEMODULE')) patch.unpatch();
// Remove ; prefix from autocomplete
GlobalAc.remove(';');
}
/**
* Load emotes from local database
*/
async loadLocalDb() {
const emotes = await FileUtils.readJsonFromFile(this.dbpath);
for (const [index, emote] of emotes.entries()) {
const { type, id, src, value } = emote;
if (index % 10000 === 0) await Utils.wait();
this.database.set(id, { id: emote.value.id || value, type });
}
}
/**
* Patches MessageContent render method
*/
async patchMessageContent() {
const MessageContent = await ReactComponents.getComponent('MessageContent', { selector: WebpackModules.getSelector('container', 'containerCozy', 'containerCompact', 'edited') });
MonkeyPatch('BD:EMOTEMODULE', MessageContent.component.prototype).after('render', this.afterRenderMessageContent.bind(this));
MessageContent.forceUpdateAll();
}
/**
* Handle message render
*/
afterRenderMessageContent(component, args, retVal) {
const markup = Utils.findInReactTree(retVal, filter =>
filter &&
filter.className &&
filter.className.includes('markup') &&
filter.children.length >= 2);
if (!markup) return;
markup.children[1] = this.processMarkup(markup.children[1]);
}
/**
* Patches MessageActions send and edit
*/
patchSendAndEdit() {
MonkeyPatch('BD:EMOTEMODULE', WebpackModules.getModuleByName('MessageActions')).instead('sendMessage', this.handleSendMessage.bind(this));
MonkeyPatch('BD:EMOTEMODULE', WebpackModules.getModuleByName('MessageActions')).instead('editMessage', this.handleEditMessage.bind(this));
}
async disabled() {
for (const patch of Patcher.getPatchesByCaller('BD:EMOTEMODULE')) patch.unpatch();
GlobalAc.remove(';');
/**
* Handle send message
*/
async handleSendMessage(component, args, orig) {
if (!args.length) return orig(...args);
const { content } = args[1];
if (!content) return orig(...args);
const emoteAsImage = Settings.getSetting('emotes', 'default', 'emoteasimage').value &&
(DiscordApi.currentChannel.type === 'DM' || DiscordApi.currentChannel.checkPermissions(DiscordApi.modules.DiscordPermissions.ATTACH_FILES));
if (!emoteAsImage || content.split(' ').length > 1) {
args[1].content = args[1].content.split(' ').map(word => {
const isEmote = /;(.*?);/g.exec(word);
if (isEmote) {
const emote = this.findByName(isEmote[1], true);
if (!emote) return word;
this.addToMostUsed(emote);
return emote ? `:${isEmote[1]}:` : word;
}
return word;
}).join(' ');
return orig(...args);
}
const isEmote = /;(.*?);/g.exec(content);
if (!isEmote) return orig(...args);
const emote = this.findByName(isEmote[1]);
if (!emote) return orig(...args);
this.addToMostUsed(emote);
const FileActions = WebpackModules.getModuleByProps(['makeFile']);
const Uploader = WebpackModules.getModuleByProps(['instantBatchUpload']);
request.get(emote.props.src, { encoding: 'binary' }).then(res => {
const arr = new Uint8Array(new ArrayBuffer(res.length));
for (let i = 0; i < res.length; i++) arr[i] = res.charCodeAt(i);
const suffix = arr[0] === 71 && arr[1] === 73 && arr[2] === 70 ? '.gif' : '.png';
Uploader.upload(args[0], FileActions.makeFile(arr, `${emote.name}${suffix}`));
});
}
/**
* Handle edit message
*/
handleEditMessage(component, args, orig) {
if (!args.length) return orig(...args);
const { content } = args[2];
if (!content) return orig(...args);
args[2].content = args[2].content.split(' ').map(word => {
const isEmote = /;(.*?);/g.exec(word);
return isEmote ? `:${isEmote[1]}:` : word;
}).join(' ');
return orig(...args);
}
/**
* Add/update emote to most used
* @param {Object} emote
*/
addToMostUsed(emote) {
const isMostUsed = this.mostUsed.find(mu => mu.key === emote.name);
if (isMostUsed) {
@ -74,9 +194,14 @@ export default new class EmoteModule extends BuiltinModule {
useCount: 1
});
}
// Save most used to database
// TODO only save first n
Database.insertOrUpdate({ 'id': 'EmoteModule' }, { 'id': 'EmoteModule', favourites: this.favourites, mostused: this.mostUsed })
}
/**
* Inject emotes into markup
*/
processMarkup(markup) {
const newMarkup = [];
if (!(markup instanceof Array)) return markup;
@ -128,97 +253,35 @@ export default new class EmoteModule extends BuiltinModule {
return newMarkup;
}
async patchMessageContent() {
const selector = `.${WebpackModules.getClassName('container', 'containerCozy', 'containerCompact', 'edited')}`;
const MessageContent = await ReactComponents.getComponent('MessageContent', { selector });
MonkeyPatch('BD:EMOTEMODULE', MessageContent.component.prototype).after('render', this.afterRenderMessageContent.bind(this));
MessageContent.forceUpdateAll();
}
afterRenderMessageContent(component, args, retVal) {
const markup = Utils.findInReactTree(retVal, filter =>
filter &&
filter.className &&
filter.className.includes('markup') &&
filter.children.length >= 2);
if (!markup) return;
markup.children[1] = this.processMarkup(markup.children[1]);
}
handleEditMessage(component, args, orig) {
if (!args.length) return orig(...args);
const { content } = args[2];
if (!content) return orig(...args);
args[2].content = args[2].content.split(' ').map(word => {
const isEmote = /;(.*?);/g.exec(word);
return isEmote ? `:${isEmote[1]}:` : word;
}).join(' ');
return orig(...args);
}
async handleSendMessage(component, args, orig) {
if (!args.length) return orig(...args);
const { content } = args[1];
if (!content) return orig(...args);
const emoteAsImage = Settings.getSetting('emotes', 'default', 'emoteasimage').value &&
(DiscordApi.currentChannel.type === 'DM' || DiscordApi.currentChannel.checkPermissions(DiscordApi.modules.DiscordPermissions.ATTACH_FILES));
if (!emoteAsImage || content.split(' ').length > 1) {
args[1].content = args[1].content.split(' ').map(word => {
const isEmote = /;(.*?);/g.exec(word);
if (isEmote) {
const emote = this.findByName(isEmote[1], true);
if (!emote) return word;
this.addToMostUsed(emote);
return emote ? `:${isEmote[1]}:` : word;
}
return word;
}).join(' ');
return orig(...args);
}
const isEmote = /;(.*?);/g.exec(content);
if (!isEmote) return orig(...args);
const emote = this.findByName(isEmote[1]);
if (!emote) return orig(...args);
this.addToMostUsed(emote);
const FileActions = WebpackModules.getModuleByProps(['makeFile']);
const Uploader = WebpackModules.getModuleByProps(['instantBatchUpload']);
request.get(emote.props.src, { encoding: 'binary' }).then(res => {
const arr = new Uint8Array(new ArrayBuffer(res.length));
for (let i = 0; i < res.length; i++) arr[i] = res.charCodeAt(i);
const suffix = arr[0] === 71 && arr[1] === 73 && arr[2] === 70 ? '.gif' : '.png';
Uploader.upload(args[0], FileActions.makeFile(arr, `${emote.name}${suffix}`));
});
}
async loadLocalDb() {
const emotes = await FileUtils.readJsonFromFile(this.dbpath);
for (const [index, emote] of emotes.entries()) {
const { type, id, src, value } = emote;
if (index % 10000 === 0) await Utils.wait();
this.database.set(id, { id: emote.value.id || value, type });
}
}
/**
* Find an emote by name
* @param {String} name Emote name
* @param {Boolean} simple Simple object or Emote instance
* @returns {Object|Emote}
*/
findByName(name, simple = false) {
const emote = this.database.get(name);
if (!emote) return null;
return this.parseEmote(name, emote, simple);
}
/**
* Parsae emote object
* @param {String} name Emote name
* @param {Object} emote Emote object
* @param {Boolean} simple Simple object or Emote instance
* @returns {Object|Emote}
*/
parseEmote(name, emote, simple = false) {
const { type, id } = emote;
if (type < 0 || type > 2) return null;
return simple ? { type, id, name } : new Emote(type, id, name);
}
/**
* Search for autocomplete
* @param {any} regex
*/
acsearch(regex) {
if (regex.length <= 0) {
return {
@ -248,6 +311,11 @@ export default new class EmoteModule extends BuiltinModule {
}
}
/**
* Search for anything else
* @param {any} regex
* @param {any} limit
*/
search(regex, limit = 10) {
if (typeof regex === 'string') regex = new RegExp(regex, 'i');
const matching = [];