New util functions and pluginmanager

This commit is contained in:
Jiiks 2018-01-30 17:59:27 +02:00
parent 2a8c6189e4
commit 5ad5eeff64
13 changed files with 343 additions and 33 deletions

View File

@ -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 };
}

View File

@ -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);
}
}

View File

@ -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) {

View File

@ -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';

View File

@ -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
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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: {

View File

@ -48,7 +48,6 @@
},
created() {
Events.on('ready', e => this.loaded = true);
this.loaded = true;
window.addEventListener('keyup', this.keyupListener);
},
destroyed() {

View File

@ -0,0 +1,6 @@
<template>
<div>PluginCard</div>
</template>
<script>
export default {}
</script>

View File

@ -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.
*/
<template>
<SettingsWrapper headertext="Plugins">
<div class="bd-flex bd-flex-col bd-pluginsView">
<div class="bd-flex bd-tabheader">
<div class="bd-flex-grow bd-button" :class="{'bd-active': local}" @click="showLocal">
<h3>Local</h3>
<div class="bd-material-button" @click="refreshLocal">
<refresh />
</div>
</div>
<div class="bd-flex-grow bd-button" :class="{'bd-active': !local}" @click="showOnline">
<h3>Online</h3>
<div class="bd-material-button">
<refresh />
</div>
</div>
</div>
<div v-if="local" class="bd-flex bd-flex-grow bd-flex-col bd-plugins-container bd-local-plugins">
<PluginCard v-for="plugin in localPlugins" :plugin="plugin" :key="plugin.id" :togglePlugin="togglePlugin" :reloadPlugin="reloadPlugin" :showSettings="showSettings" />
</div>
<div v-if="!local" class="bd-spinner-container">
<div class="bd-spinner-2"></div>
</div>
</div>
<div v-if="settingsOpen !== null" class="bd-backdrop" @click="settingsOpen = null"></div>
<div v-if="settingsOpen !== null" class="bd-modal">
<PluginSettingsModal :plugin="settingsOpen" />
</div>
</SettingsWrapper>
</template>
<script>
// Imports
import { PluginManager } from 'modules';
import { SettingsWrapper } from './';
import PluginCard from './PluginCard.vue';
import Refresh from 'vue-material-design-icons/refresh.vue';
export default {
data() {
return {
local: true,
pluginManager: PluginManager,
settingsOpen: null
}
},
components: {
SettingsWrapper, PluginCard, Refresh
},
computed: {
localPlugins() {
return this.pluginManager.localPlugins;
}
},
methods: {
showLocal() { },
showOnline() { },
refreshLocal() { },
togglePlugin() { },
reloadPlugin() { },
showSettings() { }
}
}
</script>

View File

@ -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';
export { default as CssEditorView } from './CssEditor.vue';
export { default as PluginsView } from './PluginsView.vue';

View File

@ -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 = [];