Merge pull request #249 from JsSucks/builtin-refactor

Merge for now for e2ee fix.
This commit is contained in:
Alexei Stukov 2018-08-25 18:19:12 +03:00 committed by GitHub
commit 7d05d0c884
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 254 additions and 200 deletions

View File

@ -9,29 +9,32 @@
*/
import BuiltinModule from './BuiltinModule';
import { Patcher, MonkeyPatch, Reflection, ReactComponents } from 'modules';
import { Reflection } from 'modules';
const twelveHour = new RegExp(`([0-9]{1,2}):([0-9]{1,2})\\s(AM|PM)`);
export default new class TwentyFourHour extends BuiltinModule {
get settingPath() {
return ['ui', 'default', '24-hour'];
}
/* Getters */
get moduleName() { return 'TwentyFourHour' }
async enabled(e) {
if (Patcher.getPatchesByCaller('BD:TwentyFourHour').length) return;
get settingPath() { return ['ui', 'default', '24-hour'] }
/* Patches */
applyPatches() {
if (this.patches.length) return;
const { TimeFormatter } = Reflection.modules;
MonkeyPatch('BD:TwentyFourHour', TimeFormatter).after('calendarFormat', (thisObject, args, returnValue) => {
const matched = returnValue.match(twelveHour);
if (!matched || matched.length != 4) return;
if (matched[3] == 'AM') return returnValue.replace(matched[0], `${matched[1] == '12' ? '00' : matched[1].padStart(2, '0')}:${matched[2]}`)
return returnValue.replace(matched[0], `${parseInt(matched[1]) + 12}:${matched[2]}`)
});
this.patch(TimeFormatter, 'calendarFormat', this.convertTimeStamps);
}
disabled(e) {
Patcher.unpatchAll('BD:TwentyFourHour');
/**
* Convert 12 hours timestamps to 24 hour timestamps
*/
convertTimeStamps(that, args, returnValue) {
const matched = returnValue.match(twelveHour);
if (!matched || matched.length !== 4) return;
if (matched[3] === 'AM') return returnValue.replace(matched[0], `${matched[1] === '12' ? '00' : matched[1].padStart(2, '0')}:${matched[2]}`)
return returnValue.replace(matched[0], `${parseInt(matched[1]) + 12}:${matched[2]}`)
}
}

View File

@ -9,41 +9,48 @@
*/
import BuiltinModule from './BuiltinModule';
import { Patcher, MonkeyPatch, Reflection, ReactComponents } from 'modules';
import { Reflection } from 'modules';
export default new class BlockedMessages extends BuiltinModule {
get settingPath() {
return ['ui', 'default', 'blocked-messages'];
}
/* Getters */
get moduleName() { return 'BlockedMessages' }
static isBlocked(id) {
const { RelationshipStore } = Reflection.modules;
return RelationshipStore.isBlocked(id);
}
get settingPath() { return ['ui', 'default', 'blocked-messages'] }
async enabled(e) {
if (Patcher.getPatchesByCaller('BD:BlockedMessages').length) return;
const { MessageActions } = Reflection.modules;
MonkeyPatch('BD:BlockedMessages', MessageActions).instead('receiveMessage', this.processMessage);
const MessageListComponents = Reflection.module.byProps('BlockedMessageGroup');
MessageListComponents.OriginalBlockedMessageGroup = MessageListComponents.BlockedMessageGroup;
MessageListComponents.BlockedMessageGroup = () => {return null;};
MessageListComponents.BlockedMessageGroup = () => { return null; };
this.cancelBlockedMessages = () => {
MessageListComponents.BlockedMessageGroup = MessageListComponents.OriginalBlockedMessageGroup;
delete MessageListComponents.OriginalBlockedMessageGroup;
}
}
processMessage(thisObject, args, originalFunction) {
disabled(e) {
if (this.cancelBlockedMessages) this.cancelBlockedMessages();
}
/* Methods */
static isBlocked(id) {
const { RelationshipStore } = Reflection.modules;
return RelationshipStore.isBlocked(id);
}
/* Patches */
applyPatches() {
if (this.patches.length) return;
const { MessageActions } = Reflection.modules;
this.patch(MessageActions, 'receiveMessage', this.processMessage, 'instead');
}
/**
* Ignore blocked messages completely
*/
processMessage(that, args, originalFunction) {
if (args[1] && args[1].author && args[1].author.id && BlockedMessages.isBlocked(args[1].author.id)) return;
return originalFunction(...args);
}
disabled(e) {
Patcher.unpatchAll('BD:BlockedMessages');
if (this.cancelBlockedMessages) this.cancelBlockedMessages();
}
}

View File

@ -11,58 +11,57 @@
import BuiltinModule from './BuiltinModule';
import { Utils } from 'common';
import { Settings, Patcher, MonkeyPatch, Reflection, ReactComponents, DiscordApi } from 'modules';
import { Settings, Reflection, ReactComponents, DiscordApi } from 'modules';
export default new class ColoredText extends BuiltinModule {
/* Getters */
get moduleName() { return 'ColoredText' }
get settingPath() { return ['ui', 'default', 'colored-text'] }
get intensityPath() { return ['ui', 'advanced', 'colored-text-intensity'] }
get intensitySetting() { return Settings.getSetting(...this.intensityPath) }
get intensity() { return 100 - this.intensitySetting.value }
get defaultColor() { return DiscordApi.UserSettings.theme == 'light' ? '#747f8d' : '#dcddde' }
constructor() {
super();
this._intensityUpdated = this._intensityUpdated.bind(this);
this.injectColoredText = this.injectColoredText.bind(this);
}
get settingPath() {
return ['ui', 'default', 'colored-text'];
async enabled(e) {
this.intensitySetting.off('setting-updated', this._intensityUpdated);
this.intensitySetting.on('setting-updated', this._intensityUpdated);
}
get intensityPath() {
return ['ui', 'advanced', 'colored-text-intensity'];
}
get intensitySetting() {
return Settings.getSetting(...this.intensityPath);
}
get intensity() {
return 100 - this.intensitySetting.value;
disabled(e) {
this.intensitySetting.off('setting-updated', this._intensityUpdated);
}
/* Methods */
_intensityUpdated() {
this.MessageContent.forceUpdateAll();
}
async enabled(e) {
if (Patcher.getPatchesByCaller('BD:ColoredText').length) return;
this.intensitySetting.on('setting-updated', this._intensityUpdated);
/* Patches */
async applyPatches() {
if (this.patches.length) return;
this.MessageContent = await ReactComponents.getComponent('MessageContent', { selector: Reflection.resolve('container', 'containerCozy', 'containerCompact', 'edited').selector });
MonkeyPatch('BD:ColoredText', this.MessageContent.component.prototype).after('render', this.injectColoredText);
this.patch(this.MessageContent.component.prototype, 'render', this.injectColoredText);
this.MessageContent.forceUpdateAll();
}
/**
* Set markup text colour to match role colour
*/
injectColoredText(thisObject, args, returnValue) {
const { TinyColor } = Reflection.modules;
const markup = Utils.findInReactTree(returnValue, m => m && m.props && m.props.className && m.props.className.includes('da-markup'));
const roleColor = thisObject.props.message.colorString;
if (markup && roleColor) markup.props.style = {color: TinyColor.mix(roleColor, this.defaultColor, this.intensity)};
}
get defaultColor() {
return DiscordApi.UserSettings.theme == 'light' ? '#747f8d' : '#dcddde';
}
disabled(e) {
Patcher.unpatchAll('BD:ColoredText');
this.intensitySetting.off('setting-updated', this._intensityUpdated);
}
}

View File

@ -137,7 +137,7 @@ export default new class E2EE extends BuiltinModule {
const items = Settings.getSetting('security', 'e2eedb', 'e2ekvps').items;
const index = items.findIndex(kvp => kvp.value.key === channelId);
if (index > -1) {
items[index].value = {key: channelId, value: key};
items[index].value = { key: channelId, value: key };
return;
}
Settings.getSetting('security', 'e2eedb', 'e2ekvps').addItem({ value: { key: channelId, value: key } });
@ -223,7 +223,7 @@ export default new class E2EE extends BuiltinModule {
const parsed = MessageParser.parse(currentChannel, decrypt).content;
if (userMentionPattern.test(parsed))
event.message.mentions = parsed.match(userMentionPattern).map(m => {return {id: m.replace(/[^0-9]/g, '')}});
event.message.mentions = parsed.match(userMentionPattern).map(m => { return { id: m.replace(/[^0-9]/g, '') } });
if (roleMentionPattern.test(parsed))
event.message.mention_roles = parsed.match(roleMentionPattern).map(m => m.replace(/[^0-9]/g, ''));
if (everyoneMentionPattern.test(parsed))
@ -245,7 +245,8 @@ export default new class E2EE extends BuiltinModule {
const key = this.getKey(component.props.message.channel_id);
if (!key) return; // We don't have a key for this channel
const { Message, MessageParser, Permissions, DiscordConstants } = Reflection.modules;
const Message = Reflection.module.byPrototypes('isMentioned');
const { MessageParser, Permissions, DiscordConstants } = Reflection.modules;
const currentChannel = DiscordApi.Channel.fromId(component.props.message.channel_id).discordObject;
if (typeof component.props.message.content !== 'string') return; // Ignore any non string content
@ -261,7 +262,7 @@ export default new class E2EE extends BuiltinModule {
const message = MessageParser.createMessage(currentChannel.id, MessageParser.parse(currentChannel, decrypt).content);
if (userMentionPattern.test(message.content))
message.mentions = message.content.match(userMentionPattern).map(m => {return {id: m.replace(/[^0-9]/g, '')}});
message.mentions = message.content.match(userMentionPattern).map(m => { return { id: m.replace(/[^0-9]/g, '') } });
if (roleMentionPattern.test(message.content))
message.mention_roles = message.content.match(roleMentionPattern).map(m => m.replace(/[^0-9]/g, ''));
if (everyoneMentionPattern.test(message.content))

View File

@ -13,6 +13,7 @@ import { Settings } from 'modules';
import BuiltinModule from './BuiltinModule';
import EmoteModule from './EmoteModule';
import GlobalAc from '../ui/autocomplete';
import { BdContextMenu } from 'ui';
const EMOTE_SOURCES = [
'https://static-cdn.jtvnw.net/emoticons/v1/:id/1.0',
@ -22,14 +23,46 @@ const EMOTE_SOURCES = [
export default new class EmoteAc extends BuiltinModule {
/* Getters */
get moduleName() { return 'EmoteAC' }
get settingPath() { return ['emotes', 'default', 'emoteac'] }
async enabled(e) {
GlobalAc.add(';', this);
window.removeEventListener('contextmenu', this.acCm);
window.addEventListener('contextmenu', this.acCm);
}
disabled(e) {
GlobalAc.remove(';');
window.removeEventListener('contextmenu', this.acCm);
}
/* Methods */
acCm(e) {
const row = e.target.closest('.bd-emotAc');
if (!row) return;
const img = row.querySelector('img');
if (!img || !img.alt) return;
BdContextMenu.show(e, [
{
text: 'Test',
items: [
{
text: 'Favourite',
type: 'toggle',
checked: EmoteModule.isFavourite(img.alt.replace(/;/g, '')),
onChange: checked => {
if (!img || !img.alt) return;
const emote = img.alt.replace(/;/g, '');
if (!checked) return EmoteModule.removeFavourite(emote), false;
return EmoteModule.addFavourite(emote), true;
}
}
]
}
]);
}
/**
@ -51,7 +84,8 @@ export default new class EmoteAc extends BuiltinModule {
hint: mu.useCount ? `Used ${mu.useCount} times` : null
}
}
})
}),
extraClasses: ['bd-emotAc']
}
}
@ -63,7 +97,8 @@ export default new class EmoteAc extends BuiltinModule {
result.value.src = EMOTE_SOURCES[result.value.type].replace(':id', result.value.id);
result.value.replaceWith = `;${result.key};`;
return result;
})
}),
extraClasses: ['bd-emotAc']
}
}

View File

@ -12,9 +12,9 @@ import BuiltinModule from './BuiltinModule';
import path from 'path';
import { request } from 'vendor';
import { Utils, FileUtils, ClientLogger as Logger } from 'common';
import { DiscordApi, Settings, Globals, Reflection, ReactComponents, MonkeyPatch, Cache, Patcher, Database } from 'modules';
import { VueInjector, DiscordContextMenu } from 'ui';
import { Utils, FileUtils } from 'common';
import { DiscordApi, Settings, Globals, Reflection, ReactComponents, Database } from 'modules';
import { DiscordContextMenu } from 'ui';
import Emote from './EmoteComponent.js';
import Autocomplete from '../ui/components/common/Autocomplete.vue';
@ -29,6 +29,10 @@ const EMOTE_SOURCES = [
export default new class EmoteModule extends BuiltinModule {
/* Getters */
get moduleName() { return 'EmoteModule' }
/**
* @returns {String} Path to local emote database
*/
@ -76,13 +80,14 @@ export default new class EmoteModule extends BuiltinModule {
// Read favourites and most used from database
await this.loadUserData();
this.patchMessageContent();
this.patchSendAndEdit();
const ImageWrapper = await ReactComponents.getComponent('ImageWrapper', { selector: Reflection.resolve('imageWrapper').selector });
MonkeyPatch('BD:EMOTEMODULE', ImageWrapper.component.prototype).after('render', this.beforeRenderImageWrapper.bind(this));
}
async disabled() {
DiscordContextMenu.remove(this.favCm);
}
/* Methods */
/**
* Adds an emote to favourites.
* @param {Object|String} emote
@ -115,12 +120,6 @@ export default new class EmoteModule extends BuiltinModule {
return !!this.favourites.find(e => e.name === emote || e.name === emote.name);
}
async disabled() {
// Unpatch all patches
for (const patch of Patcher.getPatchesByCaller('BD:EMOTEMODULE')) patch.unpatch();
DiscordContextMenu.remove(this.favCm);
}
/**
* Load emotes from local database
*/
@ -151,15 +150,96 @@ export default new class EmoteModule extends BuiltinModule {
});
}
/**
* Add/update emote to most used
* @param {Object} emote emote to add/update
* @return {Promise}
*/
addToMostUsed(emote) {
const isMostUsed = this.mostUsed.find(mu => mu.key === emote.name);
if (isMostUsed) {
isMostUsed.useCount += 1;
} else {
this.mostUsed.push({
key: emote.name,
id: emote.id,
type: emote.type,
useCount: 1
});
}
// Save most used to database
// TODO only save first n
return this.saveUserData();
}
/**
* 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);
}
/**
* Parse 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 anything else
* @param {any} regex
* @param {any} limit
*/
search(regex, limit = 10) {
if (typeof regex === 'string') regex = new RegExp(regex, 'i');
const matching = [];
for (const [key, value] of this.database.entries()) {
if (matching.length >= limit) break;
if (regex.test(key)) matching.push({ key, value })
}
return matching;
}
/* Patches */
async applyPatches() {
this.patchMessageContent();
this.patchSendAndEdit();
const ImageWrapper = await ReactComponents.getComponent('ImageWrapper', { selector: Reflection.resolve('imageWrapper').selector });
this.patch(ImageWrapper.component.prototype, 'render', this.beforeRenderImageWrapper, 'before');
}
/**
* Patches MessageContent render method
*/
async patchMessageContent() {
const MessageContent = await ReactComponents.getComponent('MessageContent', { selector: Reflection.resolve('container', 'containerCozy', 'containerCompact', 'edited').selector });
MonkeyPatch('BD:EMOTEMODULE', MessageContent.component.prototype).after('render', this.afterRenderMessageContent.bind(this));
this.patch(MessageContent.component.prototype, 'render', this.afterRenderMessageContent);
MessageContent.forceUpdateAll();
}
/**
* Patches MessageActions send and edit
*/
patchSendAndEdit() {
const { MessageActions } = Reflection.modules;
this.patch(MessageActions, 'sendMessage', this.handleSendMessage, 'instead');
this.patch(MessageActions, 'editMessage', this.handleEditMessage, 'instead');
}
/**
* Handle message render
*/
@ -173,15 +253,6 @@ export default new class EmoteModule extends BuiltinModule {
markup.children[1] = this.processMarkup(markup.children[1]);
}
/**
* Patches MessageActions send and edit
*/
patchSendAndEdit() {
const { MessageActions } = Reflection.modules;
MonkeyPatch('BD:EMOTEMODULE', MessageActions).instead('sendMessage', this.handleSendMessage.bind(this));
MonkeyPatch('BD:EMOTEMODULE', MessageActions).instead('editMessage', this.handleEditMessage.bind(this));
}
/**
* Handle send message
*/
@ -253,28 +324,6 @@ export default new class EmoteModule extends BuiltinModule {
retVal.props.children = emote.render();
}
/**
* Add/update emote to most used
* @param {Object} emote emote to add/update
* @return {Promise}
*/
addToMostUsed(emote) {
const isMostUsed = this.mostUsed.find(mu => mu.key === emote.name);
if (isMostUsed) {
isMostUsed.useCount += 1;
} else {
this.mostUsed.push({
key: emote.name,
id: emote.id,
type: emote.type,
useCount: 1
});
}
// Save most used to database
// TODO only save first n
return this.saveUserData();
}
/**
* Inject emotes into markup
*/
@ -330,46 +379,4 @@ export default new class EmoteModule extends BuiltinModule {
return newMarkup;
}
/**
* 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);
}
/**
* Parse 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 anything else
* @param {any} regex
* @param {any} limit
*/
search(regex, limit = 10) {
if (typeof regex === 'string') regex = new RegExp(regex, 'i');
const matching = [];
for (const [key, value] of this.database.entries()) {
if (matching.length >= limit) break;
if (regex.test(key)) matching.push({ key, value })
}
return matching;
}
}

View File

@ -9,22 +9,20 @@
*/
import BuiltinModule from './BuiltinModule';
import { Patcher, MonkeyPatch, Reflection } from 'modules';
import { Reflection } from 'modules';
export default new class KillClyde extends BuiltinModule {
get settingPath() {
return ['ui', 'default', 'kill-clyde'];
}
/* Getters */
get moduleName() { return 'KillClyde' }
async enabled(e) {
if (Patcher.getPatchesByCaller('BD:KillClyde').length) return;
get settingPath() { return ['ui', 'default', 'kill-clyde'] }
/* Patches */
applyPatches() {
if (this.patches.length) return;
const { MessageActions } = Reflection.modules;
MonkeyPatch('BD:KillClyde', MessageActions).instead('sendBotMessage', void 0);
}
disabled(e) {
Patcher.unpatchAll('BD:KillClyde');
this.patch(MessageActions, 'sendBotMessage', () => void 0, 'instead');
}
}

View File

@ -18,15 +18,16 @@ import { Toasts } from 'ui';
export default new class ReactDevtoolsModule extends BuiltinModule {
/* Getters */
get moduleName() { return 'ReactDevTools' }
get settingPath() { return ['core', 'advanced', 'react-devtools'] }
constructor() {
super();
this.devToolsOpened = this.devToolsOpened.bind(this);
}
get settingPath() {
return ['core', 'advanced', 'react-devtools'];
}
enabled(e) {
electron.remote.getCurrentWindow().webContents.on('devtools-opened', this.devToolsOpened);
if (electron.remote.getCurrentWindow().isDevToolsOpened) this.devToolsOpened();

View File

@ -10,27 +10,27 @@
import BuiltinModule from './BuiltinModule';
import { Patcher, MonkeyPatch, Reflection } from 'modules';
import { Reflection } from 'modules';
export default new class E2EE extends BuiltinModule {
get settingPath() {
return ['security', 'default', 'tracking-protection'];
/* Getters */
get moduleName() { return 'TrackingProtection' }
get settingPath() { return ['security', 'default', 'tracking-protection'] }
/* Patches */
applyPatches() {
if (this.patches.length) return;
const TrackingModule = Reflection.module.byProps('track');
if (!TrackingModule) {
this.warn('Tracking module not found!');
return;
}
this.patch(TrackingModule, 'track', this.track, 'instead');
}
track(e) {
// console.log('Blocked Tracking');
this.debug('Tracking blocked');
}
enabled(e) {
if (Patcher.getPatchesByCaller('BD:TrackingProtection').length) return;
const trackingModule = Reflection.module.byProps('track');
if (!trackingModule) return; // TODO Log it
MonkeyPatch('BD:TrackingProtection', trackingModule).instead('track', this.track);
}
disabled(e) {
for (const patch of Patcher.getPatchesByCaller('BD:TrackingProtection')) patch.unpatch();
}
}

View File

@ -13,21 +13,23 @@ import { Reflection } from 'modules';
export default new class VoiceDisconnect extends BuiltinModule {
get settingPath() {
return ['core', 'default', 'voice-disconnect'];
}
/* Getters */
get moduleName() { return 'VoiceDisconnect' }
get settingPath() { return ['core', 'default', 'voice-disconnect'] }
async enabled(e) {
window.addEventListener('beforeunload', this.listener);
}
listener() {
const { VoiceChannelActions } = Reflection.modules;
VoiceChannelActions.selectVoiceChannel(null, null);
}
disabled(e) {
window.removeEventListener('beforeunload', this.listener);
}
/* Methods */
listener() {
const { VoiceChannelActions } = Reflection.modules;
VoiceChannelActions.selectVoiceChannel(null, null);
}
}

View File

@ -18,15 +18,16 @@ import { Toasts } from 'ui';
export default new class VueDevtoolsModule extends BuiltinModule {
/* Getters */
get moduleName() { return 'VueDevTools' }
get settingPath() { return ['core', 'advanced', 'vue-devtools'] }
constructor() {
super();
this.devToolsOpened = this.devToolsOpened.bind(this);
}
get settingPath() {
return ['core', 'advanced', 'vue-devtools'];
}
enabled(e) {
electron.remote.getCurrentWindow().webContents.on('devtools-opened', this.devToolsOpened);
if (electron.remote.getCurrentWindow().isDevToolsOpened) this.devToolsOpened();

View File

@ -21,7 +21,7 @@
</div>
</div>
<div class="bd-acScroller" ref="scroller">
<div v-for="(item, index) in search.items" class="bd-acRow" @mouseover="selectedIndex = index" @click="inject">
<div v-for="(item, index) in search.items" class="bd-acRow" :class="search.extraClasses" @mouseover="selectedIndex = index" @click="inject">
<div class="bd-acSelector bd-selectable" :class="{'bd-selected': index === selectedIndex}">
<div class="bd-acField">
<img v-if="search.type === 'imagetext'" :src="item.src || item.value.src" :alt="item.key || item.text || item.alt" />