const path = window.require('path'); const fs = window.require('fs'); const electron = window.require('electron'); const Module = window.require('module').Module; Module.globalPaths.push(path.resolve(electron.remote.app.getAppPath(), 'node_modules')); const currentWindow = electron.remote.getCurrentWindow(); if (currentWindow.__preload) require(currentWindow.__preload); //Get inject directory if (!process.env.injDir) process.env.injDir = __dirname; //set up global functions const c = { log: function(msg, plugin) { if (plugin && plugin.name) console.log(`%c[EnhancedDiscord] %c[${plugin.name}]`, 'color: red;', `color: ${plugin.color}`, msg); else console.log('%c[EnhancedDiscord]', 'color: red;', msg); }, info: function(msg, plugin) { if (plugin && plugin.name) console.info(`%c[EnhancedDiscord] %c[${plugin.name}]`, 'color: red;', `color: ${plugin.color}`, msg); else console.info('%c[EnhancedDiscord]', 'color: red;', msg); }, warn: function(msg, plugin) { if (plugin && plugin.name) console.warn(`%c[EnhancedDiscord] %c[${plugin.name}]`, 'color: red;', `color: ${plugin.color}`, msg); else console.warn('%c[EnhancedDiscord]', 'color: red;', msg); }, error: function(msg, plugin) { if (plugin && plugin.name) console.error(`%c[EnhancedDiscord] %c[${plugin.name}]`, 'color: red;', `color: ${plugin.color}`, msg); else console.error('%c[EnhancedDiscord]', 'color: red;', msg); }, sleep: function(ms) { return new Promise(resolve => { setTimeout(resolve, ms); }); } } // config util window.ED = { plugins: {}, version: '2.6.2' }; Object.defineProperty(window.ED, 'config', { get: function() { let conf; try{ conf = require('./config.json'); } catch (err) { if(err.code !== 'MODULE_NOT_FOUND') c.error(err); conf = {}; } return conf; }, set: function(newSets = {}) { let confPath; let bDelCache; try{ confPath = require.resolve('./config.json'); bDelCache = true; } catch (err) { if(err.code !== 'MODULE_NOT_FOUND') c.error(err); confPath = path.join(process.env.injDir, 'config.json'); bDelCache = false; } try { fs.writeFileSync(confPath, JSON.stringify(newSets)); if(bDelCache) delete require.cache[confPath]; } catch(err) { c.error(err); } return this.config; } }); function loadPlugin(plugin) { try { if (plugin.preload) console.log(`%c[EnhancedDiscord] %c[PRELOAD] %cLoading plugin %c${plugin.name}`, 'color: red;', 'color: yellow;', '', `color: ${plugin.color}`, `by ${plugin.author}...`); else console.log(`%c[EnhancedDiscord] %cLoading plugin %c${plugin.name}`, 'color: red;', '', `color: ${plugin.color}`, `by ${plugin.author}...`); plugin.load(); } catch(err) { c.error(`Failed to load:\n${err.stack}`, plugin); } } window.ED.localStorage = window.localStorage; process.once("loaded", async () => { c.log(`v${window.ED.version} is running. Validating plugins...`); const pluginFiles = fs.readdirSync(path.join(process.env.injDir, 'plugins')); const plugins = {}; for (const i in pluginFiles) { if (!pluginFiles[i].endsWith('.js') || pluginFiles[i].endsWith(".plugin.js")) continue; let p; const pName = pluginFiles[i].replace(/\.js$/, ''); try { p = require(path.join(process.env.injDir, 'plugins', pName)); if (typeof p.name !== 'string' || typeof p.load !== 'function') { throw new Error('Plugin must have a name and load() function.'); } plugins[pName] = Object.assign(p, {id: pName}); } catch (err) { c.warn(`Failed to load ${pluginFiles[i]}: ${err}\n${err.stack}`, p); } } for (const id in plugins) { if (!plugins[id] || !plugins[id].name || typeof plugins[id].load !== 'function') { c.info(`Skipping invalid plugin: ${id}`); delete plugins[id]; continue; } plugins[id].settings; // this will set default settings in config if necessary } window.ED.plugins = plugins; c.log(`Plugins validated.`); while (!window.webpackJsonp) await c.sleep(100); // wait until this is loaded in order to use it for modules window.ED.webSocket = window._ws; /* Add helper functions that make plugins easy to create */ window.req = window.webpackJsonp.push([[], { '__extra_id__': (module, exports, req) => module.exports = req }, [['__extra_id__']]]); delete window.req.m['__extra_id__']; delete window.req.c['__extra_id__']; window.findModule = window.EDApi.findModule; window.findModules = window.EDApi.findAllModules; window.findRawModule = window.EDApi.findRawModule; window.monkeyPatch = window.EDApi.monkeyPatch; while (!window.EDApi.findModule('dispatch')) await c.sleep(100); c.log(`Loading preload plugins...`); for (const id in plugins) { if (window.ED.config[id] && window.ED.config[id].enabled == false) continue; if (!plugins[id].preload) continue; loadPlugin(plugins[id]); } const d = {resolve: () => {}}; window.monkeyPatch(window.findModule('dispatch'), 'dispatch', {before: b => { // modules seem to all be loaded when RPC server loads if (b.methodArguments[0].type === 'RPC_SERVER_READY') { window.findModule('dispatch').dispatch.unpatch(); d.resolve(); } }}); await new Promise(resolve => { d.resolve = resolve; }) c.log(`Modules done loading (${Object.keys(window.req.c).length})`); if (window.ED.config.bdPlugins) { await require('./bd_shit').setup(currentWindow); c.log(`Preparing BD plugins...`); for (const i in pluginFiles) { if (!pluginFiles[i].endsWith('.js') || !pluginFiles[i].endsWith(".plugin.js")) continue; let p; const pName = pluginFiles[i].replace(/\.js$/, ''); try { p = require(path.join(process.env.injDir, 'plugins', pName)); if (typeof p.name !== 'string' || typeof p.load !== 'function') { throw new Error('Plugin must have a name and load() function.'); } plugins[pName] = Object.assign(p, {id: pName}); } catch (err) { c.warn(`Failed to load ${pluginFiles[i]}: ${err}\n${err.stack}`, p); } } for (const id in plugins) { if (!plugins[id] || !plugins[id].name || typeof plugins[id].load !== 'function') { c.info(`Skipping invalid plugin: ${id}`); delete plugins[id]; continue; } } } c.log(`Loading plugins...`); for (const id in plugins) { if (window.ED.config[id] && window.ED.config[id].enabled == false) continue; if (plugins[id].preload) continue; if (window.ED.config[id].enabled !== true && plugins[id].disabledByDefault) { plugins[id].settings.enabled = false; continue; } loadPlugin(plugins[id]); } const ht = window.EDApi.findModule('hideToken') // prevent client from removing token from localstorage when dev tools is opened, or reverting your token if you change it window.EDApi.monkeyPatch(ht, 'hideToken', () => {}); window.fixedShowToken = () => { // Only allow this to add a token, not replace it. This allows for changing of the token in dev tools. if (!window.ED.localStorage || window.ED.localStorage.getItem("token")) return; return window.ED.localStorage.setItem("token", '"'+ht.getToken()+'"'); }; window.EDApi.monkeyPatch(ht, 'showToken', window.fixedShowToken); if (!window.ED.localStorage.getItem("token") && ht.getToken()) window.fixedShowToken(); // prevent you from being logged out for no reason // change the console warning to be more fun const wc = require('electron').remote.getCurrentWebContents(); wc.removeAllListeners("devtools-opened"); wc.on("devtools-opened", () => { console.log("%cHold Up!", "color: #FF5200; -webkit-text-stroke: 2px black; font-size: 72px; font-weight: bold;"); console.log("%cIf you're reading this, you're probably smarter than most Discord developers.", "font-size: 16px;"); console.log("%cPasting anything in here could actually improve the Discord client.", "font-size: 18px; font-weight: bold; color: red;"); console.log("%cUnless you understand exactly what you're doing, keep this window open to browse our bad code.", "font-size: 16px;"); console.log("%cIf you don't understand exactly what you're doing, you should come work with us: https://discordapp.com/jobs", "font-size: 16px;"); }); }) /* BD/ED joint api */ window.EDApi = window.BdApi = class EDApi { static get React() { return this.findModuleByProps('createElement'); } static get ReactDOM() { return this.findModuleByProps('findDOMNode'); } static escapeID(id) { return id.replace(/^[^a-z]+|[^\w-]+/gi, ""); } static injectCSS(id, css) { const style = document.createElement("style"); style.id = this.escapeID(id); style.innerHTML = css; document.head.append(style); } static clearCSS(id) { const element = document.getElementById(this.escapeID(id)); if (element) element.remove(); } static linkJS(id, url) { return new Promise(resolve => { const script = document.createElement("script"); script.id = this.escapeID(id); script.src = url; script.type = "text/javascript"; script.onload = resolve; document.head.append(script); }); } static unlinkJS(id) { const element = document.getElementById(this.escapeID(id)); if (element) element.remove(); } static getPlugin(name) { const plugin = Object.values(window.ED.plugins).find(p => p.name == name); if (!plugin) return null; return plugin.bdplugin ? plugin.bdplugin : plugin; } static alert(title, body) { const ModalStack = this.findModuleByProps("push", "update", "pop", "popWithKey"); const AlertModal = this.findModule(m => m.prototype && m.prototype.handleCancel && m.prototype.handleSubmit && m.prototype.handleMinorConfirm); if (!ModalStack || !AlertModal) return window.alert(body); ModalStack.push(function(props) { return EDApi.React.createElement(AlertModal, Object.assign({title, body}, props)); }); } static loadData(pluginName, key) { if (!window.ED.config[pluginName]) window.ED.config[pluginName] = {}; return window.ED.config[pluginName][key]; } static saveData(pluginName, key, data) { if (!window.ED.config[pluginName]) window.ED.config[pluginName] = {}; window.ED.config[pluginName][key] = data; window.ED.config = window.ED.config; } static getData(pluginName, key) { return this.loadData(pluginName, key); } static setData(pluginName, key, data) { this.saveData(pluginName, key, data); } static getInternalInstance(node) { if (!(node instanceof window.jQuery) && !(node instanceof Element)) return undefined; if (node instanceof window.jQuery) node = node[0]; return node[Object.keys(node).find(k => k.startsWith("__reactInternalInstance"))]; } static showToast(content, options = {}) { if (!document.querySelector(".toasts")) { const toastWrapper = document.createElement("div"); toastWrapper.classList.add("toasts"); const boundingElement = document.querySelector(".chat-3bRxxu form, #friends, .noChannel-Z1DQK7, .activityFeed-28jde9"); toastWrapper.style.setProperty("left", boundingElement ? boundingElement.getBoundingClientRect().left + "px" : "0px"); toastWrapper.style.setProperty("width", boundingElement ? boundingElement.offsetWidth + "px" : "100%"); toastWrapper.style.setProperty("bottom", (document.querySelector(".chat-3bRxxu form") ? document.querySelector(".chat-3bRxxu form").offsetHeight : 80) + "px"); document.querySelector("." + this.findModule('app').app).appendChild(toastWrapper); } const {type = "", icon = true, timeout = 3000} = options; const toastElem = document.createElement("div"); toastElem.classList.add("toast"); if (type) toastElem.classList.add("toast-" + type); if (type && icon) toastElem.classList.add("icon"); toastElem.innerText = content; document.querySelector(".toasts").appendChild(toastElem); setTimeout(() => { toastElem.classList.add("closing"); setTimeout(() => { toastElem.remove(); if (!document.querySelectorAll(".toasts .toast").length) document.querySelector(".toasts").remove(); }, 300); }, timeout); } static findModule(filter, silent = true) { const moduleName = typeof filter === 'string' ? filter : null; for (const i in window.req.c) { if (window.req.c.hasOwnProperty(i)) { const m = window.req.c[i].exports; if (m && m.__esModule && m.default && (moduleName ? m.default[moduleName] : filter(m.default))) return m.default; if (m && (moduleName ? m[moduleName] : filter(m))) return m; } } if (!silent) c.warn(`Could not find module ${module}.`, {name: 'Modules', color: 'black'}) return null; } static findRawModule(filter, silent = true) { const moduleName = typeof filter === 'string' ? filter : null; for (const i in window.req.c) { if (window.req.c.hasOwnProperty(i)) { const m = window.req.c[i].exports; if (m && m.__esModule && m.default && (moduleName ? m.default[moduleName] : filter(m.default))) return window.req.c[i]; if (m && (moduleName ? m[moduleName] : filter(m))) return window.req.c[i]; } } if (!silent) c.warn(`Could not find module ${module}.`, {name: 'Modules', color: 'black'}) return null; } static findAllModules(filter) { const moduleName = typeof filter === 'string' ? filter : null; const modules = []; for (const i in window.req.c) { if (window.req.c.hasOwnProperty(i)) { const m = window.req.c[i].exports; if (m && m.__esModule && m.default && (moduleName ? m.default[moduleName] : filter(m.default))) modules.push(m.default); else if (m && (moduleName ? m[moduleName] : filter(m))) modules.push(m); } } return modules; } static findModuleByProps(...props) { return this.findModule(module => props.every(prop => module[prop] !== undefined)); } static findModuleByDisplayName(name) { return this.findModule(module => module.displayName === name); } static monkeyPatch(what, methodName, options) { if (typeof options === 'function') { const newOptions = {instead: options, silent: true}; options = newOptions; } const {before, after, instead, once = false, silent = false, force = false} = options; const displayName = options.displayName || what.displayName || what.name || what.constructor ? (what.constructor.displayName || what.constructor.name) : null; if (!silent) console.log(`%c[EnhancedDiscord] %c[Modules]`, 'color: red;', `color: black;`, `Patched ${methodName} in module ${displayName || ''}:`, what); // eslint-disable-line no-console if (!what[methodName]) { if (force) what[methodName] = function() {}; else return console.warn(`%c[EnhancedDiscord] %c[Modules]`, 'color: red;', `color: black;`, `Method ${methodName} doesn't exist in module ${displayName || ''}`, what); // eslint-disable-line no-console } const origMethod = what[methodName]; const cancel = () => { if (!silent) console.log(`%c[EnhancedDiscord] %c[Modules]`, 'color: red;', `color: black;`, `Unpatched ${methodName} in module ${displayName || ''}:`, what); // eslint-disable-line no-console what[methodName] = origMethod; }; what[methodName] = function() { const data = { thisObject: this, methodArguments: arguments, cancelPatch: cancel, originalMethod: origMethod, callOriginalMethod: () => data.returnValue = data.originalMethod.apply(data.thisObject, data.methodArguments) }; if (instead) { const tempRet = EDApi.suppressErrors(instead, "`instead` callback of " + what[methodName].displayName)(data); if (tempRet !== undefined) data.returnValue = tempRet; } else { if (before) EDApi.suppressErrors(before, "`before` callback of " + what[methodName].displayName)(data); data.callOriginalMethod(); if (after) EDApi.suppressErrors(after, "`after` callback of " + what[methodName].displayName)(data); } if (once) cancel(); return data.returnValue; }; what[methodName].__monkeyPatched = true; what[methodName].displayName = "patched " + (what[methodName].displayName || methodName); what[methodName].unpatch = cancel; return cancel; } static testJSON(data) { try { JSON.parse(data); return true; } catch (err) { return false; } } static suppressErrors(method, description) { return (...params) => { try { return method(...params); } catch (e) { console.error("Error occurred in " + description, e); } }; } static formatString(string, values) { for (const val in values) { string = string.replace(new RegExp(`\\{\\{${val}\\}\\}`, 'g'), values[val]); } return string; } static isPluginEnabled(name) { const plugins = Object.values(window.ED.plugins); const plugin = plugins.find(p => p.id == name || p.name == name); if (!plugin) return false; return !(plugin.settings.enabled === false); } static isThemeEnabled() { return false; } static isSettingEnabled(id) { return window.ED.config[id]; } };