From 5ad5eeff64c83b81881daedd69e013b1c0d035e2 Mon Sep 17 00:00:00 2001 From: Jiiks Date: Tue, 30 Jan 2018 17:59:27 +0200 Subject: [PATCH] New util functions and pluginmanager --- client/src/index.js | 13 ++- client/src/modules/contentmanager.js | 105 ++++++++++++++++++ client/src/modules/globals.js | 8 +- client/src/modules/modules.js | 1 + client/src/modules/plugin.js | 58 ++++++++++ client/src/modules/pluginmanager.js | 43 ++++--- client/src/modules/thememanager.js | 27 +++++ client/src/ui/components/BdSettings.vue | 4 +- .../src/ui/components/BdSettingsWrapper.vue | 1 - client/src/ui/components/bd/PluginCard.vue | 6 + client/src/ui/components/bd/PluginsView.vue | 75 +++++++++++++ client/src/ui/components/bd/index.js | 3 +- common/modules/utils.js | 32 +++++- 13 files changed, 343 insertions(+), 33 deletions(-) create mode 100644 client/src/modules/contentmanager.js create mode 100644 client/src/modules/plugin.js create mode 100644 client/src/modules/thememanager.js create mode 100644 client/src/ui/components/bd/PluginCard.vue create mode 100644 client/src/ui/components/bd/PluginsView.vue diff --git a/client/src/index.js b/client/src/index.js index 6ce3a4e7..320fcf6b 100644 --- a/client/src/index.js +++ b/client/src/index.js @@ -10,17 +10,25 @@ import { DOM, BdUI } from 'ui'; import BdCss from './styles/index.scss'; -import { Events, CssEditor, Globals, PluginManager } from 'modules'; +import { Events, CssEditor, Globals, PluginManager, ThemeManager } from 'modules'; class BetterDiscord { constructor() { - window.pm = PluginManager; DOM.injectStyle(BdCss, 'bdmain'); Events.on('global-ready', this.globalReady.bind(this)); } + async init() { + await PluginManager.loadAllPlugins(); + await ThemeManager.loadAllThemes(); + Events.emit('ready'); + } + globalReady() { this.vueInstance = BdUI.injectUi(); + (async () => { + this.init(); + })(); } } @@ -28,5 +36,4 @@ if (window.BetterDiscord) { Logger.log('main', 'Attempting to inject again?'); } else { let bdInstance = new BetterDiscord(); - // window.BetterDiscord = { 'vendor': Vendor }; } diff --git a/client/src/modules/contentmanager.js b/client/src/modules/contentmanager.js new file mode 100644 index 00000000..de25e9d0 --- /dev/null +++ b/client/src/modules/contentmanager.js @@ -0,0 +1,105 @@ +/** + * BetterDiscord Content Manager Module + * Copyright (c) 2015-present Jiiks/JsSucks - https://github.com/Jiiks / https://github.com/JsSucks + * All rights reserved. + * https://betterdiscord.net + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. +*/ + +import Globals from './globals'; +import { FileUtils, ClientLogger as Logger } from 'common'; +import path from 'path'; + +export default class { + + static get localContent() { + return this._localContent ? this._localContent : (this._localContent = []); + } + + static get contentPath() { + return this._contentPath ? this._contentPath : (this._contentPath = Globals.getObject('paths').find(path => path.id === this.pathId).path); + } + + static async loadAllContent() { + try { + + await FileUtils.ensureDirectory(this.contentPath); + const directories = await FileUtils.listDirectory(this.contentPath); + + for (let dir of directories) { + try { + await this.preloadContent(dir); + } catch (err) { + //We don't want every plugin/theme to fail loading when one does + Logger.err(this.moduleName, err); + } + } + + return this.localContent; + } catch (err) { + throw err; + } + } + + static async preloadContent(dirName, reload = false, index) { + try { + const contentPath = path.join(this.contentPath, dirName); + + await FileUtils.directoryExists(contentPath); + + if (!reload) { + const loaded = this.localContent.find(content => content.contentPath === contentPath); + if (loaded) { + throw { 'message': `Attempted to load already loaded user content: ${path}` }; + } + } + + const readConfig = await this.readConfig(contentPath); + const mainPath = path.join(contentPath, readConfig.main); + + const userConfig = { + enabled: false, + config: readConfig.defaultConfig + }; + + try { + const readUserConfig = await this.readUserConfig(contentPath); + userConfig.config = userConfig.defaultConfig.map(config => { + const userSet = readUserConfig.config.find(c => c.id === config.id); + return userSet || config; + }); + } catch (err) {/*We don't care if this fails it either means that user config doesn't exist or there's something wrong with it so we revert to default config*/ } + + const configs = { + defaultConfig: readConfig.defaultConfig, + userConfig + } + + const paths = { + contentPath, + dirName, + mainPath + } + + const content = await this.loadContent(paths, configs, readConfig.info, readConfig.main); + this.localContent.push(content); + return content; + + } catch (err) { + throw err; + } + } + + static async readConfig(configPath) { + configPath = path.resolve(configPath, 'config.json'); + return FileUtils.readJsonFromFile(configPath); + } + + static async readUserConfig(configPath) { + configPath = path.resolve(configPath, 'user.config.json'); + return FileUtils.readJsonFromFile(configPath); + } + +} \ No newline at end of file diff --git a/client/src/modules/globals.js b/client/src/modules/globals.js index 930d0776..824baef3 100644 --- a/client/src/modules/globals.js +++ b/client/src/modules/globals.js @@ -30,6 +30,13 @@ export default new class extends Module { const config = await ClientIPC.send('getConfig'); this.setState(config); + // This is for Discord to stop error reporting :3 + window.BetterDiscord = { + 'version': config.version, + 'v': config.version + }; + window.jQuery = {}; + Events.emit('global-ready'); })(); @@ -40,7 +47,6 @@ export default new class extends Module { } Events.emit('socket-created', this.state.wsHook); } - } setWS(wSocket) { diff --git a/client/src/modules/modules.js b/client/src/modules/modules.js index 994d8da7..1e5537cb 100644 --- a/client/src/modules/modules.js +++ b/client/src/modules/modules.js @@ -2,5 +2,6 @@ export { default as Events } from './events'; export { default as Settings } from './settings'; export { default as CssEditor } from './csseditor'; export { default as PluginManager } from './pluginmanager'; +export { default as ThemeManager } from './thememanager'; export { default as Globals } from './globals'; export { default as Vendor } from './vendor'; \ No newline at end of file diff --git a/client/src/modules/plugin.js b/client/src/modules/plugin.js new file mode 100644 index 00000000..bb514e9b --- /dev/null +++ b/client/src/modules/plugin.js @@ -0,0 +1,58 @@ +/** + * BetterDiscord Plugin Base + * Copyright (c) 2015-present Jiiks/JsSucks - https://github.com/Jiiks / https://github.com/JsSucks + * All rights reserved. + * https://betterdiscord.net + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. +*/ + +export default class { + + constructor(pluginInternals) { + this.__pluginInternals = pluginInternals; + this.hasSettings = this.pluginConfig && this.pluginConfig.length > 0; + this.start = this.start.bind(this); + this.stop = this.stop.bind(this); + } + + get configs() { return this.__pluginInternals.configs } + get info() { return this.__pluginInternals.info } + get paths() { return this.__pluginInternals.paths } + get main() { return this.__pluginInternals.main } + get defaultConfig() { return this.configs.defaultConfig } + get userConfig() { return this.configs.userConfig } + get name() { return this.info.name } + get authors() { return this.info.authors } + get version() { return this.info.version } + get pluginPath() { return this.paths.pluginPath } + get dirName() { return this.paths.dirName } + get enabled() { return this.userConfig.enabled } + get pluginConfig() { return this.userConfig.pluginConfig } + + start() { + if (this.onStart) { + const started = this.onStart(); + if (started) { + return this.userConfig.enabled = true; + } + return false; + } + return this.userConfig.enabled = true; //Assume plugin started since it doesn't have onStart + } + + stop() { + if (this.onStop) { + const stopped = this.onStop(); + if (stopped) { + this.userConfig.enabled = false; + return true; + } + return false; + } + this.userConfig.enabled = false; + return true; //Assume plugin stopped since it doesn't have onStop + } + +} diff --git a/client/src/modules/pluginmanager.js b/client/src/modules/pluginmanager.js index aa61171d..9136f10b 100644 --- a/client/src/modules/pluginmanager.js +++ b/client/src/modules/pluginmanager.js @@ -8,39 +8,34 @@ * LICENSE file in the root directory of this source tree. */ -import Globals from './globals'; -import { FileUtils, ClientLogger as Logger } from 'common'; +import ContentManager from './contentmanager'; +import Plugin from './plugin'; -const localPlugins = []; - -export default class { +export default class extends ContentManager { static get localPlugins() { - return localPlugins; + return this.localContent; } - static get pluginsPath() { - Logger.log('PluginManager', 'hi!'); - return Globals.getObject('paths').find(path => path.id === 'plugins').path; + static get moduleName() { + return 'PluginManager'; } - static async loadAllPlugins() { - try { - const directories = await FileUtils.readDir(this.pluginsPath); + static get pathId() { + return 'plugins'; + } - for (let dir of directories) { - try { - await this.loadPlugin(dir); - } catch (err) { - //We don't want every plugin to fail loading when one does - Logger.err('PluginManager', err); - } - } + static get loadAllPlugins() { + return this.loadAllContent; + } - return this.plugins; - } catch (err) { - throw err; - } + static get loadContent() { return this.loadPlugin } + static async loadPlugin(paths, configs, info, main) { + const plugin = window.require(paths.mainPath)(Plugin, {}, {}); + const instance = new plugin({ configs, info, main, paths: { pluginPath: paths.contentPath, dirName: paths.dirName } }); + + if (instance.enabled) instance.start(); + return instance; } } diff --git a/client/src/modules/thememanager.js b/client/src/modules/thememanager.js new file mode 100644 index 00000000..1400db56 --- /dev/null +++ b/client/src/modules/thememanager.js @@ -0,0 +1,27 @@ +/** + * BetterDiscord Theme Manager Module + * Copyright (c) 2015-present Jiiks/JsSucks - https://github.com/Jiiks / https://github.com/JsSucks + * All rights reserved. + * https://betterdiscord.net + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. +*/ + +import ContentManager from './contentmanager'; + +export default class extends ContentManager { + + static get localThemes() { + return this.localContent; + } + + static get pathId() { + return 'themes'; + } + + static get loadAllThemes() { + return this.loadAllContent; + } + +} diff --git a/client/src/ui/components/BdSettings.vue b/client/src/ui/components/BdSettings.vue index 85aa42e2..126e6a39 100644 --- a/client/src/ui/components/BdSettings.vue +++ b/client/src/ui/components/BdSettings.vue @@ -45,7 +45,7 @@ // Imports import { Settings } from 'modules'; import { SidebarView, Sidebar, SidebarItem, ContentColumn } from './sidebar'; - import { CoreSettings, UISettings, EmoteSettings, CssEditorView } from './bd'; + import { CoreSettings, UISettings, EmoteSettings, CssEditorView, PluginsView } from './bd'; import { SvgX } from './common'; // Constants @@ -79,7 +79,7 @@ }, components: { SidebarView, Sidebar, SidebarItem, ContentColumn, - CoreSettings, UISettings, EmoteSettings, CssEditorView, + CoreSettings, UISettings, EmoteSettings, CssEditorView, PluginsView, SvgX }, methods: { diff --git a/client/src/ui/components/BdSettingsWrapper.vue b/client/src/ui/components/BdSettingsWrapper.vue index 3d9c3f08..e7c56030 100644 --- a/client/src/ui/components/BdSettingsWrapper.vue +++ b/client/src/ui/components/BdSettingsWrapper.vue @@ -48,7 +48,6 @@ }, created() { Events.on('ready', e => this.loaded = true); - this.loaded = true; window.addEventListener('keyup', this.keyupListener); }, destroyed() { diff --git a/client/src/ui/components/bd/PluginCard.vue b/client/src/ui/components/bd/PluginCard.vue new file mode 100644 index 00000000..d54a7567 --- /dev/null +++ b/client/src/ui/components/bd/PluginCard.vue @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/client/src/ui/components/bd/PluginsView.vue b/client/src/ui/components/bd/PluginsView.vue new file mode 100644 index 00000000..b78ebc7c --- /dev/null +++ b/client/src/ui/components/bd/PluginsView.vue @@ -0,0 +1,75 @@ +/** + * BetterDiscord Plugins View Component + * Copyright (c) 2015-present Jiiks/JsSucks - https://github.com/Jiiks / https://github.com/JsSucks + * All rights reserved. + * https://betterdiscord.net + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. +*/ + + + + diff --git a/client/src/ui/components/bd/index.js b/client/src/ui/components/bd/index.js index f9f2f3af..dfa19573 100644 --- a/client/src/ui/components/bd/index.js +++ b/client/src/ui/components/bd/index.js @@ -2,4 +2,5 @@ export { default as SettingsWrapper } from './SettingsWrapper.vue'; export { default as CoreSettings } from './CoreSettings.vue'; export { default as UISettings } from './UISettings.vue'; export { default as EmoteSettings } from './EmoteSettings.vue'; -export { default as CssEditorView } from './CssEditor.vue'; \ No newline at end of file +export { default as CssEditorView } from './CssEditor.vue'; +export { default as PluginsView } from './PluginsView.vue'; \ No newline at end of file diff --git a/common/modules/utils.js b/common/modules/utils.js index 5a980d30..915e44c4 100644 --- a/common/modules/utils.js +++ b/common/modules/utils.js @@ -72,6 +72,32 @@ export class FileUtils { }); } + static async createDirectory(path) { + return new Promise((resolve, reject) => { + fs.mkdir(path, err => { + if (err) { + if (err.code === 'EEXIST') return resolve(); + else return reject(err); + } + resolve(); + }); + }); + } + + static async ensureDirectory(path) { + try { + await this.directoryExists(path); + return true; + } catch (err) { + try { + await this.createDirectory(path); + return true; + } catch (err) { + throw err; + } + } + } + static async readFile(path) { try { await this.fileExists(path); @@ -120,7 +146,7 @@ export class FileUtils { return this.writeFile(path, JSON.stringify(json)); } - static async readDir(path) { + static async listDirectory(path) { try { await this.directoryExists(path); return new Promise((resolve, reject) => { @@ -133,6 +159,10 @@ export class FileUtils { throw err; } } + + static async readDir(path) { + return this.listDirectory(path); + } } const logs = [];