diff --git a/BetterDiscordApp/src/modules/bdApi.js b/BetterDiscordApp/src/modules/bdApi.js index 41e52e4..c5b9271 100644 --- a/BetterDiscordApp/src/modules/bdApi.js +++ b/BetterDiscordApp/src/modules/bdApi.js @@ -1,4 +1,12 @@ -import {pluginCookie, themeCookie, bdplugins, bdthemes, settingsCookie, settings} from "../0globals"; +import { + pluginCookie, + themeCookie, + bdplugins, + bdthemes, + settingsCookie, + settings, + bdEmotes, +} from "../0globals"; import mainCore from "./core"; import Utils from "./utils"; import BDV2 from "./v2"; @@ -9,67 +17,96 @@ import settingsPanel from "./settingsPanel"; import DOM from "./domtools"; const BdApi = { - get React() { return BDV2.React; }, - get ReactDOM() { return BDV2.ReactDom; }, - get ReactComponent() {return BDV2.ReactComponent;}, - get WindowConfigFile() {return Utils.WindowConfigFile;}, - get settings() {return settings;}, - get emotes() {return null}, // deprecated, deleted all emotes from betterdiscord. - get screenWidth() { return Math.max(document.documentElement.clientWidth, window.innerWidth || 0); }, - get screenHeight() { return Math.max(document.documentElement.clientHeight, window.innerHeight || 0); } + get React() { + return BDV2.React; + }, + get ReactDOM() { + return BDV2.ReactDom; + }, + get ReactComponent() { + return BDV2.ReactComponent; + }, + get WindowConfigFile() { + return Utils.WindowConfigFile; + }, + get settings() { + return settings; + }, + get emotes() { + return bdEmotes; + }, + get screenWidth() { + return Math.max( + document.documentElement.clientWidth, + window.innerWidth || 0 + ); + }, + get screenHeight() { + return Math.max( + document.documentElement.clientHeight, + window.innerHeight || 0 + ); + }, }; -BdApi.getAllWindowPreferences = function() { - return Utils.getAllWindowPreferences(); +BdApi.getAllWindowPreferences = function () { + return Utils.getAllWindowPreferences(); }; -BdApi.getWindowPreference = function(key) { - return Utils.getWindowPreference(key); +BdApi.getWindowPreference = function (key) { + return Utils.getWindowPreference(key); }; -BdApi.setWindowPreference = function(key, value) { - return Utils.setWindowPreference(key, value); +BdApi.setWindowPreference = function (key, value) { + return Utils.setWindowPreference(key, value); }; //Inject CSS to document head //id = id of element //css = custom css BdApi.injectCSS = function (id, css) { - DOM.addStyle(DOM.escapeID(id), css); + DOM.addStyle(DOM.escapeID(id), css); }; //Clear css/remove any element //id = id of element BdApi.clearCSS = function (id) { - DOM.removeStyle(DOM.escapeID(id)); + DOM.removeStyle(DOM.escapeID(id)); }; //Inject CSS to document head //id = id of element //css = custom css BdApi.linkJS = function (id, url) { - DOM.addScript(DOM.escapeID(id), url); + DOM.addScript(DOM.escapeID(id), url); }; //Clear css/remove any element //id = id of element BdApi.unlinkJS = function (id) { - DOM.removeScript(DOM.escapeID(id)); + DOM.removeScript(DOM.escapeID(id)); }; //Get another plugin //name = name of plugin BdApi.getPlugin = function (name) { - if (bdplugins.hasOwnProperty(name)) { - return bdplugins[name].plugin; - } - return null; + Utils.warn( + "Deprecation Notice", + `BdApi.getPlugin() will be removed in future versions. Please use the BdApi.Plugins API` + ); + if (bdplugins.hasOwnProperty(name)) { + return bdplugins[name].plugin; + } + return null; }; //Get BetterDiscord Core BdApi.getCore = function () { - Utils.warn("Deprecation Notice", `BdApi.getCore() will be removed in future versions.`); - return mainCore; + Utils.warn( + "Deprecation Notice", + `BdApi.getCore() will be removed in future versions.` + ); + return mainCore; }; /** @@ -78,7 +115,7 @@ BdApi.getCore = function () { * @param {string} content - a string of text to display in the modal */ BdApi.alert = function (title, content) { - return Utils.showConfirmationModal(title, content, {cancelText: null}); + return Utils.showConfirmationModal(title, content, { cancelText: null }); }; /** @@ -95,160 +132,169 @@ BdApi.alert = function (title, content) { * @returns {string} - the key used for this modal */ BdApi.showConfirmationModal = function (title, content, options = {}) { - return Utils.showConfirmationModal(title, content, options); + return Utils.showConfirmationModal(title, content, options); }; //Show toast alert -BdApi.showToast = function(content, options = {}) { - Utils.showToast(content, options); +BdApi.showToast = function (content, options = {}) { + Utils.showToast(content, options); }; // Finds module -BdApi.findModule = function(filter) { - return BDV2.WebpackModules.find(filter); +BdApi.findModule = function (filter) { + return BDV2.WebpackModules.find(filter); }; // Finds module -BdApi.findAllModules = function(filter) { - return BDV2.WebpackModules.findAll(filter); +BdApi.findAllModules = function (filter) { + return BDV2.WebpackModules.findAll(filter); }; // Finds module -BdApi.findModuleByProps = function(...props) { - return BDV2.WebpackModules.findByUniqueProperties(props); +BdApi.findModuleByProps = function (...props) { + return BDV2.WebpackModules.findByUniqueProperties(props); }; -BdApi.findModuleByPrototypes = function(...protos) { - return BDV2.WebpackModules.findByPrototypes(protos); +BdApi.findModuleByPrototypes = function (...protos) { + return BDV2.WebpackModules.findByPrototypes(protos); }; -BdApi.findModuleByDisplayName = function(name) { - return BDV2.WebpackModules.findByDisplayName(name); +BdApi.findModuleByDisplayName = function (name) { + return BDV2.WebpackModules.findByDisplayName(name); }; // Gets react instance -BdApi.getInternalInstance = function(node) { - if (!(node instanceof window.jQuery) && !(node instanceof Element)) return undefined; - if (node instanceof jQuery) node = node[0]; - return BDV2.getInternalInstance(node); +BdApi.getInternalInstance = function (node) { + if (!(node instanceof window.jQuery) && !(node instanceof Element)) + return undefined; + if (node instanceof jQuery) node = node[0]; + return BDV2.getInternalInstance(node); }; // Gets data -BdApi.loadData = function(pluginName, key) { - return DataStore.getPluginData(pluginName, key); +BdApi.loadData = function (pluginName, key) { + return DataStore.getPluginData(pluginName, key); }; BdApi.getData = BdApi.loadData; // Sets data -BdApi.saveData = function(pluginName, key, data) { - return DataStore.setPluginData(pluginName, key, data); +BdApi.saveData = function (pluginName, key, data) { + return DataStore.setPluginData(pluginName, key, data); }; BdApi.setData = BdApi.saveData; // Deletes data -BdApi.deleteData = function(pluginName, key) { - return DataStore.deletePluginData(pluginName, key); +BdApi.deleteData = function (pluginName, key) { + return DataStore.deletePluginData(pluginName, key); }; // Patches other functions -BdApi.monkeyPatch = function(what, methodName, options) { - return Utils.monkeyPatch(what, methodName, options); +BdApi.monkeyPatch = function (what, methodName, options) { + return Utils.monkeyPatch(what, methodName, options); }; // Event when element is removed -BdApi.onRemoved = function(node, callback) { - return Utils.onRemoved(node, callback); +BdApi.onRemoved = function (node, callback) { + return Utils.onRemoved(node, callback); }; // Wraps function in try..catch -BdApi.suppressErrors = function(method, message) { - return Utils.suppressErrors(method, message); +BdApi.suppressErrors = function (method, message) { + return Utils.suppressErrors(method, message); }; // Tests for valid JSON -BdApi.testJSON = function(data) { - return Utils.testJSON(data); +BdApi.testJSON = function (data) { + return Utils.testJSON(data); }; -BdApi.isPluginEnabled = function(name) { - return !!pluginCookie[name]; +BdApi.isPluginEnabled = function (name) { + Utils.warn( + "Deprecation Notice", + `BdApi.isPluginEnabled() will be removed in future versions. Please use the BdApi.Plugins API` + ); + return !!pluginCookie[name]; }; -BdApi.isThemeEnabled = function(name) { - return !!themeCookie[name]; +BdApi.isThemeEnabled = function (name) { + Utils.warn( + "Deprecation Notice", + `BdApi.isThemeEnabled() will be removed in future versions. Please use the BdApi.Themes API` + ); + return !!themeCookie[name]; }; -BdApi.isSettingEnabled = function(id) { - return !!settingsCookie[id]; +BdApi.isSettingEnabled = function (id) { + return !!settingsCookie[id]; }; -BdApi.enableSetting = function(id) { - return settingsPanel.onChange(id, true); +BdApi.enableSetting = function (id) { + return settingsPanel.onChange(id, true); }; -BdApi.disableSetting = function(id) { - return settingsPanel.onChange(id, false); +BdApi.disableSetting = function (id) { + return settingsPanel.onChange(id, false); }; -BdApi.toggleSetting = function(id) { - return settingsPanel.onChange(id, !settingsCookie[id]); +BdApi.toggleSetting = function (id) { + return settingsPanel.onChange(id, !settingsCookie[id]); }; // Gets data -BdApi.getBDData = function(key) { - return DataStore.getBDData(key); +BdApi.getBDData = function (key) { + return DataStore.getBDData(key); }; // Sets data -BdApi.setBDData = function(key, data) { - return DataStore.setBDData(key, data); +BdApi.setBDData = function (key, data) { + return DataStore.setBDData(key, data); }; - - -const makeAddonAPI = (cookie, list, manager) => new class AddonAPI { - - get folder() {return manager.folder;} +const makeAddonAPI = (cookie, list, manager) => + new (class AddonAPI { + get folder() { + return manager.folder; + } isEnabled(name) { - return !!cookie[name]; + return !!cookie[name]; } enable(name) { - return manager.enable(name); + return manager.enable(name); } disable(name) { - return manager.disable(name); + return manager.disable(name); } toggle(name) { - if (cookie[name]) this.disable(name); - else this.enable(name); + if (cookie[name]) this.disable(name); + else this.enable(name); } reload(name) { - return manager.reload(name); + return manager.reload(name); } get(name) { - if (list.hasOwnProperty(name)) { - if (list[name].plugin) return list[name].plugin; - return list[name]; - } - return null; + if (list.hasOwnProperty(name)) { + if (list[name].plugin) return list[name].plugin; + return list[name]; + } + return null; } getAll() { - return Object.keys(list).map(k => this.get(k)).filter(a => a); + return Object.keys(list) + .map((k) => this.get(k)) + .filter((a) => a); } -}; + })(); BdApi.Plugins = makeAddonAPI(pluginCookie, bdplugins, pluginModule); BdApi.Themes = makeAddonAPI(themeCookie, bdthemes, themeModule); export default BdApi; - -window.Lightcord.BetterDiscord.BdApi = BdApi \ No newline at end of file diff --git a/BetterDiscordApp/src/modules/contentManager.js b/BetterDiscordApp/src/modules/contentManager.js index c4d234f..bbc0229 100644 --- a/BetterDiscordApp/src/modules/contentManager.js +++ b/BetterDiscordApp/src/modules/contentManager.js @@ -1,351 +1,478 @@ -import {bdConfig, bdplugins, bdthemes, settingsCookie} from "../0globals"; +import { bdConfig, bdplugins, bdthemes, settingsCookie } from "../0globals"; import pluginModule from "./pluginModule"; import themeModule from "./themeModule"; import Utils from "./utils"; import dataStore from "./dataStore"; -import { encryptSettingsCache, decryptSettingsCache, processFile } from "./pluginCertifier"; -import * as electron from "electron" +import { + encryptSettingsCache, + decryptSettingsCache, + processFile, +} from "./pluginCertifier"; +import * as electron from "electron"; const path = require("path"); const fs = require("fs"); const Module = require("module").Module; -Module.globalPaths.push(path.resolve(electron.ipcRenderer.sendSync("LIGHTCORD_GET_APP_PATH"), "node_modules")); +Module.globalPaths.push( + path.resolve( + electron.ipcRenderer.sendSync("LIGHTCORD_GET_APP_PATH"), + "node_modules" + ) +); class MetaError extends Error { - constructor(message) { - super(message); - this.name = "MetaError"; - } + constructor(message) { + super(message); + this.name = "MetaError"; + } } const originalJSRequire = Module._extensions[".js"]; -const originalCSSRequire = Module._extensions[".css"] ? Module._extensions[".css"] : () => {return null;}; +const originalCSSRequire = Module._extensions[".css"] + ? Module._extensions[".css"] + : () => { + return null; + }; const splitRegex = /[^\S\r\n]*?(?:\r\n|\n)[^\S\r\n]*?\*[^\S\r\n]?/; const escapedAtRegex = /^\\@/; -export let addonCache = {} +export let addonCache = {}; -let hasPatched = false -export default new class ContentManager { +let hasPatched = false; +export default new (class ContentManager { + constructor() { + this.timeCache = {}; + this.watchers = {}; + } - constructor() { - this.timeCache = {}; - this.watchers = {}; + patchExtensions() { + if (hasPatched) return; + hasPatched = true; + Module._extensions[".js"] = this.getContentRequire("plugin"); + Module._extensions[".css"] = this.getContentRequire("theme"); + } + + get pluginsFolder() { + return ( + this._pluginsFolder || + (this._pluginsFolder = fs.realpathSync( + path.resolve(bdConfig.dataPath + "plugins/") + )) + ); + } + get themesFolder() { + return ( + this._themesFolder || + (this._themesFolder = fs.realpathSync( + path.resolve(bdConfig.dataPath + "themes/") + )) + ); + } + + loadAddonCertifierCache() { + if ( + typeof dataStore.getSettingGroup("PluginCertifierHashes") !== "string" + ) { + dataStore.setSettingGroup( + "PluginCertifierHashes", + encryptSettingsCache("{}") + ); + } else { + try { + addonCache = JSON.parse( + decryptSettingsCache( + dataStore.getSettingGroup("PluginCertifierHashes") + ) + ); + } catch (e) { + dataStore.setSettingGroup( + "PluginCertifierHashes", + encryptSettingsCache("{}") + ); + addonCache = {}; + } } + Object.keys(addonCache).forEach((key) => { + let value = addonCache[key]; + if (!value || typeof value !== "object" || Array.isArray(value)) + return delete addonCache[key]; - patchExtensions(){ - if(hasPatched)return - hasPatched = true - Module._extensions[".js"] = this.getContentRequire("plugin"); - Module._extensions[".css"] = this.getContentRequire("theme"); - } - - get pluginsFolder() {return this._pluginsFolder || (this._pluginsFolder = fs.realpathSync(path.resolve(bdConfig.dataPath + "plugins/")));} - get themesFolder() {return this._themesFolder || (this._themesFolder = fs.realpathSync(path.resolve(bdConfig.dataPath + "themes/")));} - - loadAddonCertifierCache(){ - if(typeof dataStore.getSettingGroup("PluginCertifierHashes") !== "string"){ - dataStore.setSettingGroup("PluginCertifierHashes", encryptSettingsCache("{}")) - }else{ - try{ - addonCache = JSON.parse(decryptSettingsCache(dataStore.getSettingGroup("PluginCertifierHashes"))) - }catch(e){ - dataStore.setSettingGroup("PluginCertifierHashes", encryptSettingsCache("{}")) - addonCache = {} - } + let props = [ + { + key: "timestamp", + type: "number", + }, + { + key: "result", + type: "object", + }, + { + key: "hash", + type: "string", + }, + ]; + for (let prop of props) { + if (!(prop.key in value) || typeof value[prop.key] !== prop.type) { + delete addonCache[key]; + return; } - Object.keys(addonCache) - .forEach(key => { - let value = addonCache[key] - if(!value || typeof value !== "object" || Array.isArray(value))return delete addonCache[key] + } + if (value.hash !== key) { + delete addonCache[key]; + return; + } + if (value.result.suspect) { + // refetch from remote to be sure you're up to date. + delete addonCache[key]; + return; + } + }); + this.saveAddonCache(); + } - let props = [{ - key: "timestamp", - type: "number" - }, { - key: "result", - type: "object" - }, { - key: "hash", - type: "string" - }] - for(let prop of props){ - if(!(prop.key in value) || typeof value[prop.key] !== prop.type){ - delete addonCache[key] - return - } - } - if(value.hash !== key){ - delete addonCache[key] - return - } - if(value.result.suspect){ // refetch from remote to be sure you're up to date. - delete addonCache[key] - return - } - }) - this.saveAddonCache() - } + saveAddonCache() { + dataStore.setSettingGroup( + "PluginCertifierHashes", + encryptSettingsCache(JSON.stringify(addonCache)) + ); + } - saveAddonCache(){ - dataStore.setSettingGroup("PluginCertifierHashes", encryptSettingsCache(JSON.stringify(addonCache))) - } + resetAddonCache() { + Object.keys(addonCache).forEach((key) => { + delete addonCache[key]; + }); + console.log(addonCache); + this.saveAddonCache(); + } - resetAddonCache(){ - Object.keys(addonCache).forEach(key => { - delete addonCache[key] - }) - console.log(addonCache) - this.saveAddonCache() - } - - watchContent(contentType) { - if (this.watchers[contentType]) return; - const isPlugin = contentType === "plugin"; - const baseFolder = isPlugin ? this.pluginsFolder : this.themesFolder; - const fileEnding = isPlugin ? ".plugin.js" : ".theme.css"; - this.watchers[contentType] = fs.watch(baseFolder, {persistent: false}, async (eventType, filename) => { - if (!eventType || !filename || !filename.endsWith(fileEnding)) return; - await new Promise(r => setTimeout(r, 50)); - try {fs.statSync(path.resolve(baseFolder, filename));} - catch (err) { - if (err.code !== "ENOENT") return; - delete this.timeCache[filename]; - if (isPlugin) return pluginModule.unloadPlugin(filename); - return themeModule.unloadTheme(filename); - } - if (!fs.statSync(path.resolve(baseFolder, filename)).isFile()) return; - const stats = fs.statSync(path.resolve(baseFolder, filename)); - if (!stats || !stats.mtime || !stats.mtime.getTime()) return; - if (typeof(stats.mtime.getTime()) !== "number") return; - if (this.timeCache[filename] == stats.mtime.getTime()) return; - this.timeCache[filename] = stats.mtime.getTime(); - if (eventType == "rename") { - if (isPlugin) await pluginModule.loadPlugin(filename); - else await themeModule.loadTheme(filename); - } - if (eventType == "change") { - if (isPlugin) await pluginModule.reloadPlugin(filename); - else await themeModule.reloadTheme(filename); - } - }); - } - - unwatchContent(contentType) { - if (!this.watchers[contentType]) return; - this.watchers[contentType].close(); - delete this.watchers[contentType]; - } - - extractMeta(content) { - const firstLine = content.split("\n")[0]; - const hasOldMeta = firstLine.includes("//META"); - if (hasOldMeta) return this.parseOldMeta(content); - const hasNewMeta = firstLine.includes("/**"); - if (hasNewMeta) return this.parseNewMeta(content); - throw new MetaError("META was not found."); - } - - parseOldMeta(content) { - const meta = content.split("\n")[0]; - const rawMeta = meta.substring(meta.lastIndexOf("//META") + 6, meta.lastIndexOf("*//")); - if (meta.indexOf("META") < 0) throw new MetaError("META was not found."); - const parsed = Utils.testJSON(rawMeta); - if (!parsed) throw new MetaError("META could not be parsed."); - if (!parsed.name) throw new MetaError("META missing name data."); - parsed.format = "json"; - return parsed; - } - - parseNewMeta(content) { - const block = content.split("/**", 2)[1].split("*/", 1)[0]; - const out = {}; - let field = ""; - let accum = ""; - for (const line of block.split(splitRegex)) { - if (line.length === 0) continue; - if (line.charAt(0) === "@" && line.charAt(1) !== " ") { - out[field] = accum; - const l = line.indexOf(" "); - field = line.substr(1, l - 1); - accum = line.substr(l + 1); - } - else { - accum += " " + line.replace("\\n", "\n").replace(escapedAtRegex, "@"); - } - } - out[field] = accum.trim(); - delete out[""]; - out.format = "jsdoc"; - return out; - } - - getContentRequire(type) { - const isPlugin = type === "plugin"; - const self = this; - const originalRequire = isPlugin ? originalJSRequire : originalCSSRequire; - return function(module, filename) { - const baseFolder = isPlugin ? self.pluginsFolder : self.themesFolder; - const possiblePath = path.resolve(baseFolder, path.basename(filename)); - if (!fs.existsSync(possiblePath) || filename !== fs.realpathSync(possiblePath)) return Reflect.apply(originalRequire, this, arguments); - let content = fs.readFileSync(filename, "utf8"); - content = Utils.stripBOM(content); - - const stats = fs.statSync(filename); - const meta = self.extractMeta(content); - meta.filename = path.basename(filename); - meta.added = stats.atimeMs; - meta.modified = stats.mtimeMs; - meta.size = stats.size; - if (!isPlugin) { - meta.css = content; - if (meta.format == "json") meta.css = meta.css.split("\n").slice(1).join("\n"); - content = `module.exports = ${JSON.stringify(meta)};`; - } - if (isPlugin) { - module._compile(content, module.filename); - const didExport = !Utils.isEmpty(module.exports); - if (didExport) { - meta.type = module.exports; - module.exports = meta; - content = ""; - } - else { - Utils.warn("Module Not Exported", `${meta.name}, please start setting module.exports`); - content += `\nmodule.exports = ${JSON.stringify(meta)};\nmodule.exports.type = ${meta.exports || meta.name};`; - } - } - module._compile(content, filename); - }; - } - - makePlaceholderPlugin(data) { - return {plugin: { - start: () => {}, - getName: () => {return data.name || data.filename;}, - getAuthor: () => {return "???";}, - getDescription: () => {return data.message ? data.message : "This plugin was unable to be loaded. Check the author's page for updates.";}, - getVersion: () => {return "???";} - }, - name: data.name || data.filename, - filename: data.filename, - source: data.source ? data.source : "", - website: data.website ? data.website : "" - }; - } - - async loadContent(filename, type) { - if (typeof(filename) === "undefined" || typeof(type) === "undefined") return; - const isPlugin = type === "plugin"; - const baseFolder = isPlugin ? this.pluginsFolder : this.themesFolder; - - if(settingsCookie["fork-ps-6"]){ - let result = await new Promise(resolve => { - processFile(path.resolve(baseFolder, filename), (result) => { - console.log(result) - resolve(result) - }, (hash) => { - resolve({ - suspect: false, - hash: hash, - filename: filename, - name: filename - }) - }, true) - }) - if(result){ - addonCache[result.hash] = { - timestamp: Date.now(), - hash: result.hash, - result: result - } - this.saveAddonCache() - if(result.suspect){ - return { - name: filename, - file: filename, - message: "This plugin might be dangerous ("+result.harm+").", - error: new Error("This plugin might be dangerous ("+result.harm+").") - } - } - } - } - - try {__non_webpack_require__(path.resolve(baseFolder, filename));} - catch (error) {return {name: filename, file: filename, message: "Could not be compiled.", error: {message: error.message, stack: error.stack}};} - const content = __non_webpack_require__(path.resolve(baseFolder, filename)); - if(!content.name)return {name: filename, file: filename, message: "Cannot escape the ID.", error: new Error("Cannot read property 'replace' of undefined")} - content.id = Utils.escapeID(content.name); - //if(!id)return {name: filename, file: filename, message: "Invalid ID", error: new Error("Please fix the name of "+filename+". BetterDiscord can't escape an ID.")} - if (isPlugin) { - if (!content.type) return; - try { - content.plugin = new content.type(); - delete bdplugins[content.plugin.getName()]; - bdplugins[content.plugin.getName()] = content; - } - catch (error) {return {name: filename, file: filename, message: "Could not be constructed.", error: {message: error.message, stack: error.stack}};} - } - else { - delete bdthemes[content.name]; - bdthemes[content.name] = content; - } - } - - unloadContent(filename, type) { - if (typeof(filename) === "undefined" || typeof(type) === "undefined") return; - const isPlugin = type === "plugin"; - const baseFolder = isPlugin ? this.pluginsFolder : this.themesFolder; + watchContent(contentType) { + if (this.watchers[contentType]) return; + const isPlugin = contentType === "plugin"; + const baseFolder = isPlugin ? this.pluginsFolder : this.themesFolder; + const fileEnding = isPlugin ? ".plugin.js" : ".theme.css"; + this.watchers[contentType] = fs.watch( + baseFolder, + { persistent: false }, + async (eventType, filename) => { + if (!eventType || !filename || !filename.endsWith(fileEnding)) return; + await new Promise((r) => setTimeout(r, 50)); try { - delete __non_webpack_require__.cache[__non_webpack_require__.resolve(path.resolve(baseFolder, filename))]; + fs.statSync(path.resolve(baseFolder, filename)); + } catch (err) { + if (err.code !== "ENOENT") return; + delete this.timeCache[filename]; + if (isPlugin) return pluginModule.unloadPlugin(filename); + return themeModule.unloadTheme(filename); } - catch (err) {return {name: filename, file: filename, message: "Could not be unloaded.", error: {message: err.message, stack: err.stack}};} - } - - isLoaded(filename, type) { - const isPlugin = type === "plugin"; - const baseFolder = isPlugin ? this.pluginsFolder : this.themesFolder; - try {__non_webpack_require__.cache[__non_webpack_require__.resolve(path.resolve(baseFolder, filename))];} - catch (err) {return false;} - return true; - } - - async reloadContent(filename, type) { - const cantUnload = this.unloadContent(filename, type); - if (cantUnload) return cantUnload; - return await this.loadContent(filename, type); - } - - loadNewContent(type) { - const isPlugin = type === "plugin"; - const fileEnding = isPlugin ? ".plugin.js" : ".theme.css"; - const basedir = isPlugin ? this.pluginsFolder : this.themesFolder; - const files = fs.readdirSync(basedir); - const contentList = Object.values(isPlugin ? bdplugins : bdthemes); - const removed = contentList.filter(t => !files.includes(t.filename)).map(c => isPlugin ? c.plugin.getName() : c.name); - const added = files.filter(f => !contentList.find(t => t.filename == f) && f.endsWith(fileEnding) && fs.statSync(path.resolve(basedir, f)).isFile()); - return {added, removed}; - } - - async loadAllContent(type) { - this.patchExtensions() - const isPlugin = type === "plugin"; - const fileEnding = isPlugin ? ".plugin.js" : ".theme.css"; - const basedir = isPlugin ? this.pluginsFolder : this.themesFolder; - const errors = []; - const files = fs.readdirSync(basedir); - - for (const filename of files) { - if (!fs.statSync(path.resolve(basedir, filename)).isFile() || !filename.endsWith(fileEnding)) continue; - const error = await this.loadContent(filename, type); - if (error) errors.push(error); + if (!fs.statSync(path.resolve(baseFolder, filename)).isFile()) return; + const stats = fs.statSync(path.resolve(baseFolder, filename)); + if (!stats || !stats.mtime || !stats.mtime.getTime()) return; + if (typeof stats.mtime.getTime() !== "number") return; + if (this.timeCache[filename] == stats.mtime.getTime()) return; + this.timeCache[filename] = stats.mtime.getTime(); + if (eventType == "rename") { + if (isPlugin) await pluginModule.loadPlugin(filename); + else await themeModule.loadTheme(filename); } + if (eventType == "change") { + if (isPlugin) await pluginModule.reloadPlugin(filename); + else await themeModule.reloadTheme(filename); + } + } + ); + } - return errors; + unwatchContent(contentType) { + if (!this.watchers[contentType]) return; + this.watchers[contentType].close(); + delete this.watchers[contentType]; + } + + extractMeta(content) { + const firstLine = content.split("\n")[0]; + const hasOldMeta = firstLine.includes("//META"); + if (hasOldMeta) return this.parseOldMeta(content); + const hasNewMeta = firstLine.includes("/**"); + if (hasNewMeta) return this.parseNewMeta(content); + throw new MetaError("META was not found."); + } + + parseOldMeta(content) { + const meta = content.split("\n")[0]; + const rawMeta = meta.substring( + meta.lastIndexOf("//META") + 6, + meta.lastIndexOf("*//") + ); + if (meta.indexOf("META") < 0) throw new MetaError("META was not found."); + const parsed = Utils.testJSON(rawMeta); + if (!parsed) throw new MetaError("META could not be parsed."); + if (!parsed.name) throw new MetaError("META missing name data."); + parsed.format = "json"; + return parsed; + } + + parseNewMeta(content) { + const block = content.split("/**", 2)[1].split("*/", 1)[0]; + const out = {}; + let field = ""; + let accum = ""; + for (const line of block.split(splitRegex)) { + if (line.length === 0) continue; + if (line.charAt(0) === "@" && line.charAt(1) !== " ") { + out[field] = accum; + const l = line.indexOf(" "); + field = line.substr(1, l - 1); + accum = line.substr(l + 1); + } else { + accum += " " + line.replace("\\n", "\n").replace(escapedAtRegex, "@"); + } + } + out[field] = accum.trim(); + delete out[""]; + out.format = "jsdoc"; + return out; + } + + getContentRequire(type) { + const isPlugin = type === "plugin"; + const self = this; + const originalRequire = isPlugin ? originalJSRequire : originalCSSRequire; + return function (module, filename) { + const baseFolder = isPlugin ? self.pluginsFolder : self.themesFolder; + const possiblePath = path.resolve(baseFolder, path.basename(filename)); + if ( + !fs.existsSync(possiblePath) || + filename !== fs.realpathSync(possiblePath) + ) + return Reflect.apply(originalRequire, this, arguments); + let content = fs.readFileSync(filename, "utf8"); + content = Utils.stripBOM(content); + + const stats = fs.statSync(filename); + const meta = self.extractMeta(content); + meta.filename = path.basename(filename); + meta.added = stats.atimeMs; + meta.modified = stats.mtimeMs; + meta.size = stats.size; + if (!isPlugin) { + meta.css = content; + if (meta.format == "json") + meta.css = meta.css.split("\n").slice(1).join("\n"); + content = `module.exports = ${JSON.stringify(meta)};`; + } + if (isPlugin) { + module._compile(content, module.filename); + const didExport = !Utils.isEmpty(module.exports); + if (didExport) { + meta.type = module.exports; + module.exports = meta; + content = ""; + } else { + Utils.warn( + "Module Not Exported", + `${meta.name}, please start setting module.exports` + ); + content += `\nmodule.exports = ${JSON.stringify( + meta + )};\nmodule.exports.type = ${meta.exports || meta.name};`; + } + } + module._compile(content, filename); + }; + } + + makePlaceholderPlugin(data) { + return { + plugin: { + start: () => {}, + getName: () => { + return data.name || data.filename; + }, + getAuthor: () => { + return "???"; + }, + getDescription: () => { + return data.message + ? data.message + : "This plugin was unable to be loaded. Check the author's page for updates."; + }, + getVersion: () => { + return "???"; + }, + }, + name: data.name || data.filename, + filename: data.filename, + source: data.source ? data.source : "", + website: data.website ? data.website : "", + }; + } + + async loadContent(filename, type) { + if (typeof filename === "undefined" || typeof type === "undefined") return; + const isPlugin = type === "plugin"; + const baseFolder = isPlugin ? this.pluginsFolder : this.themesFolder; + + if (settingsCookie["fork-ps-6"]) { + let result = await new Promise((resolve) => { + processFile( + path.resolve(baseFolder, filename), + (result) => { + console.log(result); + resolve(result); + }, + (hash) => { + resolve({ + suspect: false, + hash: hash, + filename: filename, + name: filename, + }); + }, + true + ); + }); + if (result) { + addonCache[result.hash] = { + timestamp: Date.now(), + hash: result.hash, + result: result, + }; + this.saveAddonCache(); + if (result.suspect) { + return { + name: filename, + file: filename, + message: "This plugin might be dangerous (" + result.harm + ").", + error: new Error( + "This plugin might be dangerous (" + result.harm + ")." + ), + }; + } + } } - loadPlugins() {return this.loadAllContent("plugin");} - loadThemes() {return this.loadAllContent("theme");} -}; + try { + __non_webpack_require__(path.resolve(baseFolder, filename)); + } catch (error) { + return { + name: filename, + file: filename, + message: "Could not be compiled.", + error: { message: error.message, stack: error.stack }, + }; + } + const content = __non_webpack_require__(path.resolve(baseFolder, filename)); + if (!content.name) + return { + name: filename, + file: filename, + message: "Cannot escape the ID.", + error: new Error("Cannot read property 'replace' of undefined"), + }; + content.id = Utils.escapeID(content.name); + //if(!id)return {name: filename, file: filename, message: "Invalid ID", error: new Error("Please fix the name of "+filename+". BetterDiscord can't escape an ID.")} + if (isPlugin) { + if (!content.type) return; + try { + content.plugin = new content.type(); + delete bdplugins[content.plugin.getName()]; + bdplugins[content.plugin.getName()] = content; + } catch (error) { + return { + name: filename, + file: filename, + message: "Could not be constructed.", + error: { message: error.message, stack: error.stack }, + }; + } + } else { + delete bdthemes[content.name]; + bdthemes[content.name] = content; + } + } + + unloadContent(filename, type) { + if (typeof filename === "undefined" || typeof type === "undefined") return; + const isPlugin = type === "plugin"; + const baseFolder = isPlugin ? this.pluginsFolder : this.themesFolder; + try { + delete __non_webpack_require__.cache[ + __non_webpack_require__.resolve(path.resolve(baseFolder, filename)) + ]; + } catch (err) { + return { + name: filename, + file: filename, + message: "Could not be unloaded.", + error: { message: err.message, stack: err.stack }, + }; + } + } + + isLoaded(filename, type) { + const isPlugin = type === "plugin"; + const baseFolder = isPlugin ? this.pluginsFolder : this.themesFolder; + try { + __non_webpack_require__.cache[ + __non_webpack_require__.resolve(path.resolve(baseFolder, filename)) + ]; + } catch (err) { + return false; + } + return true; + } + + async reloadContent(filename, type) { + const cantUnload = this.unloadContent(filename, type); + if (cantUnload) return cantUnload; + return await this.loadContent(filename, type); + } + + loadNewContent(type) { + const isPlugin = type === "plugin"; + const fileEnding = isPlugin ? ".plugin.js" : ".theme.css"; + const basedir = isPlugin ? this.pluginsFolder : this.themesFolder; + const files = fs.readdirSync(basedir); + const contentList = Object.values(isPlugin ? bdplugins : bdthemes); + const removed = contentList + .filter((t) => !files.includes(t.filename)) + .map((c) => (isPlugin ? c.plugin.getName() : c.name)); + const added = files.filter( + (f) => + !contentList.find((t) => t.filename == f) && + f.endsWith(fileEnding) && + fs.statSync(path.resolve(basedir, f)).isFile() + ); + return { added, removed }; + } + + async loadAllContent(type) { + this.patchExtensions(); + const isPlugin = type === "plugin"; + const fileEnding = isPlugin ? ".plugin.js" : ".theme.css"; + const basedir = isPlugin ? this.pluginsFolder : this.themesFolder; + const errors = []; + const files = fs.readdirSync(basedir); + + for (const filename of files) { + if ( + !fs.statSync(path.resolve(basedir, filename)).isFile() || + !filename.endsWith(fileEnding) + ) + continue; + const error = await this.loadContent(filename, type); + if (error) errors.push(error); + } + + return errors; + } + + loadPlugins() { + return this.loadAllContent("plugin"); + } + loadThemes() { + return this.loadAllContent("theme"); + } +})(); /** * Don't expose contentManager - could be dangerous for now - */ \ No newline at end of file + */ diff --git a/BetterDiscordApp/src/modules/publicServers.js b/BetterDiscordApp/src/modules/publicServers.js index eb72d96..1cfed5d 100644 --- a/BetterDiscordApp/src/modules/publicServers.js +++ b/BetterDiscordApp/src/modules/publicServers.js @@ -1,58 +1,87 @@ -import {settingsCookie} from "../0globals"; +import { settingsCookie } from "../0globals"; import BDV2 from "./v2"; import webpackModules from "./webpackModules"; import Utils from "./utils"; import DOM from "./domtools"; import V2C_PublicServers from "../ui/publicservers/publicServers"; -import Layers from "./Layers"; +import Layer from "../ui/publicservers/layer"; -export default new class V2_PublicServers { +export default new (class V2_PublicServers { + constructor() { + this._appendButton = this._appendButton.bind(this); + } - constructor() { - this._appendButton = this._appendButton.bind(this); - window.Lightcord.BetterDiscord.V2_PublicServers = this + get component() { + return BDV2.react.createElement( + Layer, + { rootId: "pubslayerroot", id: "pubslayer" }, + BDV2.react.createElement(V2C_PublicServers, { rootId: "pubslayerroot" }) + ); + } + + get root() { + const _root = document.getElementById("pubslayerroot"); + if (!_root) { + if (!this.injectRoot()) return null; + return this.root; } + return _root; + } - render() { - Layers.createLayer((close) => { - return BDV2.react.createElement(V2C_PublicServers, {rootId: "pubslayerroot", close}) - }) + injectRoot() { + const layers = DOM.query(".layers, .layers-3iHuyZ"); + if (!layers) return false; + layers.append(DOM.createElement("