Merge pull request #184 from samuelthomas2774/refactor
Refactor and comment
This commit is contained in:
commit
a4ceb8bd2c
|
@ -2,6 +2,7 @@ root = true
|
|||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
indent_style = space
|
||||
|
|
|
@ -1 +1 @@
|
|||
*.sh text=auto
|
||||
*.sh text=auto
|
||||
|
|
|
@ -1,27 +1,11 @@
|
|||
.idea/*
|
||||
*.name
|
||||
devjs/.idea/devjs.iml
|
||||
*.bak
|
||||
*.bak.*
|
||||
*.xpi
|
||||
Firefox/data/js/jquery-2.1.4.min.js
|
||||
*.dev.*
|
||||
/nbproject/private/
|
||||
# Generated files
|
||||
node_modules
|
||||
.sass-cache
|
||||
/*.jiiks
|
||||
Installers/dotNet/bin/
|
||||
Installers/dotNet/packages/
|
||||
Installers/dotNet/dlls/
|
||||
v2/dist/vendor/
|
||||
v2/lib/static.js
|
||||
**/*.suo
|
||||
Installers/**/*/bin
|
||||
Installers/**/*/obj
|
||||
Installers/**/*/packages
|
||||
.vs
|
||||
dist/
|
||||
user.config.json
|
||||
dist
|
||||
etc
|
||||
release
|
||||
|
||||
tests/log.txt
|
||||
|
||||
# User data
|
||||
tests/data
|
||||
/tests/themes/SimplerFlat
|
||||
release
|
||||
user.config.json
|
||||
|
|
|
@ -3,4 +3,4 @@ node_js:
|
|||
- stable
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- master
|
||||
|
|
2
LICENSE
2
LICENSE
|
@ -6,4 +6,4 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# BetterDiscordApp [![Travis][build-badge]][build]
|
||||
|
||||
[build-badge]: https://img.shields.io/travis/JsSucks/BetterDiscordApp/master.svg
|
||||
[build]: https://travis-ci.org/JsSucks/BetterDiscordApp
|
||||
[build]: https://travis-ci.org/JsSucks/BetterDiscordApp
|
|
@ -5,21 +5,19 @@
|
|||
"version": "2.0.0b",
|
||||
"homepage": "https://betterdiscord.net",
|
||||
"license": "MIT",
|
||||
"main": "index.js",
|
||||
"main": "dist/betterdiscord.client.js",
|
||||
"contributors": [
|
||||
"Jiiks",
|
||||
"Pohky"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Jiiks/BetterDiscordApp.git"
|
||||
"url": "https://github.com/JsSucks/BetterDiscordApp.git"
|
||||
},
|
||||
"private": false,
|
||||
"devDependencies": {
|
||||
|
||||
},
|
||||
"scripts": {
|
||||
"build": "webpack --progress --colors",
|
||||
"watch": "webpack --progress --colors --watch"
|
||||
"watch": "webpack --progress --colors --watch",
|
||||
"release": "webpack --progress --colors --config=webpack.production.config.js"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,11 +9,10 @@
|
|||
data() {
|
||||
return {
|
||||
favourite: false
|
||||
}
|
||||
};
|
||||
},
|
||||
props: ['src', 'name'],
|
||||
methods: {
|
||||
},
|
||||
methods: {},
|
||||
beforeMount() {
|
||||
// Check favourite state
|
||||
}
|
||||
|
|
|
@ -7,26 +7,34 @@
|
|||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
import { FileUtils, ClientLogger as Logger } from 'common';
|
||||
|
||||
import { Events, Globals, WebpackModules, ReactComponents, MonkeyPatch } from 'modules';
|
||||
import { DOM, VueInjector, Reflection } from 'ui';
|
||||
import { FileUtils, ClientLogger as Logger } from 'common';
|
||||
import path from 'path';
|
||||
import EmoteComponent from './EmoteComponent.vue';
|
||||
|
||||
let emotes = null;
|
||||
const emotesEnabled = true;
|
||||
|
||||
export default class {
|
||||
|
||||
static get searchCache() {
|
||||
return this._searchCache || (this._searchCache = {});
|
||||
}
|
||||
|
||||
static get emoteDb() {
|
||||
return emotes;
|
||||
}
|
||||
|
||||
static get React() {
|
||||
return WebpackModules.getModuleByName('React');
|
||||
}
|
||||
|
||||
static get ReactDOM() {
|
||||
return WebpackModules.getModuleByName('ReactDOM');
|
||||
}
|
||||
|
||||
static processMarkup(markup) {
|
||||
if (!emotesEnabled) return markup; // TODO Get it from setttings
|
||||
const newMarkup = [];
|
||||
|
@ -92,13 +100,20 @@ export default class {
|
|||
}
|
||||
|
||||
static async observe() {
|
||||
const dataPath = Globals.getObject('paths').find(path => path.id === 'data').path;
|
||||
const dataPath = Globals.getPath('data');
|
||||
try {
|
||||
emotes = await FileUtils.readJsonFromFile(path.join(dataPath, 'emotes.json'));
|
||||
} catch (err) {
|
||||
Logger.err('EmoteModule', [`Failed to load emote data. Make sure you've downloaded the emote data and placed it in ${dataPath}:`, err]);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
emotes = await FileUtils.readJsonFromFile(dataPath + '/emotes.json');
|
||||
const Message = await ReactComponents.getComponent('Message');
|
||||
this.unpatchRender = MonkeyPatch('BD:EmoteModule', Message.component.prototype).after('render', (component, args, retVal) => {
|
||||
try {
|
||||
const markup = this.findByProp(retVal, 'className', 'markup'); // First child has all the actual text content, second is the edited timestamp
|
||||
// First child has all the actual text content, second is the edited timestamp
|
||||
const markup = this.findByProp(retVal, 'className', 'markup');
|
||||
if (!markup) return;
|
||||
markup.children[0] = this.processMarkup(markup.children[0]);
|
||||
} catch (err) {
|
||||
|
@ -131,12 +146,11 @@ export default class {
|
|||
}
|
||||
const { bdemoteName, bdemoteSrc } = root.dataset;
|
||||
if (!bdemoteName || !bdemoteSrc) return;
|
||||
VueInjector.inject(
|
||||
root,
|
||||
DOM.createElement('span'),
|
||||
{ EmoteComponent },
|
||||
`<EmoteComponent src="${bdemoteSrc}" name="${bdemoteName}"/>`
|
||||
);
|
||||
VueInjector.inject(root, {
|
||||
components: { EmoteComponent },
|
||||
data: { src: bdemoteSrc, name: bdemoteName },
|
||||
template: '<EmoteComponent :src="src" :name="name" />'
|
||||
}, DOM.createElement('span'));
|
||||
root.classList.add('bd-is-emote');
|
||||
}
|
||||
|
||||
|
@ -179,4 +193,5 @@ export default class {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
[
|
||||
{
|
||||
"__user": "Jiiks#5000",
|
||||
"id": "81388395867156480",
|
||||
"developer": true,
|
||||
"webdev": true,
|
||||
"contributor": true
|
||||
},
|
||||
{
|
||||
"__user": "Pohky#0156",
|
||||
"id": "98003542823944192",
|
||||
"developer": true,
|
||||
"webdev": false,
|
||||
"contributor": true
|
||||
},
|
||||
{
|
||||
"__user": "Hammock#3110",
|
||||
"id": "138850472541814784",
|
||||
"developer": false,
|
||||
"webdev": true,
|
||||
"contributor": true
|
||||
},
|
||||
{
|
||||
"__user": "Zerebos#7790",
|
||||
"id": "249746236008169473",
|
||||
"developer": true,
|
||||
"webdev": false,
|
||||
"contributor": true
|
||||
},
|
||||
{
|
||||
"__user": "Pierce#1337",
|
||||
"id": "125367412370440192",
|
||||
"developer": true,
|
||||
"webdev": false,
|
||||
"contributor": true
|
||||
},
|
||||
{
|
||||
"__user": "Samuel Elliott#2764",
|
||||
"id": "284056145272766465",
|
||||
"developer": true,
|
||||
"webdev": false,
|
||||
"contributor": true
|
||||
},
|
||||
{
|
||||
"__user": "Lilian Tedone#6223",
|
||||
"id": "184021060562321419",
|
||||
"developer": false,
|
||||
"webdev": false,
|
||||
"contributor": true
|
||||
},
|
||||
{
|
||||
"__user": "samfun123#8972",
|
||||
"id": "76052829285916672",
|
||||
"developer": false,
|
||||
"webdev": false,
|
||||
"contributor": true
|
||||
},
|
||||
{
|
||||
"__user": "samogot#4379",
|
||||
"id": "171005991272316937",
|
||||
"developer": false,
|
||||
"webdev": false,
|
||||
"contributor": true
|
||||
}
|
||||
]
|
|
@ -51,6 +51,12 @@
|
|||
"hint": "Adds some of BetterDiscord's internal modules to `global._bd`.",
|
||||
"value": false
|
||||
},
|
||||
{
|
||||
"id": "debugger-keybind",
|
||||
"type": "keybind",
|
||||
"text": "Debugger keybind",
|
||||
"hint": "When this keybind is activated the developer tools will be opened and Discord will be paused."
|
||||
},
|
||||
{
|
||||
"id": "ignore-content-manager-errors",
|
||||
"type": "bool",
|
||||
|
@ -91,7 +97,6 @@
|
|||
{
|
||||
"id": "css",
|
||||
"text": "CSS Editor",
|
||||
"hidden": true,
|
||||
"settings": [
|
||||
{
|
||||
"category": "default",
|
||||
|
|
|
@ -10,39 +10,32 @@
|
|||
|
||||
import { DOM, BdUI, Modals, Reflection } from 'ui';
|
||||
import BdCss from './styles/index.scss';
|
||||
import { Patcher, MonkeyPatch, Vendor, Events, CssEditor, Globals, ExtModuleManager, PluginManager, ThemeManager, ModuleManager, WebpackModules, Settings, Database, ReactComponents, ReactAutoPatcher, DiscordApi } from 'modules';
|
||||
import { Events, CssEditor, Globals, Settings, Database, Updater, ModuleManager, PluginManager, ThemeManager, ExtModuleManager, Vendor, WebpackModules, Patcher, MonkeyPatch, ReactComponents, ReactAutoPatcher, DiscordApi } from 'modules';
|
||||
import { ClientLogger as Logger, ClientIPC, Utils } from 'common';
|
||||
import { EmoteModule } from 'builtin';
|
||||
import electron from 'electron';
|
||||
import path from 'path';
|
||||
|
||||
const tests = typeof PRODUCTION === 'undefined';
|
||||
const ignoreExternal = false;
|
||||
const DEV = true;
|
||||
|
||||
class BetterDiscord {
|
||||
|
||||
constructor() {
|
||||
Logger.file = tests ? path.resolve(__dirname, '..', '..', 'tests', 'log.txt') : path.join(__dirname, 'log.txt');
|
||||
Logger.log('main', 'BetterDiscord starting');
|
||||
|
||||
this._bd = {
|
||||
DOM,
|
||||
BdUI,
|
||||
Modals,
|
||||
Reflection,
|
||||
Patcher,
|
||||
MonkeyPatch,
|
||||
DOM, BdUI, Modals, Reflection,
|
||||
|
||||
Events, CssEditor, Globals, Settings, Database, Updater,
|
||||
ModuleManager, PluginManager, ThemeManager, ExtModuleManager,
|
||||
Vendor,
|
||||
Events,
|
||||
CssEditor,
|
||||
Globals,
|
||||
ExtModuleManager,
|
||||
PluginManager,
|
||||
ThemeManager,
|
||||
ModuleManager,
|
||||
WebpackModules,
|
||||
Settings,
|
||||
Database,
|
||||
ReactComponents,
|
||||
DiscordApi,
|
||||
Logger,
|
||||
ClientIPC,
|
||||
Utils,
|
||||
EmoteModule
|
||||
|
||||
WebpackModules, Patcher, MonkeyPatch, ReactComponents, DiscordApi,
|
||||
EmoteModule,
|
||||
|
||||
Logger, ClientIPC, Utils
|
||||
};
|
||||
|
||||
const developermode = Settings.getSetting('core', 'advanced', 'developer-mode');
|
||||
|
@ -52,6 +45,14 @@ class BetterDiscord {
|
|||
else if (window._bd) delete window._bd;
|
||||
});
|
||||
|
||||
const debuggerkeybind = Settings.getSetting('core', 'advanced', 'debugger-keybind');
|
||||
debuggerkeybind.on('keybind-activated', () => {
|
||||
const currentWindow = electron.remote.getCurrentWindow();
|
||||
if (currentWindow.isDevToolsOpened()) return eval('debugger;');
|
||||
currentWindow.openDevTools();
|
||||
setTimeout(() => eval('debugger;'), 1000);
|
||||
});
|
||||
|
||||
DOM.injectStyle(BdCss, 'bdmain');
|
||||
this.globalReady = this.globalReady.bind(this);
|
||||
Events.on('global-ready', this.globalReady);
|
||||
|
@ -63,14 +64,16 @@ class BetterDiscord {
|
|||
await Database.init();
|
||||
await Settings.loadSettings();
|
||||
await ModuleManager.initModules();
|
||||
Modals.showContentManagerErrors();
|
||||
|
||||
if (!ignoreExternal) {
|
||||
await ExtModuleManager.loadAllModules(true);
|
||||
await PluginManager.loadAllPlugins(true);
|
||||
await ThemeManager.loadAllThemes(true);
|
||||
}
|
||||
|
||||
if (!Settings.get('core', 'advanced', 'ignore-content-manager-errors'))
|
||||
Modals.showContentManagerErrors();
|
||||
|
||||
Events.emit('ready');
|
||||
Events.emit('discord-ready');
|
||||
EmoteModule.observe();
|
||||
|
@ -90,7 +93,7 @@ class BetterDiscord {
|
|||
if (window.BetterDiscord) {
|
||||
Logger.log('main', 'Attempting to inject again?');
|
||||
} else {
|
||||
let instance = null;
|
||||
let instance;
|
||||
Events.on('autopatcher', () => instance = new BetterDiscord());
|
||||
ReactAutoPatcher.autoPatch().then(() => Events.emit('autopatcher'));
|
||||
}
|
||||
|
|
|
@ -59,6 +59,7 @@ export default class Content {
|
|||
|
||||
/**
|
||||
* Opens a settings modal for this content.
|
||||
* @return {Modal}
|
||||
*/
|
||||
showSettingsModal() {
|
||||
return Modals.contentSettings(this);
|
||||
|
@ -73,27 +74,20 @@ export default class Content {
|
|||
|
||||
/**
|
||||
* Saves the content's current configuration.
|
||||
* @return {Promise}
|
||||
*/
|
||||
async saveConfiguration() {
|
||||
try {
|
||||
/*
|
||||
await FileUtils.writeFile(`${this.contentPath}/user.config.json`, JSON.stringify({
|
||||
enabled: this.enabled,
|
||||
config: this.settings.strip().settings,
|
||||
data: this.data
|
||||
}));
|
||||
*/
|
||||
Database.insertOrUpdate({ type: 'contentconfig', $or: [{ id: this.id }, { name: this.name }] }, {
|
||||
type: 'contentconfig',
|
||||
Database.insertOrUpdate({ type: `${this.type}-config`, id: this.id }, {
|
||||
type: `${this.type}-config`,
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
enabled: this.enabled,
|
||||
config: this.settings.strip().settings,
|
||||
data: this.data
|
||||
});
|
||||
this.settings.setSaved();
|
||||
this.settings.setSaved();
|
||||
} catch (err) {
|
||||
Logger.err(this.name, ['Failed to save configuration', err]);
|
||||
Logger.err(this.name, ['Failed to save configuration', err]);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
@ -143,15 +137,6 @@ export default class Content {
|
|||
return this.events.on(...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an event listener.
|
||||
* @param {String} event The event to remove the listener from
|
||||
* @param {Function} callback The bound callback (optional)
|
||||
*/
|
||||
off(...args) {
|
||||
return this.events.removeListener(...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an event listener that removes itself when called, therefore only being called once.
|
||||
* @param {String} event The event to add the listener to
|
||||
|
@ -162,6 +147,15 @@ export default class Content {
|
|||
return this.events.once(...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an event listener.
|
||||
* @param {String} event The event to remove the listener from
|
||||
* @param {Function} callback The bound callback (optional)
|
||||
*/
|
||||
off(...args) {
|
||||
return this.events.removeListener(...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an event.
|
||||
* @param {String} event The event to emit
|
||||
|
|
|
@ -24,31 +24,52 @@ import Combokeys from 'combokeys';
|
|||
export default class {
|
||||
|
||||
/**
|
||||
* Any errors that happened
|
||||
* returns {Array}
|
||||
* Any errors that happened.
|
||||
* @return {Array}
|
||||
*/
|
||||
static get errors() {
|
||||
return this._errors || (this._errors = []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Locallly stored content
|
||||
* returns {Array}
|
||||
* Locally stored content.
|
||||
* @return {Array}
|
||||
*/
|
||||
static get localContent() {
|
||||
return this._localContent ? this._localContent : (this._localContent = []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Local path for content
|
||||
* returns {String}
|
||||
* The type of content this content manager manages.
|
||||
*/
|
||||
static get contentType() {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of this content manager.
|
||||
*/
|
||||
static get moduleName() {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* The path used to store this content manager's content.
|
||||
*/
|
||||
static get pathId() {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Local path for content.
|
||||
* @return {String}
|
||||
*/
|
||||
static get contentPath() {
|
||||
return Globals.getPath(this.pathId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all locally stored content
|
||||
* Load all locally stored content.
|
||||
* @param {bool} suppressErrors Suppress any errors that occur during loading of content
|
||||
*/
|
||||
static async loadAllContent(suppressErrors = false) {
|
||||
|
@ -83,8 +104,6 @@ export default class {
|
|||
});
|
||||
this._errors = [];
|
||||
}
|
||||
|
||||
return this.localContent;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
|
@ -102,7 +121,7 @@ export default class {
|
|||
const directories = await FileUtils.listDirectory(this.contentPath);
|
||||
|
||||
for (let dir of directories) {
|
||||
// If content is already loaded this should resolve.
|
||||
// If content is already loaded this should resolve
|
||||
if (this.getContentByDirName(dir)) continue;
|
||||
|
||||
try {
|
||||
|
@ -150,8 +169,6 @@ export default class {
|
|||
});
|
||||
this._errors = [];
|
||||
}
|
||||
|
||||
return this.localContent;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
|
@ -169,15 +186,12 @@ export default class {
|
|||
|
||||
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}` };
|
||||
}
|
||||
}
|
||||
if (!reload && this.getContentByPath(contentPath))
|
||||
throw { 'message': `Attempted to load already loaded user content: ${path}` };
|
||||
|
||||
const readConfig = await this.readConfig(contentPath);
|
||||
const mainPath = path.join(contentPath, readConfig.main);
|
||||
const configPath = path.resolve(contentPath, 'config.json');
|
||||
const readConfig = await FileUtils.readJsonFromFile(configPath);
|
||||
const mainPath = path.join(contentPath, readConfig.main || 'index.js');
|
||||
|
||||
const defaultConfig = new SettingsSet({
|
||||
settings: readConfig.defaultConfig,
|
||||
|
@ -191,19 +205,16 @@ export default class {
|
|||
};
|
||||
|
||||
try {
|
||||
//const readUserConfig = await this.readUserConfig(contentPath);
|
||||
const readUserConfig = await Database.find({ type: 'contentconfig', name: readConfig.info.name });
|
||||
const id = readConfig.info.id || readConfig.info.name.toLowerCase().replace(/[^a-zA-Z0-9-]/g, '-').replace(/--/g, '-');
|
||||
const readUserConfig = await Database.find({ type: `${this.contentType}-config`, id });
|
||||
if (readUserConfig.length) {
|
||||
userConfig.enabled = readUserConfig[0].enabled || false;
|
||||
// await userConfig.config.merge({ settings: readUserConfig.config });
|
||||
// userConfig.config.setSaved();
|
||||
// userConfig.config = userConfig.config.clone({ settings: readUserConfig.config });
|
||||
userConfig.config = readUserConfig[0].config;
|
||||
userConfig.data = readUserConfig[0].data || {};
|
||||
}
|
||||
} 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*/
|
||||
Logger.info(this.moduleName, `Failed reading config for ${this.contentType} ${readConfig.info.name} in ${dirName}`);
|
||||
Logger.err(this.moduleName, err);
|
||||
} 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
|
||||
Logger.warn(this.moduleName, [`Failed reading config for ${this.contentType} ${readConfig.info.name} in ${dirName}`, err]);
|
||||
}
|
||||
|
||||
userConfig.config = defaultConfig.clone({ settings: userConfig.config });
|
||||
|
@ -244,9 +255,10 @@ export default class {
|
|||
}
|
||||
|
||||
/**
|
||||
* Unload content
|
||||
* @param {any} content Content to unload
|
||||
* @param {bool} reload Whether to reload the content after
|
||||
* Unload content.
|
||||
* @param {Content|String} content Content to unload
|
||||
* @param {Boolean} reload Whether to reload the content after
|
||||
* @return {Content}
|
||||
*/
|
||||
static async unloadContent(content, reload) {
|
||||
content = this.findContent(content);
|
||||
|
@ -275,34 +287,18 @@ export default class {
|
|||
}
|
||||
|
||||
/**
|
||||
* Reload content
|
||||
* @param {any} content Content to reload
|
||||
* Reload content.
|
||||
* @param {Content|String} content Content to reload
|
||||
* @return {Content}
|
||||
*/
|
||||
static reloadContent(content) {
|
||||
return this.unloadContent(content, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read content config file
|
||||
* @param {any} configPath Config file path
|
||||
*/
|
||||
static async readConfig(configPath) {
|
||||
configPath = path.resolve(configPath, 'config.json');
|
||||
return FileUtils.readJsonFromFile(configPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read content user config file
|
||||
* @param {any} configPath User config file path
|
||||
*/
|
||||
static async readUserConfig(configPath) {
|
||||
configPath = path.resolve(configPath, 'user.config.json');
|
||||
return FileUtils.readJsonFromFile(configPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the passed object is an instance of this content type.
|
||||
* @param {any} content Object to check
|
||||
* @param {Any} content Object to check
|
||||
* @return {Boolean}
|
||||
*/
|
||||
static isThisContent(content) {
|
||||
return content instanceof Content;
|
||||
|
@ -318,8 +314,9 @@ export default class {
|
|||
|
||||
/**
|
||||
* Wildcard content finder
|
||||
* @param {any} wild Content ID / directory name / path / name
|
||||
* @param {bool} nonunique Allow searching attributes that may not be unique
|
||||
* @param {String} wild Content ID / directory name / path / name
|
||||
* @param {Boolean} nonunique Allow searching attributes that may not be unique
|
||||
* @return {Content}
|
||||
*/
|
||||
static findContent(wild, nonunique) {
|
||||
if (this.isThisContent(wild)) return wild;
|
||||
|
@ -338,7 +335,8 @@ export default class {
|
|||
|
||||
/**
|
||||
* Wait for content to load
|
||||
* @param {any} content_id
|
||||
* @param {String} content_id
|
||||
* @return {Promise}
|
||||
*/
|
||||
static waitForContent(content_id) {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
|
|
@ -32,24 +32,24 @@ export default new class {
|
|||
}
|
||||
|
||||
/**
|
||||
* Init css editor
|
||||
* Init css editor.
|
||||
*/
|
||||
init() {
|
||||
ClientIPC.on('bd-get-scss', () => this.sendToEditor('set-scss', { scss: this.scss }));
|
||||
ClientIPC.on('bd-get-scss', () => this.scss, true);
|
||||
ClientIPC.on('bd-update-scss', (e, scss) => this.updateScss(scss));
|
||||
ClientIPC.on('bd-save-csseditor-bounds', (e, bounds) => this.saveEditorBounds(bounds));
|
||||
|
||||
ClientIPC.on('bd-save-scss', async (e, scss) => {
|
||||
await this.updateScss(scss);
|
||||
await this.save();
|
||||
});
|
||||
}, true);
|
||||
|
||||
this.liveupdate = Settings.getSetting('css', 'default', 'live-update');
|
||||
this.liveupdate.on('setting-updated', event => {
|
||||
this.sendToEditor('set-liveupdate', event.value);
|
||||
});
|
||||
|
||||
ClientIPC.on('bd-get-liveupdate', () => this.sendToEditor('set-liveupdate', this.liveupdate.value));
|
||||
ClientIPC.on('bd-get-liveupdate', () => this.liveupdate.value, true);
|
||||
ClientIPC.on('bd-set-liveupdate', (e, value) => this.liveupdate.value = value);
|
||||
|
||||
this.watchfilessetting = Settings.getSetting('css', 'default', 'watch-files');
|
||||
|
@ -60,20 +60,20 @@ export default new class {
|
|||
}
|
||||
|
||||
/**
|
||||
* Show css editor, flashes if already visible
|
||||
* Show css editor, flashes if already visible.
|
||||
*/
|
||||
async show() {
|
||||
await ClientIPC.send('openCssEditor', this.editor_bounds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update css in client
|
||||
* @param {String} scss scss to compile
|
||||
* @param {bool} sendSource send to css editor instance
|
||||
* Update css in client.
|
||||
* @param {String} scss SCSS to compile
|
||||
* @param {bool} sendSource Whether to send to css editor instance
|
||||
*/
|
||||
async updateScss(scss, sendSource) {
|
||||
if (sendSource)
|
||||
this.sendToEditor('set-scss', { scss });
|
||||
this.sendToEditor('set-scss', scss);
|
||||
|
||||
if (!scss) {
|
||||
this._scss = this.css = '';
|
||||
|
@ -97,24 +97,26 @@ export default new class {
|
|||
}
|
||||
|
||||
/**
|
||||
* Save css to file
|
||||
* Save css to file.
|
||||
* @return {Promise}
|
||||
*/
|
||||
async save() {
|
||||
Settings.saveSettings();
|
||||
save() {
|
||||
return Settings.saveSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Save current editor bounds
|
||||
* @param {Rectangle} bounds editor bounds
|
||||
* Save current editor bounds.
|
||||
* @param {Rectangle} bounds Editor bounds
|
||||
* @return {Promise}
|
||||
*/
|
||||
saveEditorBounds(bounds) {
|
||||
this.editor_bounds = bounds;
|
||||
Settings.saveSettings();
|
||||
return Settings.saveSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send scss to core for compilation
|
||||
* @param {String} scss scss string
|
||||
* Send SCSS to core for compilation.
|
||||
* @param {String} scss SCSS string
|
||||
*/
|
||||
async compile(scss) {
|
||||
return await ClientIPC.send('bd-compileSass', {
|
||||
|
@ -124,7 +126,7 @@ export default new class {
|
|||
}
|
||||
|
||||
/**
|
||||
* Recompile the current SCSS
|
||||
* Recompile the current SCSS.
|
||||
* @return {Promise}
|
||||
*/
|
||||
async recompile() {
|
||||
|
@ -132,16 +134,18 @@ export default new class {
|
|||
}
|
||||
|
||||
/**
|
||||
* Send data to open editor
|
||||
* @param {any} channel
|
||||
* @param {any} data
|
||||
* Send data to open editor.
|
||||
* @param {String} channel
|
||||
* @param {Any} data
|
||||
* @return {Promise}
|
||||
*/
|
||||
async sendToEditor(channel, data) {
|
||||
return await ClientIPC.send('sendToCssEditor', { channel, data });
|
||||
return ClientIPC.sendToCssEditor(channel, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens an SCSS file in a system editor
|
||||
* Opens an SCSS file in a system editor.
|
||||
* @return {Promise}
|
||||
*/
|
||||
async openSystemEditor() {
|
||||
try {
|
||||
|
@ -160,7 +164,8 @@ export default new class {
|
|||
throw {message: 'Failed to open system editor.'};
|
||||
}
|
||||
|
||||
/** Set current state
|
||||
/**
|
||||
* Set current state
|
||||
* @param {String} scss Current uncompiled SCSS
|
||||
* @param {String} css Current compiled CSS
|
||||
* @param {String} files Files imported in the SCSS
|
||||
|
@ -168,36 +173,35 @@ export default new class {
|
|||
*/
|
||||
setState(scss, css, files, err) {
|
||||
this._scss = scss;
|
||||
this.sendToEditor('set-scss', { scss });
|
||||
this.sendToEditor('set-scss', scss);
|
||||
this.css = css;
|
||||
this.files = files;
|
||||
this.error = err;
|
||||
}
|
||||
|
||||
/**
|
||||
* Current uncompiled scss
|
||||
* Current uncompiled scss.
|
||||
*/
|
||||
get scss() {
|
||||
return this._scss || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set current scss
|
||||
* Set current scss.
|
||||
*/
|
||||
set scss(scss) {
|
||||
this.updateScss(scss, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Current compiled css
|
||||
* Current compiled css.
|
||||
*/
|
||||
get css() {
|
||||
return this._css || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject compiled css to head
|
||||
* {DOM}
|
||||
* Inject compiled css to head.
|
||||
*/
|
||||
set css(css) {
|
||||
this._css = css;
|
||||
|
@ -205,15 +209,14 @@ export default new class {
|
|||
}
|
||||
|
||||
/**
|
||||
* Current error
|
||||
* Current error.
|
||||
*/
|
||||
get error() {
|
||||
return this._error || undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set current error
|
||||
* {DOM}
|
||||
* Set current error.
|
||||
*/
|
||||
set error(err) {
|
||||
this._error = err;
|
||||
|
@ -293,7 +296,7 @@ export default new class {
|
|||
|
||||
/**
|
||||
* Checks if the system editor's file exists.
|
||||
* @return {Boolean}
|
||||
* @return {Promise}
|
||||
*/
|
||||
async fileExists() {
|
||||
try {
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { ClientIPC } from 'bdipc';
|
||||
import { ClientIPC } from 'common';
|
||||
|
||||
export default class {
|
||||
|
||||
|
@ -16,6 +16,12 @@ export default class {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts or updates data in the database.
|
||||
* @param {Object} args The record to find
|
||||
* @param {Object} data The new record
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async insertOrUpdate(args, data) {
|
||||
try {
|
||||
return ClientIPC.send('bd-dba', { action: 'update', args, data });
|
||||
|
@ -24,6 +30,11 @@ export default class {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds data in the database.
|
||||
* @param {Object} args The record to find
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async find(args) {
|
||||
try {
|
||||
return ClientIPC.send('bd-dba', { action: 'find', args });
|
||||
|
@ -31,4 +42,5 @@ export default class {
|
|||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -402,7 +402,7 @@ export default class DiscordApi {
|
|||
|
||||
static get currentChannel() {
|
||||
const channel = Modules.ChannelStore.getChannel(Modules.SelectedChannelStore.getChannelId());
|
||||
return channel.isPrivate ? new PrivateChannel(channel) : new GuildChannel(channel);
|
||||
if (channel) return channel.isPrivate() ? new PrivateChannel(channel) : new GuildChannel(channel);
|
||||
}
|
||||
|
||||
static get currentUser() {
|
||||
|
@ -415,5 +415,5 @@ export default class DiscordApi {
|
|||
for (const id of friends) returnUsers.push(User.fromId(id));
|
||||
return returnUsers;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* BetterDiscord WebpackModules Module
|
||||
* BetterDiscord Event Hook
|
||||
* Copyright (c) 2015-present Jiiks/JsSucks - https://github.com/Jiiks / https://github.com/JsSucks
|
||||
* All rights reserved.
|
||||
* https://betterdiscord.net
|
||||
|
@ -8,14 +8,13 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import EventListener from './eventlistener';
|
||||
import { Utils } from 'common';
|
||||
import Events from './events';
|
||||
import { Utils, ClientLogger as Logger } from 'common';
|
||||
import { WebpackModules } from './webpackmodules';
|
||||
import Events from './events';
|
||||
import EventListener from './eventlistener';
|
||||
|
||||
import * as SocketStructs from '../structs/socketstructs';
|
||||
|
||||
|
||||
/**
|
||||
* Discord socket event hook
|
||||
* @extends {EventListener}
|
||||
|
@ -23,7 +22,7 @@ import * as SocketStructs from '../structs/socketstructs';
|
|||
export default class extends EventListener {
|
||||
|
||||
init() {
|
||||
console.log(SocketStructs);
|
||||
Logger.log('EventHook', SocketStructs);
|
||||
this.hook();
|
||||
}
|
||||
|
||||
|
@ -44,16 +43,16 @@ export default class extends EventListener {
|
|||
orig.call(this, ...args);
|
||||
self.wsc = this;
|
||||
self.emit(...args);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
get eventsModule() {
|
||||
return WebpackModules.getModuleByPrototypes(['setMaxListeners', 'emit']);
|
||||
return WebpackModules.getModuleByName('Events');
|
||||
}
|
||||
|
||||
/**
|
||||
* Discord emit overload
|
||||
* @param {any} e
|
||||
* @param {any} event
|
||||
* @param {any} action
|
||||
* @param {any} data
|
||||
*/
|
||||
|
@ -66,8 +65,8 @@ export default class extends EventListener {
|
|||
|
||||
/**
|
||||
* Emit callback
|
||||
* @param {any} e Event Action
|
||||
* @param {any} d Event Args
|
||||
* @param {any} event Event
|
||||
* @param {any} data Event data
|
||||
*/
|
||||
dispatch(e, d) {
|
||||
Events.emit('raw-event', { type: e, data: d });
|
||||
|
@ -143,7 +142,7 @@ export default class extends EventListener {
|
|||
LFG_LISTING_CREATE: 'LFG_LISTING_CREATE', // No groups here
|
||||
LFG_LISTING_DELETE: 'LFG_LISTING_DELETE', // Thank you
|
||||
BRAINTREE_POPUP_BRIDGE_CALLBACK: 'BRAINTREE_POPUP_BRIDGE_CALLBACK' // What
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -12,14 +12,39 @@ import { EventEmitter } from 'events';
|
|||
const emitter = new EventEmitter();
|
||||
|
||||
export default class {
|
||||
static on(eventName, callBack) {
|
||||
emitter.on(eventName, callBack);
|
||||
|
||||
/**
|
||||
* Adds an event listener.
|
||||
* @param {String} event The event to listen for
|
||||
* @param {Function} callback The function to call when the event is emitted
|
||||
*/
|
||||
static on(event, callback) {
|
||||
emitter.on(event, callback);
|
||||
}
|
||||
|
||||
static off(eventName, callBack) {
|
||||
emitter.removeListener(eventName, callBack);
|
||||
/**
|
||||
* Adds an event listener that is only called once.
|
||||
* @param {String} event The event to listen for
|
||||
* @param {Function} callback The function to call when the event is emitted
|
||||
*/
|
||||
static once(event, callback) {
|
||||
emitter.once(event, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an event listener.
|
||||
* @param {String} event The event to remove
|
||||
* @param {Function} callback The listener to remove
|
||||
*/
|
||||
static off(event, callback) {
|
||||
emitter.removeListener(event, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an event
|
||||
* @param {String} event The event to emit
|
||||
* @param {Any} ...data Data to pass to the event listeners
|
||||
*/
|
||||
static emit(...args) {
|
||||
emitter.emit(...args);
|
||||
}
|
||||
|
|
|
@ -11,7 +11,8 @@
|
|||
const eventemitters = new WeakMap();
|
||||
|
||||
export default class EventsWrapper {
|
||||
constructor(eventemitter) {
|
||||
|
||||
constructor(eventemitter, bind) {
|
||||
eventemitters.set(this, eventemitter);
|
||||
}
|
||||
|
||||
|
@ -19,26 +20,33 @@ export default class EventsWrapper {
|
|||
return this._eventSubs || (this._eventSubs = []);
|
||||
}
|
||||
|
||||
get on() { return this.subscribe }
|
||||
subscribe(event, callback) {
|
||||
if (this.eventSubs.find(e => e.event === event && e.callback === callback)) return;
|
||||
this.eventSubs.push({
|
||||
event,
|
||||
callback
|
||||
});
|
||||
eventemitters.get(this).on(event, callback);
|
||||
const boundCallback = () => callback.apply(this.bind, arguments);
|
||||
this.eventSubs.push({ event, callback, boundCallback });
|
||||
eventemitters.get(this).on(event, boundCallback);
|
||||
}
|
||||
|
||||
once(event, callback) {
|
||||
if (this.eventSubs.find(e => e.event === event && e.callback === callback)) return;
|
||||
const boundCallback = () => this.off(event, callback) && callback.apply(this.bind, arguments);
|
||||
this.eventSubs.push({ event, callback, boundCallback });
|
||||
eventemitters.get(this).on(event, boundCallback);
|
||||
}
|
||||
|
||||
get off() { return this.unsubscribe }
|
||||
unsubscribe(event, callback) {
|
||||
for (let index of this.eventSubs) {
|
||||
if (this.eventSubs[index].event !== event || (callback && this.eventSubs[index].callback === callback)) return;
|
||||
eventemitters.get(this).off(event, this.eventSubs[index].callback);
|
||||
if (this.eventSubs[index].event !== event || (callback && this.eventSubs[index].callback === callback)) continue;
|
||||
eventemitters.get(this).off(event, this.eventSubs[index].boundCallback);
|
||||
this.eventSubs.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
unsubscribeAll() {
|
||||
for (let event of this.eventSubs) {
|
||||
eventemitters.get(this).off(event.event, event.callback);
|
||||
eventemitters.get(this).off(event.event, event.boundCallback);
|
||||
}
|
||||
this.eventSubs.splice(0, this.eventSubs.length);
|
||||
}
|
||||
|
|
|
@ -8,9 +8,10 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import sparkplug from 'sparkplug';
|
||||
import { ClientIPC } from 'common';
|
||||
import Module from './module';
|
||||
import Events from './events';
|
||||
import { ClientIPC } from 'bdipc';
|
||||
|
||||
export default new class extends Module {
|
||||
|
||||
|
@ -28,28 +29,24 @@ export default new class extends Module {
|
|||
this.getObject = this.getObject.bind(this);
|
||||
}
|
||||
|
||||
first() {
|
||||
(async() => {
|
||||
const config = await ClientIPC.send('getConfig');
|
||||
this.setState(config);
|
||||
async first() {
|
||||
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 = {};
|
||||
// This is for Discord to stop error reporting :3
|
||||
window.BetterDiscord = {
|
||||
version: config.version,
|
||||
v: config.version
|
||||
};
|
||||
window.jQuery = {};
|
||||
|
||||
if (window.__bd) {
|
||||
this.setState(window.__bd);
|
||||
window.__bd = {
|
||||
setWS: this.setWS
|
||||
}
|
||||
}
|
||||
if (sparkplug.bd) {
|
||||
this.setState({ bd: sparkplug.bd });
|
||||
sparkplug.bd.setWS = this.setWS;
|
||||
}
|
||||
|
||||
Events.emit('global-ready');
|
||||
Events.emit('socket-created', this.state.wsHook);
|
||||
})();
|
||||
Events.emit('global-ready');
|
||||
Events.emit('socket-created', this.state.wsHook);
|
||||
}
|
||||
|
||||
setWS(wSocket) {
|
||||
|
@ -60,19 +57,43 @@ export default new class extends Module {
|
|||
}
|
||||
|
||||
getObject(name) {
|
||||
return this.state[name];
|
||||
return this.config[name] || this.bd[name];
|
||||
}
|
||||
|
||||
get bd() {
|
||||
return this.state.bd;
|
||||
}
|
||||
|
||||
get localStorage() {
|
||||
return this.bd.localStorage;
|
||||
}
|
||||
|
||||
get webSocket() {
|
||||
return this.bd.wsHook;
|
||||
}
|
||||
|
||||
get WebSocket() {
|
||||
return this.bd.wsOrig;
|
||||
}
|
||||
|
||||
get ignited() {
|
||||
return this.bd.ignited;
|
||||
}
|
||||
|
||||
get config() {
|
||||
return this.state.config;
|
||||
}
|
||||
|
||||
get paths() {
|
||||
return this.config.paths;
|
||||
}
|
||||
|
||||
getPath(id) {
|
||||
return this.state.paths.find(path => path.id === id).path;
|
||||
return this.paths.find(path => path.id === id).path;
|
||||
}
|
||||
|
||||
static get paths() {
|
||||
return this.state.paths;
|
||||
}
|
||||
|
||||
static get version() {
|
||||
return this.state.version;
|
||||
get version() {
|
||||
return this.config.version;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -5,20 +5,19 @@
|
|||
* https://github.com/JsSucks - 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
Base Module that every non-static module should extend
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Base Module that every non-static module should extend
|
||||
*/
|
||||
export default class Module {
|
||||
|
||||
constructor(args) {
|
||||
this.__ = {
|
||||
state: args || {},
|
||||
args
|
||||
}
|
||||
};
|
||||
this.setState = this.setState.bind(this);
|
||||
this.initialize();
|
||||
}
|
||||
|
@ -38,7 +37,6 @@ export default class Module {
|
|||
set args(t) { }
|
||||
get args() { return this.__.args; }
|
||||
|
||||
|
||||
set state(state) { return this.__.state = state; }
|
||||
get state() { return this.__.state; }
|
||||
|
||||
|
|
|
@ -8,30 +8,39 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/*Module Manager initializes all modules when everything is ready*/
|
||||
|
||||
import { ClientLogger as Logger } from 'common';
|
||||
import { Events, SocketProxy, EventHook, CssEditor } from 'modules';
|
||||
import { ProfileBadges } from 'ui';
|
||||
import Updater from './updater';
|
||||
|
||||
/**
|
||||
* Module Manager initializes all modules when everything is ready
|
||||
*/
|
||||
export default class {
|
||||
|
||||
/**
|
||||
* An array of modules.
|
||||
*/
|
||||
static get modules() {
|
||||
return this._modules ? this._modules : (this._modules = [
|
||||
new ProfileBadges(),
|
||||
new SocketProxy(),
|
||||
new EventHook(),
|
||||
CssEditor,
|
||||
new Updater()
|
||||
Updater
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes all modules.
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async initModules() {
|
||||
for (let module of this.modules) {
|
||||
try {
|
||||
if (module.init && module.init instanceof Function) module.init();
|
||||
} catch (err) {
|
||||
console.log(`Failed to initialize module: ${err}`);
|
||||
Logger.err('Module Manager', ['Failed to initialize module:', err]);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
|
|
@ -1,19 +1,23 @@
|
|||
export { default as Events } from './events';
|
||||
export { default as Settings } from './settings';
|
||||
export { default as CssEditor } from './csseditor';
|
||||
export { default as ExtModuleManager } from './extmodulemanager';
|
||||
export { default as Globals } from './globals';
|
||||
export { default as Settings } from './settings';
|
||||
export { default as Database } from './database';
|
||||
export { default as Updater } from './updater';
|
||||
|
||||
export { default as ModuleManager } from './modulemanager';
|
||||
export { default as PluginManager } from './pluginmanager';
|
||||
export { default as ThemeManager } from './thememanager';
|
||||
export { default as Globals } from './globals';
|
||||
export { default as ExtModuleManager } from './extmodulemanager';
|
||||
export { default as Permissions } from './permissionmanager';
|
||||
|
||||
export { default as EventsWrapper } from './eventswrapper';
|
||||
export { default as Vendor } from './vendor';
|
||||
|
||||
export * from './webpackmodules';
|
||||
export { default as ModuleManager } from './modulemanager';
|
||||
export * from './patcher';
|
||||
export * from './reactcomponents';
|
||||
export { default as EventListener } from './eventlistener';
|
||||
export { default as SocketProxy } from './socketproxy';
|
||||
export { default as EventHook } from './eventhook';
|
||||
export { default as Permissions } from './permissionmanager';
|
||||
export { default as Database } from './database';
|
||||
export { default as EventsWrapper } from './eventswrapper';
|
||||
export { default as DiscordApi } from './discordapi';
|
||||
export * from './patcher';
|
||||
export * from './reactcomponents';
|
||||
|
|
|
@ -5,14 +5,16 @@
|
|||
* https://github.com/JsSucks - 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.
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { WebpackModules } from './webpackmodules';
|
||||
import { ClientLogger as Logger, Utils } from 'common';
|
||||
|
||||
export class Patcher {
|
||||
|
||||
static get patches() { return this._patches || (this._patches = {}) }
|
||||
|
||||
static getPatchesByCaller(id) {
|
||||
const patches = [];
|
||||
for (const patch in this.patches) {
|
||||
|
@ -22,16 +24,21 @@ export class Patcher {
|
|||
}
|
||||
return patches;
|
||||
}
|
||||
|
||||
static unpatchAll(patches) {
|
||||
if (typeof patches === 'string')
|
||||
patches = this.getPatchesByCaller(patches);
|
||||
|
||||
for (const patch of patches) {
|
||||
for (const child of patch.children) {
|
||||
child.unpatch();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static resolveModule(module) {
|
||||
if (module instanceof Function || (module instanceof Object && !(module instanceof Array))) return module;
|
||||
if ('string' === typeof module) return WebpackModules.getModuleByName(module);
|
||||
if (typeof module === 'string') return WebpackModules.getModuleByName(module);
|
||||
if (module instanceof Array) return WebpackModules.getModuleByProps(module);
|
||||
return null;
|
||||
}
|
||||
|
@ -99,10 +106,12 @@ export class Patcher {
|
|||
static before() { return this.pushChildPatch(...arguments, 'before') }
|
||||
static after() { return this.pushChildPatch(...arguments, 'after') }
|
||||
static instead() { return this.pushChildPatch(...arguments, 'instead') }
|
||||
|
||||
static pushChildPatch(caller, unresolvedModule, functionName, callback, displayName, type = 'after') {
|
||||
const module = this.resolveModule(unresolvedModule);
|
||||
if (!module || !module[functionName] || !(module[functionName] instanceof Function)) return null;
|
||||
displayName = 'string' === typeof unresolvedModule ? unresolvedModule : displayName || module.displayName || module.name || module.constructor.displayName || module.constructor.name;
|
||||
displayName = typeof unresolvedModule === 'string' ? unresolvedModule :
|
||||
displayName || module.displayName || module.name || module.constructor.displayName || module.constructor.name;
|
||||
const patchId = `${displayName}:${functionName}:${caller}`;
|
||||
|
||||
const patch = this.patches[patchId] || this.pushPatch(caller, patchId, module, functionName);
|
||||
|
|
|
@ -15,15 +15,15 @@ export default class Plugin extends Content {
|
|||
|
||||
get type() { return 'plugin' }
|
||||
|
||||
// Don't use - these will eventually be removed!
|
||||
get pluginPath() { return this.contentPath }
|
||||
get pluginConfig() { return this.config }
|
||||
|
||||
get start() { return this.enable }
|
||||
get stop() { return this.disable }
|
||||
get stop() { return this.disable }
|
||||
|
||||
reload() {
|
||||
return PluginManager.reloadPlugin(this);
|
||||
}
|
||||
|
||||
unload() {
|
||||
PluginManager.unloadPlugin(this);
|
||||
return PluginManager.unloadPlugin(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* BetterDiscord Plugin Api
|
||||
* BetterDiscord Plugin API
|
||||
* Copyright (c) 2015-present Jiiks/JsSucks - https://github.com/Jiiks / https://github.com/JsSucks
|
||||
* All rights reserved.
|
||||
* https://betterdiscord.net
|
||||
|
@ -8,7 +8,7 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { Utils, ClientLogger as Logger, ClientIPC } from 'common';
|
||||
import { Utils, ClientLogger as Logger, ClientIPC, AsyncEventEmitter } from 'common';
|
||||
import Settings from './settings';
|
||||
import ExtModuleManager from './extmodulemanager';
|
||||
import PluginManager from './pluginmanager';
|
||||
|
@ -20,31 +20,24 @@ import { SettingsSet, SettingsCategory, Setting, SettingsScheme } from 'structs'
|
|||
import { BdMenuItems, Modals, DOM, Reflection } from 'ui';
|
||||
import DiscordApi from './discordapi';
|
||||
import { ReactComponents } from './reactcomponents';
|
||||
import { MonkeyPatch } from './patcher';
|
||||
import { Patcher, MonkeyPatch } from './patcher';
|
||||
|
||||
export default class PluginApi {
|
||||
|
||||
constructor(pluginInfo) {
|
||||
constructor(pluginInfo, pluginPath) {
|
||||
this.pluginInfo = pluginInfo;
|
||||
this.pluginPath = pluginPath;
|
||||
|
||||
this.Events = new EventsWrapper(Events);
|
||||
Utils.defineSoftGetter(this.Events, 'bind', () => this.plugin);
|
||||
|
||||
this._menuItems = undefined;
|
||||
this._injectedStyles = undefined;
|
||||
this._modalStack = undefined;
|
||||
}
|
||||
get Discord() {
|
||||
return DiscordApi;
|
||||
}
|
||||
get ReactComponents() {
|
||||
return ReactComponents;
|
||||
}
|
||||
get Reflection() {
|
||||
return Reflection;
|
||||
}
|
||||
get MonkeyPatch() {
|
||||
return module => MonkeyPatch(this.pluginInfo.id, module);
|
||||
}
|
||||
|
||||
get plugin() {
|
||||
return PluginManager.getPluginById(this.pluginInfo.id || this.pluginInfo.name.toLowerCase().replace(/[^a-zA-Z0-9-]/g, '-').replace(/--/g, '-'));
|
||||
return PluginManager.getPluginByPath(this.pluginPath);
|
||||
}
|
||||
|
||||
async bridge(plugin_id) {
|
||||
|
@ -61,15 +54,18 @@ export default class PluginApi {
|
|||
|
||||
get Api() { return this }
|
||||
|
||||
get AsyncEventEmitter() { return AsyncEventEmitter }
|
||||
get EventsWrapper() { return EventsWrapper }
|
||||
|
||||
/**
|
||||
* Logger
|
||||
*/
|
||||
|
||||
loggerLog(...message) { Logger.log(this.pluginInfo.name, message) }
|
||||
loggerErr(...message) { Logger.err(this.pluginInfo.name, message) }
|
||||
loggerWarn(...message) { Logger.warn(this.pluginInfo.name, message) }
|
||||
loggerInfo(...message) { Logger.info(this.pluginInfo.name, message) }
|
||||
loggerDbg(...message) { Logger.dbg(this.pluginInfo.name, message) }
|
||||
loggerLog(...message) { Logger.log(this.plugin.name, message) }
|
||||
loggerErr(...message) { Logger.err(this.plugin.name, message) }
|
||||
loggerWarn(...message) { Logger.warn(this.plugin.name, message) }
|
||||
loggerInfo(...message) { Logger.info(this.plugin.name, message) }
|
||||
loggerDbg(...message) { Logger.dbg(this.plugin.name, message) }
|
||||
get Logger() {
|
||||
return {
|
||||
log: this.loggerLog.bind(this),
|
||||
|
@ -381,6 +377,53 @@ export default class PluginApi {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* DiscordApi
|
||||
*/
|
||||
|
||||
get Discord() {
|
||||
return DiscordApi;
|
||||
}
|
||||
|
||||
get ReactComponents() {
|
||||
return ReactComponents;
|
||||
}
|
||||
|
||||
get Reflection() {
|
||||
return Reflection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Patcher
|
||||
*/
|
||||
|
||||
get patches() {
|
||||
return Patcher.getPatchesByCaller(this.plugin.id);
|
||||
}
|
||||
patchBefore(...args) { return this.pushChildPatch(...args, 'before') }
|
||||
patchAfter(...args) { return this.pushChildPatch(...args, 'after') }
|
||||
patchInstead(...args) { return this.pushChildPatch(...args, 'instead') }
|
||||
pushChildPatch(...args) {
|
||||
return Patcher.pushChildPatch(this.plugin.id, ...args);
|
||||
}
|
||||
unpatchAll(patches) {
|
||||
return Patcher.unpatchAll(patches || this.plugin.id);
|
||||
}
|
||||
get Patcher() {
|
||||
return Object.defineProperty({
|
||||
before: this.patchBefore.bind(this),
|
||||
after: this.patchAfter.bind(this),
|
||||
instead: this.patchInstead.bind(this),
|
||||
pushChildPatch: this.pushChildPatch.bind(this),
|
||||
unpatchAll: this.unpatchAll.bind(this),
|
||||
}, 'patches', {
|
||||
get: () => this.patches
|
||||
});
|
||||
}
|
||||
get monkeyPatch() {
|
||||
return module => MonkeyPatch(this.plugin.id, module);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Stop plugins from modifying the plugin API for all plugins
|
||||
|
|
|
@ -76,12 +76,12 @@ export default class extends ContentManager {
|
|||
static async loadPlugin(paths, configs, info, main, dependencies, permissions) {
|
||||
if (permissions && permissions.length > 0) {
|
||||
for (let perm of permissions) {
|
||||
console.log(`Permission: ${Permissions.permissionText(perm).HEADER} - ${Permissions.permissionText(perm).BODY}`);
|
||||
Logger.log(this.moduleName, `Permission: ${Permissions.permissionText(perm).HEADER} - ${Permissions.permissionText(perm).BODY}`);
|
||||
}
|
||||
try {
|
||||
const allowed = await Modals.permissions(`${info.name} wants to:`, info.name, permissions).promise;
|
||||
} catch (err) {
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -90,15 +90,13 @@ export default class extends ContentManager {
|
|||
for (const [key, value] of Object.entries(dependencies)) {
|
||||
const extModule = ExtModuleManager.findModule(key);
|
||||
if (!extModule) {
|
||||
throw {
|
||||
'message': `Dependency: ${key}:${value} is not loaded`
|
||||
};
|
||||
throw {message: `Dependency ${key}:${value} is not loaded.`};
|
||||
}
|
||||
deps[key] = extModule.__require;
|
||||
}
|
||||
}
|
||||
|
||||
const plugin = window.require(paths.mainPath)(Plugin, new PluginApi(info), Vendor, deps);
|
||||
const plugin = window.require(paths.mainPath)(Plugin, new PluginApi(info, paths.contentPath), Vendor, deps);
|
||||
if (!(plugin.prototype instanceof Plugin))
|
||||
throw {message: `Plugin ${info.name} did not return a class that extends Plugin.`};
|
||||
|
||||
|
@ -121,24 +119,24 @@ export default class extends ContentManager {
|
|||
static get unloadPlugin() { return this.unloadContent }
|
||||
static get reloadPlugin() { return this.reloadContent }
|
||||
|
||||
static stopPlugin(name) {
|
||||
const plugin = name instanceof Plugin ? name : this.getPluginByName(name);
|
||||
try {
|
||||
if (plugin) return plugin.stop();
|
||||
} catch (err) {
|
||||
// Logger.err('PluginManager', err);
|
||||
}
|
||||
return true; //Return true anyways since plugin doesn't exist
|
||||
/**
|
||||
* Stops a plugin.
|
||||
* @param {Plugin|String} plugin
|
||||
* @return {Promise}
|
||||
*/
|
||||
static stopPlugin(plugin) {
|
||||
plugin = this.isPlugin(plugin) ? plugin : this.getPluginById(plugin);
|
||||
return plugin.stop();
|
||||
}
|
||||
|
||||
static startPlugin(name) {
|
||||
const plugin = name instanceof Plugin ? name : this.getPluginByName(name);
|
||||
try {
|
||||
if (plugin) return plugin.start();
|
||||
} catch (err) {
|
||||
// Logger.err('PluginManager', err);
|
||||
}
|
||||
return true; //Return true anyways since plugin doesn't exist
|
||||
/**
|
||||
* Starts a plugin.
|
||||
* @param {Plugin|String} plugin
|
||||
* @return {Promise}
|
||||
*/
|
||||
static startPlugin(plugin) {
|
||||
plugin = this.isPlugin(plugin) ? plugin : this.getPluginById(plugin);
|
||||
return plugin.start();
|
||||
}
|
||||
|
||||
static get isPlugin() { return this.isThisContent }
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* https://github.com/JsSucks - 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.
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { MonkeyPatch, Patcher } from './patcher';
|
||||
|
@ -19,6 +19,7 @@ class Helpers {
|
|||
static get plannedActions() {
|
||||
return this._plannedActions || (this._plannedActions = new Map());
|
||||
}
|
||||
|
||||
static recursiveArray(parent, key, count = 1) {
|
||||
let index = 0;
|
||||
function* innerCall(parent, key) {
|
||||
|
@ -34,13 +35,15 @@ class Helpers {
|
|||
|
||||
return innerCall(parent, key);
|
||||
}
|
||||
|
||||
static recursiveArrayCount(parent, key) {
|
||||
let count = 0;
|
||||
// eslint-disable-next-line no-empty-pattern
|
||||
for (let { } of this.recursiveArray(parent, key))
|
||||
for (let {} of this.recursiveArray(parent, key))
|
||||
++count;
|
||||
return this.recursiveArray(parent, key, count);
|
||||
}
|
||||
|
||||
static get recursiveChildren() {
|
||||
return function* (parent, key, index = 0, count = 1) {
|
||||
const item = parent[key];
|
||||
|
@ -52,12 +55,14 @@ class Helpers {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
static returnFirst(iterator, process) {
|
||||
for (let child of iterator) {
|
||||
const retVal = process(child);
|
||||
if (retVal !== undefined) return retVal;
|
||||
}
|
||||
}
|
||||
|
||||
static getFirstChild(rootParent, rootKey, selector) {
|
||||
const getDirectChild = (item, selector) => {
|
||||
if (item && item.props && item.props.children) {
|
||||
|
@ -116,11 +121,13 @@ class Helpers {
|
|||
};
|
||||
return this.returnFirst(this.recursiveChildren(rootParent, rootKey), checkFilter.bind(null, selector)) || {};
|
||||
}
|
||||
|
||||
static parseSelector(selector) {
|
||||
if (selector.startsWith('.')) return { className: selector.substr(1) }
|
||||
if (selector.startsWith('#')) return { id: selector.substr(1) }
|
||||
return {}
|
||||
}
|
||||
|
||||
static findByProp(obj, what, value) {
|
||||
if (obj.hasOwnProperty(what) && obj[what] === value) return obj;
|
||||
if (obj.props && !obj.children) return this.findByProp(obj.props, what, value);
|
||||
|
@ -132,6 +139,7 @@ class Helpers {
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static findProp(obj, what) {
|
||||
if (obj.hasOwnProperty(what)) return obj[what];
|
||||
if (obj.props && !obj.children) return this.findProp(obj.props, what);
|
||||
|
@ -144,6 +152,7 @@ class Helpers {
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static get ReactDOM() {
|
||||
return WebpackModules.getModuleByName('ReactDOM');
|
||||
}
|
||||
|
@ -155,12 +164,15 @@ class ReactComponent {
|
|||
this._component = component;
|
||||
this._retVal = retVal;
|
||||
}
|
||||
|
||||
get id() {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
get component() {
|
||||
return this._component;
|
||||
}
|
||||
|
||||
get retVal() {
|
||||
return this._retVal;
|
||||
}
|
||||
|
|
|
@ -8,22 +8,23 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import defaultSettings from '../data/user.settings.default';
|
||||
import Globals from './globals';
|
||||
import CssEditor from './csseditor';
|
||||
import Events from './events';
|
||||
import { Utils, FileUtils, ClientLogger as Logger } from 'common';
|
||||
import { SettingsSet, SettingUpdatedEvent } from 'structs';
|
||||
import path from 'path';
|
||||
import Globals from './globals';
|
||||
import CssEditor from './csseditor';
|
||||
import Events from './events';
|
||||
import defaultSettings from '../data/user.settings.default';
|
||||
|
||||
export default new class Settings {
|
||||
|
||||
constructor() {
|
||||
this.settings = defaultSettings.map(_set => {
|
||||
const set = new SettingsSet(_set);
|
||||
|
||||
set.on('setting-updated', event => {
|
||||
const { category, setting, value, old_value } = event;
|
||||
Logger.log('Settings', `${set.id}/${category.id}/${setting.id} was changed from ${old_value} to ${value}`);
|
||||
Logger.log('Settings', [`${set.id}/${category.id}/${setting.id} was changed from`, old_value, 'to', value]);
|
||||
Events.emit('setting-updated', event);
|
||||
Events.emit(`setting-updated-${set.id}_${category.id}_${setting.id}`, event);
|
||||
});
|
||||
|
@ -37,6 +38,9 @@ export default new class Settings {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads BetterDiscord's settings.
|
||||
*/
|
||||
async loadSettings() {
|
||||
try {
|
||||
await FileUtils.ensureDirectory(this.dataPath);
|
||||
|
@ -48,7 +52,7 @@ export default new class Settings {
|
|||
for (let set of this.settings) {
|
||||
const newSet = settings.find(s => s.id === set.id);
|
||||
if (!newSet) continue;
|
||||
set.merge(newSet);
|
||||
await set.merge(newSet);
|
||||
set.setSaved();
|
||||
}
|
||||
|
||||
|
@ -57,10 +61,13 @@ export default new class Settings {
|
|||
} catch (err) {
|
||||
// There was an error loading settings
|
||||
// This probably means that the user doesn't have any settings yet
|
||||
Logger.err('Settings', err);
|
||||
Logger.warn('Settings', ['Failed to load internal settings', err]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves BetterDiscord's settings including CSS editor data.
|
||||
*/
|
||||
async saveSettings() {
|
||||
try {
|
||||
await FileUtils.ensureDirectory(this.dataPath);
|
||||
|
@ -72,15 +79,10 @@ export default new class Settings {
|
|||
css: CssEditor.css,
|
||||
css_editor_files: CssEditor.files,
|
||||
scss_error: CssEditor.error,
|
||||
css_editor_bounds: {
|
||||
width: CssEditor.editor_bounds.width,
|
||||
height: CssEditor.editor_bounds.height,
|
||||
x: CssEditor.editor_bounds.x,
|
||||
y: CssEditor.editor_bounds.y
|
||||
}
|
||||
css_editor_bounds: CssEditor.editor_bounds
|
||||
});
|
||||
|
||||
for (let set of this.getSettings) {
|
||||
for (let set of this.settings) {
|
||||
set.setSaved();
|
||||
}
|
||||
} catch (err) {
|
||||
|
@ -90,8 +92,13 @@ export default new class Settings {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds one of BetterDiscord's settings sets.
|
||||
* @param {String} set_id The ID of the set to find
|
||||
* @return {SettingsSet}
|
||||
*/
|
||||
getSet(set_id) {
|
||||
return this.getSettings.find(s => s.id === set_id);
|
||||
return this.settings.find(s => s.id === set_id);
|
||||
}
|
||||
|
||||
get core() { return this.getSet('core') }
|
||||
|
@ -100,39 +107,46 @@ export default new class Settings {
|
|||
get css() { return this.getSet('css') }
|
||||
get security() { return this.getSet('security') }
|
||||
|
||||
/**
|
||||
* Finds a category in one of BetterDiscord's settings sets.
|
||||
* @param {String} set_id The ID of the set to look in
|
||||
* @param {String} category_id The ID of the category to find
|
||||
* @return {SettingsCategory}
|
||||
*/
|
||||
getCategory(set_id, category_id) {
|
||||
const set = this.getSet(set_id);
|
||||
return set ? set.getCategory(category_id) : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a setting in one of BetterDiscord's settings sets.
|
||||
* @param {String} set_id The ID of the set to look in
|
||||
* @param {String} category_id The ID of the category to look in
|
||||
* @param {String} setting_id The ID of the setting to find
|
||||
* @return {Setting}
|
||||
*/
|
||||
getSetting(set_id, category_id, setting_id) {
|
||||
const set = this.getSet(set_id);
|
||||
return set ? set.getSetting(category_id, setting_id) : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a setting's value in one of BetterDiscord's settings sets.
|
||||
* @param {String} set_id The ID of the set to look in
|
||||
* @param {String} category_id The ID of the category to look in
|
||||
* @param {String} setting_id The ID of the setting to find
|
||||
* @return {Any}
|
||||
*/
|
||||
get(set_id, category_id, setting_id) {
|
||||
const set = this.getSet(set_id);
|
||||
return set ? set.get(category_id, setting_id) : undefined;
|
||||
}
|
||||
|
||||
async mergeSettings(set_id, newSettings) {
|
||||
const set = this.getSet(set_id);
|
||||
if (!set) return;
|
||||
|
||||
return await set.merge(newSettings);
|
||||
}
|
||||
|
||||
setSetting(set_id, category_id, setting_id, value) {
|
||||
const setting = this.getSetting(set_id, category_id, setting_id);
|
||||
if (!setting) throw {message: `Tried to set ${set_id}/${category_id}/${setting_id}, which doesn't exist`};
|
||||
setting.value = value;
|
||||
}
|
||||
|
||||
get getSettings() {
|
||||
return this.settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* The path to store user data in.
|
||||
*/
|
||||
get dataPath() {
|
||||
return Globals.getPath('data');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { ClientLogger as Logger } from 'common';
|
||||
import EventListener from './eventlistener';
|
||||
|
||||
export default class SocketProxy extends EventListener {
|
||||
|
@ -19,7 +20,7 @@ export default class SocketProxy extends EventListener {
|
|||
|
||||
get eventBindings() {
|
||||
return [
|
||||
{ id: 'socket-created', 'callback': this.socketCreated }
|
||||
{ id: 'socket-created', callback: this.socketCreated }
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -29,11 +30,11 @@ export default class SocketProxy extends EventListener {
|
|||
|
||||
socketCreated(socket) {
|
||||
this.activeSocket = socket;
|
||||
// socket.addEventListener('message', this.onMessage);
|
||||
// socket.addEventListener('message', this.onMessage);
|
||||
}
|
||||
|
||||
onMessage(e) {
|
||||
console.info(e);
|
||||
Logger.info('SocketProxy', e);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -31,10 +31,6 @@ export default class Theme extends Content {
|
|||
get type() { return 'theme' }
|
||||
get css() { return this.data.css }
|
||||
|
||||
// Don't use - these will eventually be removed!
|
||||
get themePath() { return this.contentPath }
|
||||
get themeConfig() { return this.config }
|
||||
|
||||
/**
|
||||
* Called when settings are updated.
|
||||
* This can be overridden by other content types.
|
||||
|
@ -63,7 +59,7 @@ export default class Theme extends Content {
|
|||
* @return {Promise}
|
||||
*/
|
||||
async compile() {
|
||||
console.log('Compiling CSS');
|
||||
Logger.log(this.name, 'Compiling CSS');
|
||||
|
||||
if (this.info.type === 'sass') {
|
||||
const config = await ThemeManager.getConfigAsSCSS(this.settings);
|
||||
|
@ -76,7 +72,7 @@ export default class Theme extends Content {
|
|||
Logger.log(this.name, ['Finished compiling theme', new class Info {
|
||||
get SCSS_variables() { console.log(config); }
|
||||
get Compiled_SCSS() { console.log(result.css.toString()); }
|
||||
get Result() { console.log(result); }
|
||||
get Result() { console.log(result); }
|
||||
}]);
|
||||
|
||||
return {
|
||||
|
@ -121,6 +117,7 @@ export default class Theme extends Content {
|
|||
*/
|
||||
set files(files) {
|
||||
this.data.files = files;
|
||||
|
||||
if (Settings.get('css', 'default', 'watch-files'))
|
||||
this.watchfiles = files;
|
||||
}
|
||||
|
|
|
@ -62,11 +62,11 @@ export default class ThemeManager extends ContentManager {
|
|||
}
|
||||
|
||||
static enableTheme(theme) {
|
||||
theme.enable();
|
||||
return theme.enable();
|
||||
}
|
||||
|
||||
static disableTheme(theme) {
|
||||
theme.disable();
|
||||
return theme.disable();
|
||||
}
|
||||
|
||||
static get isTheme() { return this.isThisContent }
|
||||
|
@ -74,6 +74,11 @@ export default class ThemeManager extends ContentManager {
|
|||
return theme instanceof Theme;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a representation of a settings set's values in SCSS.
|
||||
* @param {SettingsSet} settingsset The set to convert to SCSS
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async getConfigAsSCSS(settingsset) {
|
||||
const variables = [];
|
||||
|
||||
|
@ -87,6 +92,11 @@ export default class ThemeManager extends ContentManager {
|
|||
return variables.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a representation of a settings set's values as an SCSS map.
|
||||
* @param {SettingsSet} settingsset The set to convert to an SCSS map
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async getConfigAsSCSSMap(settingsset) {
|
||||
const variables = [];
|
||||
|
||||
|
@ -100,6 +110,11 @@ export default class ThemeManager extends ContentManager {
|
|||
return '(' + variables.join(', ') + ')';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a setting's name and value as a string that can be included in SCSS.
|
||||
* @param {Setting} setting The setting to convert to SCSS
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async parseSetting(setting) {
|
||||
const { type, id, value } = setting;
|
||||
const name = id.replace(/[^a-zA-Z0-9-]/g, '-').replace(/--/g, '-');
|
||||
|
@ -108,6 +123,11 @@ export default class ThemeManager extends ContentManager {
|
|||
if (scss) return [name, scss];
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes a string so it can be included in SCSS.
|
||||
* @param {String} value The string to escape
|
||||
* @return {String}
|
||||
*/
|
||||
static toSCSSString(value) {
|
||||
if (typeof value !== 'string' && value.toString) value = value.toString();
|
||||
return `'${typeof value === 'string' ? value.replace(/\\/g, '\\\\').replace(/'/g, '\\\'') : ''}'`;
|
||||
|
|
|
@ -13,15 +13,20 @@ import Globals from './globals';
|
|||
import { $ } from 'vendor';
|
||||
import { ClientLogger as Logger } from 'common';
|
||||
|
||||
export default class {
|
||||
export default new class {
|
||||
|
||||
constructor() {
|
||||
window.updater = this;
|
||||
this.updatesAvailable = false;
|
||||
this.latestVersion = undefined;
|
||||
this.error = undefined;
|
||||
|
||||
this.init = this.init.bind(this);
|
||||
this.checkForUpdates = this.checkForUpdates.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* The interval to wait before checking for updates.
|
||||
*/
|
||||
get interval() {
|
||||
return 60 * 1000 * 30;
|
||||
}
|
||||
|
@ -30,34 +35,61 @@ export default class {
|
|||
this.updateInterval = setInterval(this.checkForUpdates, this.interval);
|
||||
}
|
||||
|
||||
update() {
|
||||
// TODO
|
||||
this.updatesAvailable = false;
|
||||
Events.emit('update-check-end');
|
||||
/**
|
||||
* Installs an update.
|
||||
* TODO
|
||||
*/
|
||||
async update() {
|
||||
try {
|
||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
|
||||
this.updatesAvailable = false;
|
||||
this.latestVersion = Globals.version;
|
||||
Events.emit('update-check-end');
|
||||
} catch (err) {
|
||||
this.error = err;
|
||||
this.checkForUpdates();
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for updates.
|
||||
* @return {Promise}
|
||||
*/
|
||||
checkForUpdates() {
|
||||
if (this.updatesAvailable) return;
|
||||
Events.emit('update-check-start');
|
||||
Logger.info('Updater', 'Checking for updates');
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: 'https://rawgit.com/JsSucks/BetterDiscordApp/master/package.json',
|
||||
cache: false,
|
||||
success: e => {
|
||||
try {
|
||||
Events.emit('update-check-end');
|
||||
Logger.info('Updater',
|
||||
`Latest Version: ${e.version} - Current Version: ${Globals.version}`);
|
||||
if (e.version !== Globals.version) {
|
||||
this.updatesAvailable = true;
|
||||
Events.emit('updates-available');
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this.updatesAvailable) return resolve(true);
|
||||
Events.emit('update-check-start');
|
||||
Logger.info('Updater', 'Checking for updates');
|
||||
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: 'https://rawgit.com/JsSucks/BetterDiscordApp/master/package.json',
|
||||
cache: false,
|
||||
success: e => {
|
||||
try {
|
||||
this.latestVersion = e.version;
|
||||
Events.emit('update-check-end');
|
||||
Logger.info('Updater', `Latest Version: ${e.version} - Current Version: ${Globals.version}`);
|
||||
|
||||
if (this.latestVersion !== Globals.version) {
|
||||
this.updatesAvailable = true;
|
||||
Events.emit('updates-available');
|
||||
resolve(true);
|
||||
}
|
||||
|
||||
resolve(false);
|
||||
} catch (err) {
|
||||
Events.emit('update-check-fail', err);
|
||||
reject(err);
|
||||
}
|
||||
} catch (err) {
|
||||
},
|
||||
fail: err => {
|
||||
Events.emit('update-check-fail', err);
|
||||
reject(err);
|
||||
}
|
||||
},
|
||||
fail: e => Events.emit('update-check-fail', e)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -11,29 +11,36 @@
|
|||
import { WebpackModules } from './webpackmodules';
|
||||
import jQuery from 'jquery';
|
||||
import lodash from 'lodash';
|
||||
import Vue from 'vue';
|
||||
|
||||
export { jQuery as $ };
|
||||
|
||||
export default class {
|
||||
|
||||
static get jQuery() {
|
||||
return jQuery;
|
||||
}
|
||||
/**
|
||||
* jQuery
|
||||
*/
|
||||
static get jQuery() { return jQuery }
|
||||
static get $() { return this.jQuery }
|
||||
|
||||
static get $() {
|
||||
return this.jQuery;
|
||||
}
|
||||
|
||||
static get lodash() {
|
||||
return lodash;
|
||||
}
|
||||
|
||||
static get _() {
|
||||
return this.lodash;
|
||||
}
|
||||
/**
|
||||
* Lodash
|
||||
*/
|
||||
static get lodash() { return lodash }
|
||||
static get _() { return this.lodash }
|
||||
|
||||
/**
|
||||
* Moment
|
||||
*/
|
||||
static get moment() {
|
||||
return WebpackModules.getModuleByName('Moment');
|
||||
}
|
||||
|
||||
/**
|
||||
* Vue
|
||||
*/
|
||||
static get Vue() {
|
||||
return Vue;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -51,6 +51,8 @@ const KnownModules = {
|
|||
React: Filters.byProperties(['createElement', 'cloneElement']),
|
||||
ReactDOM: Filters.byProperties(['render', 'findDOMNode']),
|
||||
|
||||
Events: Filters.byPrototypeFields(['setMaxListeners', 'emit']),
|
||||
|
||||
/* Guild Info, Stores, and Utilities */
|
||||
GuildStore: Filters.byProperties(['getGuild']),
|
||||
SortedGuildStore: Filters.byProperties(['getSortedGuilds']),
|
||||
|
@ -89,7 +91,6 @@ const KnownModules = {
|
|||
UserActivityStore: Filters.byProperties(['getActivity']),
|
||||
UserNameResolver: Filters.byProperties(['getName']),
|
||||
|
||||
|
||||
/* Emoji Store and Utils */
|
||||
EmojiInfo: Filters.byProperties(['isEmojiDisabled']),
|
||||
EmojiUtils: Filters.byProperties(['getGuildEmoji']),
|
||||
|
@ -100,7 +101,6 @@ const KnownModules = {
|
|||
InviteResolver: Filters.byProperties(['findInvite']),
|
||||
InviteActions: Filters.byProperties(['acceptInvite']),
|
||||
|
||||
|
||||
/* Discord Objects & Utils */
|
||||
DiscordConstants: Filters.byProperties(["Permissions", "ActivityTypes", "StatusTypes"]),
|
||||
Permissions: Filters.byProperties(['getHighestRole']),
|
||||
|
@ -126,7 +126,6 @@ const KnownModules = {
|
|||
ExperimentsManager: Filters.byProperties(['isDeveloper']),
|
||||
CurrentExperiment: Filters.byProperties(['getExperimentId']),
|
||||
|
||||
|
||||
/* Images, Avatars and Utils */
|
||||
ImageResolver: Filters.byProperties(["getUserAvatarURL"]),
|
||||
ImageUtils: Filters.byProperties(['getSizedImageSrc']),
|
||||
|
@ -180,7 +179,6 @@ const KnownModules = {
|
|||
URLParser: Filters.byProperties(['Url', 'parse']),
|
||||
ExtraURLs: Filters.byProperties(['getArticleURL']),
|
||||
|
||||
|
||||
/* DOM/React Components */
|
||||
/* ==================== */
|
||||
UserSettingsWindow: Filters.byProperties(['open', 'updateAccount']),
|
||||
|
@ -207,37 +205,37 @@ const KnownModules = {
|
|||
|
||||
export class WebpackModules {
|
||||
|
||||
/**
|
||||
* Finds a module using a filter function.
|
||||
* @param {Function} filter A function to use to filter modules
|
||||
* @param {Boolean} first Whether to return only the first matching module
|
||||
* @return {Any}
|
||||
*/
|
||||
static getModule(filter, first = true) {
|
||||
const modules = this.getAllModules();
|
||||
const rm = [];
|
||||
for (let index in modules) {
|
||||
if (!modules.hasOwnProperty(index)) continue;
|
||||
const module = modules[index];
|
||||
const { exports } = module;
|
||||
let foundModule = null;
|
||||
/**
|
||||
* Finds a module using a filter function.
|
||||
* @param {Function} filter A function to use to filter modules
|
||||
* @param {Boolean} first Whether to return only the first matching module
|
||||
* @return {Any}
|
||||
*/
|
||||
static getModule(filter, first = true) {
|
||||
const modules = this.getAllModules();
|
||||
const rm = [];
|
||||
for (let index in modules) {
|
||||
if (!modules.hasOwnProperty(index)) continue;
|
||||
const module = modules[index];
|
||||
const { exports } = module;
|
||||
let foundModule = null;
|
||||
|
||||
if (!exports) continue;
|
||||
if (exports.__esModule && exports.default && filter(exports.default)) foundModule = exports.default;
|
||||
if (filter(exports)) foundModule = exports;
|
||||
if (!foundModule) continue;
|
||||
if (first) return foundModule;
|
||||
rm.push(foundModule);
|
||||
}
|
||||
return first || rm.length == 0 ? undefined : rm;
|
||||
}
|
||||
if (!exports) continue;
|
||||
if (exports.__esModule && exports.default && filter(exports.default)) foundModule = exports.default;
|
||||
if (filter(exports)) foundModule = exports;
|
||||
if (!foundModule) continue;
|
||||
if (first) return foundModule;
|
||||
rm.push(foundModule);
|
||||
}
|
||||
return first || rm.length == 0 ? undefined : rm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a module by it's name.
|
||||
* @param {String} name The name of the module
|
||||
* @param {Function} fallback A function to use to filter modules if not finding a known module
|
||||
* @return {Any}
|
||||
*/
|
||||
/**
|
||||
* Finds a module by it's name.
|
||||
* @param {String} name The name of the module
|
||||
* @param {Function} fallback A function to use to filter modules if not finding a known module
|
||||
* @return {Any}
|
||||
*/
|
||||
static getModuleByName(name, fallback) {
|
||||
if (Cache.hasOwnProperty(name)) return Cache[name];
|
||||
if (KnownModules.hasOwnProperty(name)) fallback = KnownModules[name];
|
||||
|
@ -246,48 +244,48 @@ export class WebpackModules {
|
|||
return module ? Cache[name] = module : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a module by it's display name.
|
||||
* @param {String} name The display name of the module
|
||||
* @return {Any}
|
||||
*/
|
||||
/**
|
||||
* Finds a module by it's display name.
|
||||
* @param {String} name The display name of the module
|
||||
* @return {Any}
|
||||
*/
|
||||
static getModuleByDisplayName(name) {
|
||||
return this.getModule(Filters.byDisplayName(name), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a module using it's code.
|
||||
* @param {RegEx} regex A regular expression to use to filter modules
|
||||
* @param {Boolean} first Whether to return the only the first matching module
|
||||
* @return {Any}
|
||||
*/
|
||||
/**
|
||||
* Finds a module using it's code.
|
||||
* @param {RegEx} regex A regular expression to use to filter modules
|
||||
* @param {Boolean} first Whether to return the only the first matching module
|
||||
* @return {Any}
|
||||
*/
|
||||
static getModuleByRegex(regex, first = true) {
|
||||
return this.getModule(Filters.byCode(regex), first);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a module using properties on it's prototype.
|
||||
* @param {Array} props Properties to use to filter modules
|
||||
* @param {Boolean} first Whether to return only the first matching module
|
||||
* @return {Any}
|
||||
*/
|
||||
/**
|
||||
* Finds a module using properties on it's prototype.
|
||||
* @param {Array} props Properties to use to filter modules
|
||||
* @param {Boolean} first Whether to return only the first matching module
|
||||
* @return {Any}
|
||||
*/
|
||||
static getModuleByPrototypes(prototypes, first = true) {
|
||||
return this.getModule(Filters.byPrototypeFields(prototypes), first);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a module using it's own properties.
|
||||
* @param {Array} props Properties to use to filter modules
|
||||
* @param {Boolean} first Whether to return only the first matching module
|
||||
* @return {Any}
|
||||
*/
|
||||
/**
|
||||
* Finds a module using it's own properties.
|
||||
* @param {Array} props Properties to use to filter modules
|
||||
* @param {Boolean} first Whether to return only the first matching module
|
||||
* @return {Any}
|
||||
*/
|
||||
static getModuleByProps(props, first = true) {
|
||||
return this.getModule(Filters.byProperties(props), first);
|
||||
}
|
||||
|
||||
/**
|
||||
* Discord's __webpack_require__ function.
|
||||
*/
|
||||
/**
|
||||
* Discord's __webpack_require__ function.
|
||||
*/
|
||||
static get require() {
|
||||
if (this._require) return this._require;
|
||||
const id = 'bd-webpackmodules';
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
export default class {
|
||||
|
||||
}
|
|
@ -17,22 +17,37 @@ export default class ErrorEvent extends Event {
|
|||
this.showStack = false; // For error modal
|
||||
}
|
||||
|
||||
/**
|
||||
* The module the error occured in.
|
||||
*/
|
||||
get module() {
|
||||
return this.args.module;
|
||||
}
|
||||
|
||||
/**
|
||||
* A message describing the error.
|
||||
*/
|
||||
get message() {
|
||||
return this.args.message;
|
||||
}
|
||||
|
||||
/**
|
||||
* The original error object.
|
||||
*/
|
||||
get err() {
|
||||
return this.args.err;
|
||||
}
|
||||
|
||||
/**
|
||||
* A trace showing which functions were called when the error occured.
|
||||
*/
|
||||
get stackTrace() {
|
||||
return this.err.stack;
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of event.
|
||||
*/
|
||||
get __eventType() {
|
||||
return 'error';
|
||||
}
|
||||
|
|
|
@ -17,14 +17,23 @@ export default class Event {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* An object containing information about the event.
|
||||
*/
|
||||
get event() {
|
||||
return this.__eventInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* The first argument that was passed to the constructor, which contains information about the event.
|
||||
*/
|
||||
get args() {
|
||||
return this.event.args[0];
|
||||
}
|
||||
|
||||
get __eventType() { return null; }
|
||||
/**
|
||||
* The type of event.
|
||||
*/
|
||||
get __eventType() { return undefined; }
|
||||
|
||||
}
|
||||
|
|
|
@ -12,10 +12,16 @@ import Event from './event';
|
|||
|
||||
export default class SettingsUpdatedEvent extends Event {
|
||||
|
||||
/**
|
||||
* An array of SettingUpdated events.
|
||||
*/
|
||||
get updatedSettings() {
|
||||
return this.args.updatedSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of event.
|
||||
*/
|
||||
get __eventType() {
|
||||
return 'settings-updated';
|
||||
}
|
||||
|
|
|
@ -12,38 +12,65 @@ import Event from './event';
|
|||
|
||||
export default class SettingUpdatedEvent extends Event {
|
||||
|
||||
/**
|
||||
* The set containing the setting that was updated.
|
||||
*/
|
||||
get set() {
|
||||
return this.args.set;
|
||||
}
|
||||
|
||||
/**
|
||||
* The ID of the set containing the setting that was updated.
|
||||
*/
|
||||
get set_id() {
|
||||
return this.args.set.id;
|
||||
return this.set.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* The category containing the setting that was updated.
|
||||
*/
|
||||
get category() {
|
||||
return this.args.category;
|
||||
}
|
||||
|
||||
/**
|
||||
* The ID of the category containing the setting that was updated.
|
||||
*/
|
||||
get category_id() {
|
||||
return this.args.category.id;
|
||||
return this.category.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* The setting that was updated.
|
||||
*/
|
||||
get setting() {
|
||||
return this.args.setting;
|
||||
}
|
||||
|
||||
/**
|
||||
* The ID of the setting that was updated.
|
||||
*/
|
||||
get setting_id() {
|
||||
return this.args.setting.id;
|
||||
return this.setting.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* The setting's new value.
|
||||
*/
|
||||
get value() {
|
||||
return this.args.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* The setting's old value.
|
||||
*/
|
||||
get old_value() {
|
||||
return this.args.old_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of event.
|
||||
*/
|
||||
get __eventType() {
|
||||
return 'setting-updated';
|
||||
}
|
||||
|
|
|
@ -2,3 +2,4 @@ export { default as SettingsSet } from './settingsset';
|
|||
export { default as SettingsCategory } from './settingscategory';
|
||||
export { default as Setting } from './setting';
|
||||
export { default as SettingsScheme } from './settingsscheme';
|
||||
export * from './types';
|
||||
|
|
|
@ -14,22 +14,29 @@ export default class MultipleChoiceOption {
|
|||
|
||||
constructor(args) {
|
||||
this.args = args.args || args;
|
||||
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* This option's ID.
|
||||
*/
|
||||
get id() {
|
||||
return this.args.id || this.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* A string describing this option.
|
||||
*/
|
||||
get text() {
|
||||
return this.args.text;
|
||||
}
|
||||
|
||||
/**
|
||||
* The value to return when this option is active.
|
||||
*/
|
||||
get value() {
|
||||
return this.args.value;
|
||||
}
|
||||
|
||||
clone() {
|
||||
return new MultipleChoiceOption(Utils.deepclone(this.args));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -15,35 +15,49 @@ export default class SettingsScheme {
|
|||
constructor(args) {
|
||||
this.args = args.args || args;
|
||||
|
||||
this.args.settings = this.settings.map(({ category, settings }) => ({
|
||||
category, settings: settings.map(({ id, value }) => ({
|
||||
id, value
|
||||
}))
|
||||
}));
|
||||
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* The scheme's ID.
|
||||
*/
|
||||
get id() {
|
||||
return this.args.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* The URL of the scheme's icon. This should be a base64 encoded data URI.
|
||||
*/
|
||||
get icon_url() {
|
||||
return this.args.icon_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* The scheme's name.
|
||||
*/
|
||||
get name() {
|
||||
return this.args.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* A string to be displayed under the scheme.
|
||||
*/
|
||||
get hint() {
|
||||
return this.args.hint;
|
||||
}
|
||||
|
||||
/**
|
||||
* An array of stripped settings categories this scheme manages.
|
||||
*/
|
||||
get settings() {
|
||||
return this.args.settings || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this scheme's values are currently applied to a set.
|
||||
* @param {SettingsSet} set The set to check
|
||||
* @return {Boolean}
|
||||
*/
|
||||
isActive(set) {
|
||||
for (let schemeCategory of this.settings) {
|
||||
const category = set.categories.find(c => c.category === schemeCategory.category);
|
||||
|
@ -66,12 +80,13 @@ export default class SettingsScheme {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies this scheme's values to a set.
|
||||
* @param {SettingsSet} set The set to merge this scheme's values into
|
||||
* @return {Promise}
|
||||
*/
|
||||
applyTo(set) {
|
||||
return set.merge({ settings: this.settings });
|
||||
}
|
||||
|
||||
clone() {
|
||||
return new SettingsScheme(Utils.deepclone(this.args));
|
||||
return set.merge(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -446,7 +446,7 @@ export default class SettingsSet {
|
|||
text: this.text,
|
||||
headertext: this.headertext,
|
||||
settings: this.categories.map(category => category.clone()),
|
||||
schemes: this.schemes.map(scheme => scheme.clone())
|
||||
schemes: this.schemes
|
||||
}, ...merge);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
export { default as BoolSetting } from './bool';
|
||||
export { default as StringSetting } from './text';
|
||||
export { default as NumberSetting } from './number';
|
||||
export { default as DropdownSetting } from './dropdown';
|
||||
export { default as RadioSetting } from './radio';
|
||||
export { default as SliderSetting } from './slider';
|
||||
export { default as ColourSetting } from './colour';
|
||||
export { default as KeybindSetting } from './keybind';
|
||||
export { default as FileSetting } from './file';
|
||||
export { default as GuildSetting } from './guild';
|
||||
export { default as ArraySetting } from './array';
|
||||
export { default as CustomSetting } from './custom';
|
|
@ -11,18 +11,42 @@
|
|||
import Setting from './basesetting';
|
||||
import Combokeys from 'combokeys';
|
||||
|
||||
let keybindsPaused = false;
|
||||
|
||||
export default class KeybindSetting extends Setting {
|
||||
|
||||
constructor(args, ...merge) {
|
||||
super(args, ...merge);
|
||||
|
||||
this.__keybind_activated = this.__keybind_activated.bind(this);
|
||||
|
||||
this.combokeys = new Combokeys(document);
|
||||
this.combokeys.bind(this.value, event => this.emit('keybind-activated', event));
|
||||
this.combokeys.bind(this.value, this.__keybind_activated);
|
||||
}
|
||||
|
||||
/**
|
||||
* The value to use when the setting doesn't have a value.
|
||||
*/
|
||||
get defaultValue() {
|
||||
return '';
|
||||
}
|
||||
|
||||
setValueHook() {
|
||||
this.combokeys.reset();
|
||||
this.combokeys.bind(this.value, event => this.emit('keybind-activated', event));
|
||||
this.combokeys.bind(this.value, this.__keybind_activated);
|
||||
}
|
||||
|
||||
__keybind_activated(event) {
|
||||
if (KeybindSetting.paused) return;
|
||||
this.emit('keybind-activated', event);
|
||||
}
|
||||
|
||||
static get paused() {
|
||||
return keybindsPaused;
|
||||
}
|
||||
|
||||
static set paused(paused) {
|
||||
keybindsPaused = paused;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -25,16 +25,21 @@
|
|||
width: 16px;
|
||||
filter: brightness(10);
|
||||
cursor: pointer;
|
||||
.theme-light [class*="topSectionNormal-"] & {
|
||||
background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iQ2FscXVlXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjAwMCAyMDAwIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCAyMDAwIDIwMDAiIHhtbDpzcGFjZT0icHJlc2VydmUiPjxnPjxwYXRoIGZpbGw9IiMzRTgyRTUiIGQ9Ik0xNDAyLjIsNjMxLjdjLTkuNy0zNTMuNC0yODYuMi00OTYtNjQyLjYtNDk2SDY4LjR2NzE0LjFsNDQyLDM5OFY0OTAuN2gyNTdjMjc0LjUsMCwyNzQuNSwzNDQuOSwwLDM0NC45SDU5Ny42djMyOS41aDE2OS44YzI3NC41LDAsMjc0LjUsMzQ0LjgsMCwzNDQuOGgtNjk5djM1NC45aDY5MS4yYzM1Ni4zLDAsNjMyLjgtMTQyLjYsNjQyLjYtNDk2YzAtMTYyLjYtNDQuNS0yODQuMS0xMjIuOS0zNjguNkMxMzU3LjcsOTE1LjgsMTQwMi4yLDc5NC4zLDE0MDIuMiw2MzEuN3oiLz48cGF0aCBmaWxsPSIjYmJiYmJiIiBkPSJNMTI2Mi41LDEzNS4yTDEyNjIuNSwxMzUuMmwtNzYuOCwwYzI2LjYsMTMuMyw1MS43LDI4LjEsNzUsNDQuM2M3MC43LDQ5LjEsMTI2LjEsMTExLjUsMTY0LjYsMTg1LjNjMzkuOSw3Ni42LDYxLjUsMTY1LjYsNjQuMywyNjQuNmwwLDEuMnYxLjJjMCwxNDEuMSwwLDU5Ni4xLDAsNzM3LjF2MS4ybDAsMS4yYy0yLjcsOTktMjQuMywxODgtNjQuMywyNjQuNmMtMzguNSw3My44LTkzLjgsMTM2LjItMTY0LjYsMTg1LjNjLTIyLjYsMTUuNy00Ni45LDMwLjEtNzIuNiw0My4xaDcyLjVjMzQ2LjIsMS45LDY3MS0xNzEuMiw2NzEtNTY3LjlWNzE2LjdDMTkzMy41LDMxMi4yLDE2MDguNywxMzUuMiwxMjYyLjUsMTM1LjJ6Ii8+PC9nPjwvc3ZnPg==');
|
||||
filter: none;
|
||||
}
|
||||
}
|
||||
|
||||
.theme-light [class*="topSectionNormal-"] .bd-profile-badge-developer,
|
||||
.theme-light [class*="topSectionNormal-"] .bd-profile-badge-contributor,
|
||||
.theme-light .bd-message-badge-developer,
|
||||
.theme-light .bd-message-badge-contributor {
|
||||
background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iQ2FscXVlXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjAwMCAyMDAwIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCAyMDAwIDIwMDAiIHhtbDpzcGFjZT0icHJlc2VydmUiPjxnPjxwYXRoIGZpbGw9IiMzRTgyRTUiIGQ9Ik0xNDAyLjIsNjMxLjdjLTkuNy0zNTMuNC0yODYuMi00OTYtNjQyLjYtNDk2SDY4LjR2NzE0LjFsNDQyLDM5OFY0OTAuN2gyNTdjMjc0LjUsMCwyNzQuNSwzNDQuOSwwLDM0NC45SDU5Ny42djMyOS41aDE2OS44YzI3NC41LDAsMjc0LjUsMzQ0LjgsMCwzNDQuOGgtNjk5djM1NC45aDY5MS4yYzM1Ni4zLDAsNjMyLjgtMTQyLjYsNjQyLjYtNDk2YzAtMTYyLjYtNDQuNS0yODQuMS0xMjIuOS0zNjguNkMxMzU3LjcsOTE1LjgsMTQwMi4yLDc5NC4zLDE0MDIuMiw2MzEuN3oiLz48cGF0aCBmaWxsPSIjYmJiYmJiIiBkPSJNMTI2Mi41LDEzNS4yTDEyNjIuNSwxMzUuMmwtNzYuOCwwYzI2LjYsMTMuMyw1MS43LDI4LjEsNzUsNDQuM2M3MC43LDQ5LjEsMTI2LjEsMTExLjUsMTY0LjYsMTg1LjNjMzkuOSw3Ni42LDYxLjUsMTY1LjYsNjQuMywyNjQuNmwwLDEuMnYxLjJjMCwxNDEuMSwwLDU5Ni4xLDAsNzM3LjF2MS4ybDAsMS4yYy0yLjcsOTktMjQuMywxODgtNjQuMywyNjQuNmMtMzguNSw3My44LTkzLjgsMTM2LjItMTY0LjYsMTg1LjNjLTIyLjYsMTUuNy00Ni45LDMwLjEtNzIuNiw0My4xaDcyLjVjMzQ2LjIsMS45LDY3MS0xNzEuMiw2NzEtNTY3LjlWNzE2LjdDMTkzMy41LDMxMi4yLDE2MDguNywxMzUuMiwxMjYyLjUsMTM1LjJ6Ii8+PC9nPjwvc3ZnPg==');
|
||||
filter: none;
|
||||
}
|
||||
|
||||
.bd-message-badges-wrap {
|
||||
display: inline-block;
|
||||
margin-left: 6px;
|
||||
height: 11px;
|
||||
|
||||
.bd-message-badge-developer,
|
||||
.bd-message-badge-contributor {
|
||||
width: 12px;
|
||||
|
|
|
@ -52,12 +52,12 @@
|
|||
|
||||
.bd-settings-button-btn {
|
||||
background-image: $logoBigBw;
|
||||
background-size: 100% 100%;
|
||||
filter: none;
|
||||
opacity: 1;
|
||||
width: 130px;
|
||||
height: 80px;
|
||||
background-size: 100% 100%;
|
||||
margin-left: 20px;
|
||||
height: 43px;
|
||||
margin: 18px 0 17px 20px;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,3 +4,4 @@
|
|||
@import './card.scss';
|
||||
@import './tooltips.scss';
|
||||
@import './settings-schemes.scss';
|
||||
@import './updater.scss';
|
||||
|
|
|
@ -19,18 +19,53 @@ bd-tooltips {
|
|||
position: absolute;
|
||||
word-wrap: break-word;
|
||||
z-index: 9001;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.bd-tooltip:after {
|
||||
border: 5px solid transparent;
|
||||
content: " ";
|
||||
height: 0;
|
||||
pointer-events: none;
|
||||
width: 0;
|
||||
border-top-color: #000;
|
||||
left: 50%;
|
||||
margin-left: -5px;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
.bd-tooltip-arrow {
|
||||
border: 5px solid transparent;
|
||||
content: " ";
|
||||
height: 0;
|
||||
pointer-events: none;
|
||||
width: 0;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
&[x-placement^="top"] {
|
||||
margin-bottom: 10px;
|
||||
|
||||
.bd-tooltip-arrow {
|
||||
margin-left: -5px;
|
||||
border-top-color: #000;
|
||||
bottom: -10px;
|
||||
}
|
||||
}
|
||||
|
||||
&[x-placement^="bottom"] {
|
||||
margin-top: 10px;
|
||||
|
||||
.bd-tooltip-arrow {
|
||||
border-width: 0 5px 5px 5px;
|
||||
top: -5px;
|
||||
border-bottom-color: #000;
|
||||
}
|
||||
}
|
||||
|
||||
&[x-placement^="right"] {
|
||||
margin-left: 10px;
|
||||
|
||||
.bd-tooltip-arrow {
|
||||
border-width: 5px 5px 5px 0;
|
||||
left: -5px;
|
||||
border-right-color: #000;
|
||||
}
|
||||
}
|
||||
|
||||
&[x-placement^="left"] {
|
||||
margin-right: 10px;
|
||||
|
||||
.bd-tooltip-arrow {
|
||||
border-width: 5px 0 5px 5px;
|
||||
right: -5px;
|
||||
border-left-color: #000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
.bd-updaterview {
|
||||
p {
|
||||
margin: 0 0 10px;
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
|
@ -8,37 +8,14 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { Events, WebpackModules, EventListener, ReactComponents, Renderer } from 'modules';
|
||||
import { Events, WebpackModules, EventListener, DiscordApi, ReactComponents, Renderer } from 'modules';
|
||||
import { ClientLogger as Logger } from 'common';
|
||||
import Reflection from './reflection';
|
||||
import DOM from './dom';
|
||||
import VueInjector from './vueinjector';
|
||||
import EditedTimeStamp from './components/common/EditedTimestamp.vue';
|
||||
import Autocomplete from './components/common/Autocomplete.vue';
|
||||
|
||||
class TempApi {
|
||||
static get currentGuildId() {
|
||||
try {
|
||||
return WebpackModules.getModuleByName('SelectedGuildStore').getGuildId();
|
||||
} catch (err) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
static get currentChannelId() {
|
||||
try {
|
||||
return WebpackModules.getModuleByName('SelectedChannelStore').getChannelId();
|
||||
} catch (err) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
static get currentUserId() {
|
||||
try {
|
||||
return WebpackModules.getModuleByName('UserStore').getCurrentUser().id;
|
||||
} catch (err) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default class extends EventListener {
|
||||
|
||||
constructor(args) {
|
||||
|
@ -54,22 +31,19 @@ export default class extends EventListener {
|
|||
}
|
||||
|
||||
get eventBindings() {
|
||||
return [{ id: 'gkh:keyup', callback: this.injectAutocomplete }];
|
||||
/*
|
||||
return [
|
||||
{ id: 'server-switch', callback: this.manipAll },
|
||||
{ id: 'channel-switch', callback: this.manipAll },
|
||||
{ id: 'discord:MESSAGE_CREATE', callback: this.markupInjector },
|
||||
{ id: 'discord:MESSAGE_UPDATE', callback: this.markupInjector },
|
||||
// { id: 'server-switch', callback: this.manipAll },
|
||||
// { id: 'channel-switch', callback: this.manipAll },
|
||||
// { id: 'discord:MESSAGE_CREATE', callback: this.markupInjector },
|
||||
// { id: 'discord:MESSAGE_UPDATE', callback: this.markupInjector },
|
||||
{ id: 'gkh:keyup', callback: this.injectAutocomplete }
|
||||
];
|
||||
*/
|
||||
}
|
||||
|
||||
manipAll() {
|
||||
try {
|
||||
this.appMount.setAttribute('guild-id', TempApi.currentGuildId);
|
||||
this.appMount.setAttribute('channel-id', TempApi.currentChannelId);
|
||||
this.appMount.setAttribute('guild-id', DiscordApi.currentGuild.id);
|
||||
this.appMount.setAttribute('channel-id', DiscordApi.currentChannel.id);
|
||||
this.setIds();
|
||||
this.makeMutable();
|
||||
} catch (err) {
|
||||
|
@ -128,13 +102,11 @@ export default class extends EventListener {
|
|||
if (markup.ets) {
|
||||
const etsRoot = document.createElement('span');
|
||||
markup.clone.appendChild(etsRoot);
|
||||
VueInjector.inject(
|
||||
etsRoot,
|
||||
DOM.createElement('span', null, 'test'),
|
||||
{ EditedTimeStamp },
|
||||
`<EditedTimeStamp ets="${markup.ets}"/>`,
|
||||
true
|
||||
);
|
||||
VueInjector.inject(etsRoot, {
|
||||
components: { EditedTimeStamp },
|
||||
data: { ets: markup.ets },
|
||||
template: '<EditedTimeStamp :ets="ets" />'
|
||||
});
|
||||
}
|
||||
|
||||
Events.emit('ui:mutable:.markup', markup.clone);
|
||||
|
@ -174,14 +146,14 @@ export default class extends EventListener {
|
|||
const userTest = Reflection(msgGroup).prop('user');
|
||||
if (!userTest) return;
|
||||
msgGroup.setAttribute('data-author-id', userTest.id);
|
||||
if (userTest.id === TempApi.currentUserId) msgGroup.setAttribute('data-currentuser', true);
|
||||
if (userTest.id === DiscordApi.currentUserId) msgGroup.setAttribute('data-currentuser', true);
|
||||
return;
|
||||
}
|
||||
msg.setAttribute('data-message-id', messageid);
|
||||
const msgGroup = msg.closest('.message-group');
|
||||
if (!msgGroup) return;
|
||||
msgGroup.setAttribute('data-author-id', authorid);
|
||||
if (authorid === TempApi.currentUserId) msgGroup.setAttribute('data-currentuser', true);
|
||||
if (authorid === DiscordApi.currentUser.id) msgGroup.setAttribute('data-currentuser', true);
|
||||
}
|
||||
|
||||
setUserId(user) {
|
||||
|
@ -189,7 +161,7 @@ export default class extends EventListener {
|
|||
const userid = Reflection(user).prop('user.id');
|
||||
if (!userid) return;
|
||||
user.setAttribute('data-user-id', userid);
|
||||
const currentUser = userid === TempApi.currentUserId;
|
||||
const currentUser = userid === DiscordApi.currentUser.id;
|
||||
if (currentUser) user.setAttribute('data-currentuser', true);
|
||||
Events.emit('ui:useridset', user);
|
||||
}
|
||||
|
@ -214,12 +186,11 @@ export default class extends EventListener {
|
|||
const parent = document.querySelector('[class*="channelTextArea"] > [class*="inner"]');
|
||||
if (!parent) return;
|
||||
parent.append(root);
|
||||
VueInjector.inject(
|
||||
root,
|
||||
DOM.createElement('span'),
|
||||
{ Autocomplete },
|
||||
`<Autocomplete initial="${e.target.value}"/>`,
|
||||
true
|
||||
);
|
||||
VueInjector.inject(root, {
|
||||
components: { Autocomplete },
|
||||
data: { initial: e.target.value },
|
||||
template: '<Autocomplete :initial="initial" />'
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -8,17 +8,22 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { Events } from 'modules';
|
||||
import { Utils } from 'common';
|
||||
|
||||
let items = 0;
|
||||
|
||||
const BdMenuItems = new class {
|
||||
export const BdMenuItems = new class {
|
||||
|
||||
constructor() {
|
||||
window.bdmenu = this;
|
||||
|
||||
this.items = [];
|
||||
|
||||
const updater = this.add({category: 'Updates', contentid: 'updater', text: 'Updates available!', hidden: true});
|
||||
Events.on('update-check-end', () => updater.hidden = true);
|
||||
Events.on('updates-available', () => updater.hidden = false);
|
||||
|
||||
this.addSettingsSet('Internal', 'core', 'Core');
|
||||
this.addSettingsSet('Internal', 'ui', 'UI');
|
||||
this.addSettingsSet('Internal', 'emotes', 'Emotes');
|
||||
|
@ -28,7 +33,14 @@ const BdMenuItems = new class {
|
|||
this.add({category: 'External', contentid: 'themes', text: 'Themes'});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an item to the menu.
|
||||
* @param {Object} item The item to add to the menu
|
||||
* @return {Object}
|
||||
*/
|
||||
add(item) {
|
||||
if (this.items.includes(item)) return item;
|
||||
|
||||
item.id = items++;
|
||||
item.contentid = item.contentid || (items++ + '');
|
||||
item.active = false;
|
||||
|
@ -39,6 +51,13 @@ const BdMenuItems = new class {
|
|||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a settings set to the menu.
|
||||
* @param {String} category The category to display this item under
|
||||
* @param {SettingsSet} set The settings set to display when this item is active
|
||||
* @param {String} text The text to display in the menu (optional)
|
||||
* @return {Object} The item that was added
|
||||
*/
|
||||
addSettingsSet(category, set, text) {
|
||||
return this.add({
|
||||
category, set,
|
||||
|
@ -46,16 +65,25 @@ const BdMenuItems = new class {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a Vue component to the menu.
|
||||
* @param {String} category The category to display this item under
|
||||
* @param {String} text The text to display in the menu
|
||||
* @param {Object} component The Vue component to display when this item is active
|
||||
* @return {Object} The item that was added
|
||||
*/
|
||||
addVueComponent(category, text, component) {
|
||||
return this.add({
|
||||
category, text, component
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an item from the menu.
|
||||
* @param {Object} item The item to remove from the menu
|
||||
*/
|
||||
remove(item) {
|
||||
Utils.removeFromArray(this.items, item);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
export { BdMenuItems };
|
||||
|
|
|
@ -8,48 +8,21 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { Events, WebpackModules, DiscordApi } from 'modules';
|
||||
import { Utils } from 'common';
|
||||
import { remote } from 'electron';
|
||||
import DOM from './dom';
|
||||
import Vue from './vue';
|
||||
import { BdSettingsWrapper } from './components';
|
||||
import BdModals from './components/bd/BdModals.vue';
|
||||
import { Events, WebpackModules } from 'modules';
|
||||
import { Utils } from 'common';
|
||||
import AutoManip from './automanip';
|
||||
import { remote } from 'electron';
|
||||
|
||||
class TempApi {
|
||||
static get currentGuild() {
|
||||
try {
|
||||
const currentGuildId = WebpackModules.getModuleByName('SelectedGuildStore').getGuildId();
|
||||
return WebpackModules.getModuleByName('GuildStore').getGuild(currentGuildId);
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
static get currentChannel() {
|
||||
try {
|
||||
const currentChannelId = WebpackModules.getModuleByName('SelectedChannelStore').getChannelId();
|
||||
return WebpackModules.getModuleByName('ChannelStore').getChannel(currentChannelId);
|
||||
} catch (err) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
static get currentUserId() {
|
||||
try {
|
||||
return WebpackModules.getModuleByName('UserStore').getCurrentUser().id;
|
||||
} catch (err) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
import { BdSettingsWrapper, BdModals } from './components';
|
||||
|
||||
export default class {
|
||||
|
||||
static initUiEvents() {
|
||||
this.pathCache = {
|
||||
isDm: null,
|
||||
server: TempApi.currentGuild,
|
||||
channel: TempApi.currentChannel
|
||||
server: DiscordApi.currentGuild,
|
||||
channel: DiscordApi.currentChannel
|
||||
};
|
||||
window.addEventListener('keyup', e => Events.emit('gkh:keyup', e));
|
||||
this.autoManip = new AutoManip();
|
||||
|
@ -67,9 +40,9 @@ export default class {
|
|||
if (!remote.BrowserWindow.getFocusedWindow()) return;
|
||||
clearInterval(ehookInterval);
|
||||
remote.BrowserWindow.getFocusedWindow().webContents.on('did-navigate-in-page', (e, url, isMainFrame) => {
|
||||
const { currentGuild, currentChannel } = TempApi;
|
||||
const { currentGuild, currentChannel } = DiscordApi;
|
||||
if (!this.pathCache.server) {
|
||||
Events.emit('server-switch', { 'server': currentGuild, 'channel': currentChannel });
|
||||
Events.emit('server-switch', { server: currentGuild, channel: currentChannel });
|
||||
this.pathCache.server = currentGuild;
|
||||
this.pathCache.channel = currentChannel;
|
||||
return;
|
||||
|
@ -84,7 +57,7 @@ export default class {
|
|||
currentGuild.id &&
|
||||
this.pathCache.server &&
|
||||
this.pathCache.server.id !== currentGuild.id) {
|
||||
Events.emit('server-switch', { 'server': currentGuild, 'channel': currentChannel });
|
||||
Events.emit('server-switch', { server: currentGuild, channel: currentChannel });
|
||||
this.pathCache.server = currentGuild;
|
||||
this.pathCache.channel = currentChannel;
|
||||
return;
|
||||
|
@ -110,19 +83,19 @@ export default class {
|
|||
DOM.createElement('div', null, 'bd-modals').appendTo(DOM.bdModals);
|
||||
DOM.createElement('bd-tooltips').appendTo(DOM.bdBody);
|
||||
|
||||
const modals = new Vue({
|
||||
this.modals = new Vue({
|
||||
el: '#bd-modals',
|
||||
components: { BdModals },
|
||||
template: '<BdModals/>'
|
||||
template: '<BdModals />'
|
||||
});
|
||||
|
||||
const vueInstance = new Vue({
|
||||
this.vueInstance = new Vue({
|
||||
el: '#bd-settings',
|
||||
components: { BdSettingsWrapper },
|
||||
template: '<BdSettingsWrapper/>'
|
||||
template: '<BdSettingsWrapper />'
|
||||
});
|
||||
|
||||
return vueInstance;
|
||||
return this.vueInstance;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -19,13 +19,14 @@
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// Imports
|
||||
import { Events } from 'modules';
|
||||
import { Modals } from 'ui';
|
||||
import { Modal } from '../common';
|
||||
import { MiError } from '../common/MaterialIcon';
|
||||
import ErrorModal from './modals/ErrorModal.vue';
|
||||
import { Modal } from './common';
|
||||
import { MiError } from './common/MaterialIcon';
|
||||
import ErrorModal from './bd/modals/ErrorModal.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
|
@ -49,6 +49,7 @@
|
|||
<CssEditorView v-if="item.contentid === 'css'" />
|
||||
<PluginsView v-if="item.contentid === 'plugins'" />
|
||||
<ThemesView v-if="item.contentid === 'themes'" />
|
||||
<UpdaterView v-if="item.contentid === 'updater'" />
|
||||
</div>
|
||||
</ContentColumn>
|
||||
</SidebarView>
|
||||
|
@ -60,7 +61,7 @@
|
|||
import { Settings } from 'modules';
|
||||
import { BdMenuItems } from 'ui';
|
||||
import { SidebarView, Sidebar, SidebarItem, ContentColumn } from './sidebar';
|
||||
import { SettingsWrapper, SettingsPanel, CssEditorView, PluginsView, ThemesView } from './bd';
|
||||
import { SettingsWrapper, SettingsPanel, CssEditorView, PluginsView, ThemesView, UpdaterView } from './bd';
|
||||
import { SvgX, MiGithubCircle, MiWeb, MiClose, MiTwitterCircle } from './common';
|
||||
|
||||
export default {
|
||||
|
@ -79,7 +80,7 @@
|
|||
props: ['active', 'close'],
|
||||
components: {
|
||||
SidebarView, Sidebar, SidebarItem, ContentColumn,
|
||||
SettingsWrapper, SettingsPanel, CssEditorView, PluginsView, ThemesView,
|
||||
SettingsWrapper, SettingsPanel, CssEditorView, PluginsView, ThemesView, UpdaterView,
|
||||
MiGithubCircle, MiWeb, MiClose, MiTwitterCircle
|
||||
},
|
||||
computed: {
|
||||
|
@ -131,7 +132,7 @@
|
|||
this.timeout = setTimeout(() => {
|
||||
this.animating = false;
|
||||
this.lastActiveIndex = -1;
|
||||
this.timeout = null;
|
||||
this.timeout = null;
|
||||
}, 400);
|
||||
},
|
||||
openGithub() {
|
||||
|
|
|
@ -9,11 +9,11 @@
|
|||
*/
|
||||
|
||||
<template>
|
||||
<div :class="{'bd-profile-badges-wrap': hasBadges == 'false'}">
|
||||
<div :class="{'bd-profile-badges-wrap': !hasBadges}">
|
||||
<div class="bd-profile-badges">
|
||||
<div v-if="developer == 'true'" v-tooltip="'BetterDiscord Developer'" class="bd-profile-badge bd-profile-badge-developer" @click="onClick"></div>
|
||||
<div v-else-if="webdev == 'true'" v-tooltip="'BetterDiscord Web Developer'" class="bd-profile-badge bd-profile-badge-developer" @click="onClick"></div>
|
||||
<div v-else-if="contributor == 'true'" v-tooltip="'BetterDiscord Contributor'" class="bd-profile-badge bd-profile-badge-contributor" @click="onClick"></div>
|
||||
<div v-if="developer" v-tooltip="'BetterDiscord Developer'" class="bd-profile-badge bd-profile-badge-developer" @click="onClick"></div>
|
||||
<div v-else-if="webdev" v-tooltip="'BetterDiscord Web Developer'" class="bd-profile-badge bd-profile-badge-developer" @click="onClick"></div>
|
||||
<div v-else-if="contributor" v-tooltip="'BetterDiscord Contributor'" class="bd-profile-badge bd-profile-badge-contributor" @click="onClick"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -10,9 +10,9 @@
|
|||
|
||||
<template>
|
||||
<div class="bd-message-badges-wrap">
|
||||
<div v-if="developer == 'true'" v-tooltip="'BetterDiscord Developer'" class="bd-message-badge bd-message-badge-developer" @click="onClick"></div>
|
||||
<div v-else-if="webdev == 'true'" v-tooltip="'BetterDiscord Web Developer'" class="bd-message-badge bd-message-badge-developer" @click="onClick"></div>
|
||||
<div v-else-if="contributor == 'true'" v-tooltip="'BetterDiscord Contributor'" class="bd-message-badge bd-message-badge-contributor" @click="onClick"></div>
|
||||
<div v-if="developer" v-tooltip="'BetterDiscord Developer'" class="bd-message-badge bd-message-badge-developer" @click="onClick"></div>
|
||||
<div v-else-if="webdev" v-tooltip="'BetterDiscord Web Developer'" class="bd-message-badge bd-message-badge-developer" @click="onClick"></div>
|
||||
<div v-else-if="contributor" v-tooltip="'BetterDiscord Contributor'" class="bd-message-badge bd-message-badge-contributor" @click="onClick"></div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
|
|
|
@ -11,9 +11,9 @@
|
|||
<template>
|
||||
<SettingsWrapper headertext="CSS Editor">
|
||||
<div class="bd-css-editor">
|
||||
<div v-if="CssEditor.error" class="bd-form-item">
|
||||
<div v-if="error" class="bd-form-item">
|
||||
<h5 style="margin-bottom: 10px;">Compiler error</h5>
|
||||
<div class="bd-err bd-pre-wrap"><div class="bd-pre">{{ CssEditor.error.formatted }}</div></div>
|
||||
<div class="bd-err bd-pre-wrap"><div class="bd-pre">{{ error.formatted }}</div></div>
|
||||
<div class="bd-form-divider"></div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -21,16 +21,12 @@
|
|||
</template>
|
||||
<script>
|
||||
// Imports
|
||||
import { ClientLogger as Logger } from 'common';
|
||||
import { shell } from 'electron';
|
||||
import Card from './Card.vue';
|
||||
import { Button, ButtonGroup, SettingSwitch, MiSettings, MiRefresh, MiPencil, MiDelete, MiExtension } from '../common';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
settingsOpen: false
|
||||
}
|
||||
},
|
||||
props: ['plugin', 'togglePlugin', 'reloadPlugin', 'deletePlugin', 'showSettings'],
|
||||
components: {
|
||||
Card, Button, ButtonGroup, SettingSwitch, MiSettings, MiRefresh, MiPencil, MiDelete, MiExtension
|
||||
|
@ -38,9 +34,9 @@
|
|||
methods: {
|
||||
editPlugin() {
|
||||
try {
|
||||
shell.openItem(this.plugin.pluginPath);
|
||||
shell.openItem(this.plugin.contentPath);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
Logger.err('PluginCard', [`Error opening plugin directory ${this.plugin.contentPath}:`, err]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,17 +37,19 @@
|
|||
// Imports
|
||||
import { PluginManager } from 'modules';
|
||||
import { Modals } from 'ui';
|
||||
import { SettingsWrapper } from './';
|
||||
import PluginCard from './PluginCard.vue';
|
||||
import { ClientLogger as Logger } from 'common';
|
||||
import { MiRefresh } from '../common';
|
||||
import SettingsWrapper from './SettingsWrapper.vue';
|
||||
import PluginCard from './PluginCard.vue';
|
||||
import RefreshBtn from '../common/RefreshBtn.vue';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
PluginManager,
|
||||
local: true,
|
||||
localPlugins: PluginManager.localPlugins
|
||||
}
|
||||
};
|
||||
},
|
||||
components: {
|
||||
SettingsWrapper, PluginCard,
|
||||
|
@ -62,32 +64,32 @@
|
|||
this.local = false;
|
||||
},
|
||||
async refreshLocal() {
|
||||
await PluginManager.refreshPlugins();
|
||||
await this.PluginManager.refreshPlugins();
|
||||
},
|
||||
async refreshOnline() {
|
||||
|
||||
// TODO
|
||||
},
|
||||
async togglePlugin(plugin) {
|
||||
// TODO Display error if plugin fails to start/stop
|
||||
// TODO: display error if plugin fails to start/stop
|
||||
const enabled = plugin.enabled;
|
||||
try {
|
||||
await plugin.enabled ? PluginManager.stopPlugin(plugin) : PluginManager.startPlugin(plugin);
|
||||
await enabled ? this.PluginManager.stopPlugin(plugin) : this.PluginManager.startPlugin(plugin);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
Logger.err('PluginsView', [`Error ${enabled ? 'stopp' : 'start'}ing plugin ${plugin.name}:`, err]);
|
||||
}
|
||||
},
|
||||
async reloadPlugin(plugin) {
|
||||
try {
|
||||
await PluginManager.reloadPlugin(plugin);
|
||||
await this.PluginManager.reloadPlugin(plugin);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
Logger.err('PluginsView', [`Error reloading plugin ${plugin.name}:`, err]);
|
||||
}
|
||||
},
|
||||
async deletePlugin(plugin, unload) {
|
||||
try {
|
||||
if (unload) await PluginManager.unloadPlugin(plugin);
|
||||
else await PluginManager.deletePlugin(plugin);
|
||||
await unload ? this.PluginManager.unloadPlugin(plugin) : this.PluginManager.deletePlugin(plugin);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
Logger.err('PluginsView', [`Error ${unload ? 'unload' : 'delet'}ing plugin ${plugin.name}:`, err]);
|
||||
}
|
||||
},
|
||||
showSettings(plugin, dont_clone) {
|
||||
|
|
|
@ -26,11 +26,6 @@
|
|||
import { Button, ButtonGroup, SettingSwitch, MiSettings, MiRefresh, MiPencil, MiDelete, MiExtension } from '../common';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
settingsOpen: false
|
||||
}
|
||||
},
|
||||
props: ['theme', 'toggleTheme', 'reloadTheme', 'deleteTheme', 'showSettings'],
|
||||
components: {
|
||||
Card, Button, ButtonGroup, SettingSwitch, MiSettings, MiRefresh, MiPencil, MiDelete, MiExtension
|
||||
|
@ -40,7 +35,7 @@
|
|||
try {
|
||||
shell.openItem(this.theme.themePath);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
Logger.err('ThemeCard', [`Error opening theme directory ${this.theme.contentPath}:`, err]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,17 +37,19 @@
|
|||
// Imports
|
||||
import { ThemeManager } from 'modules';
|
||||
import { Modals } from 'ui';
|
||||
import { SettingsWrapper } from './';
|
||||
import { ClientLogger as Logger } from 'common';
|
||||
import { MiRefresh } from '../common';
|
||||
import SettingsWrapper from './SettingsWrapper.vue';
|
||||
import ThemeCard from './ThemeCard.vue';
|
||||
import RefreshBtn from '../common/RefreshBtn.vue';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
ThemeManager,
|
||||
local: true,
|
||||
localThemes: ThemeManager.localThemes
|
||||
}
|
||||
};
|
||||
},
|
||||
components: {
|
||||
SettingsWrapper, ThemeCard,
|
||||
|
@ -62,33 +64,31 @@
|
|||
this.local = false;
|
||||
},
|
||||
async refreshLocal() {
|
||||
await ThemeManager.refreshThemes();
|
||||
await this.ThemeManager.refreshThemes();
|
||||
},
|
||||
async refreshOnline() {
|
||||
|
||||
// TODO
|
||||
},
|
||||
async toggleTheme(theme) {
|
||||
// TODO Display error if theme fails to enable/disable
|
||||
// TODO: display error if theme fails to enable/disable
|
||||
try {
|
||||
await theme.enabled ? ThemeManager.disableTheme(theme) : ThemeManager.enableTheme(theme);
|
||||
await theme.enabled ? this.ThemeManager.disableTheme(theme) : this.ThemeManager.enableTheme(theme);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
Logger.err('ThemesView', [`Error ${enabled ? 'stopp' : 'start'}ing theme ${theme.name}:`, err]);
|
||||
}
|
||||
},
|
||||
async reloadTheme(theme, reload) {
|
||||
try {
|
||||
if (reload) await ThemeManager.reloadTheme(theme);
|
||||
else await theme.recompile();
|
||||
await reload ? this.ThemeManager.reloadTheme(theme) : theme.recompile();
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
Logger.err('ThemesView', [`Error ${reload ? 'reload' : 'recompil'}ing theme ${theme.name}:`, err]);
|
||||
}
|
||||
},
|
||||
async deleteTheme(theme, unload) {
|
||||
try {
|
||||
if (unload) await ThemeManager.unloadTheme(theme);
|
||||
else await ThemeManager.deleteTheme(theme);
|
||||
await unload ? this.ThemeManager.unloadTheme(theme) : this.ThemeManager.deleteTheme(theme);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
Logger.err('ThemesView', [`Error ${unload ? 'unload' : 'delet'}ing theme ${theme.name}:`, err]);
|
||||
}
|
||||
},
|
||||
showSettings(theme, dont_clone) {
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
/**
|
||||
* BetterDiscord Updater 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="Updates">
|
||||
<div class="bd-flex bd-flex-col bd-updaterview">
|
||||
<div v-if="error" class="bd-form-item">
|
||||
<h5 style="margin-bottom: 10px;">Error installing updates</h5>
|
||||
<div class="bd-err bd-pre-wrap"><div class="bd-pre">{{ error.formatted }}</div></div>
|
||||
<div class="bd-form-divider"></div>
|
||||
</div>
|
||||
|
||||
<template v-if="updatesAvailable">
|
||||
<p>Version {{ newVersion }} is available. You are currently running version {{ currentVersion }}.</p>
|
||||
<FormButton :onClick="install" :loading="updating">Install</FormButton>
|
||||
</template>
|
||||
<template v-else>
|
||||
<p>You're all up to date!</p>
|
||||
</template>
|
||||
</div>
|
||||
</SettingsWrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Globals, Updater } from 'modules';
|
||||
import { ClientLogger as Logger } from 'common';
|
||||
import SettingsWrapper from './SettingsWrapper.vue';
|
||||
import { FormButton } from '../common';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
currentVersion: Globals.version,
|
||||
updating: false,
|
||||
updater: Updater
|
||||
};
|
||||
},
|
||||
components: {
|
||||
SettingsWrapper,
|
||||
FormButton
|
||||
},
|
||||
computed: {
|
||||
updatesAvailable() {
|
||||
return this.updater.updatesAvailable;
|
||||
},
|
||||
newVersion() {
|
||||
return this.updater.latestVersion;
|
||||
},
|
||||
error() {
|
||||
return this.updater.error;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async install() {
|
||||
this.updating = true;
|
||||
try {
|
||||
await this.updater.update();
|
||||
} catch (err) {}
|
||||
this.updating = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -3,5 +3,6 @@ export { default as SettingsPanel } from './SettingsPanel.vue';
|
|||
export { default as CssEditorView } from './CssEditor.vue';
|
||||
export { default as PluginsView } from './PluginsView.vue';
|
||||
export { default as ThemesView } from './ThemesView.vue';
|
||||
export { default as UpdaterView } from './UpdaterView.vue';
|
||||
export { default as BdBadge } from './BdBadge.vue';
|
||||
export { default as BdMessageBadge } from './BdMessageBadge.vue';
|
||||
|
|
|
@ -24,8 +24,9 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { KeybindSetting } from 'structs';
|
||||
import { ClientIPC, ClientLogger as Logger } from 'common';
|
||||
import { shell } from 'electron';
|
||||
import { ClientIPC } from 'common';
|
||||
import Combokeys from 'combokeys';
|
||||
import CombokeysRecord from 'combokeys/plugins/record';
|
||||
|
||||
|
@ -49,6 +50,7 @@
|
|||
},
|
||||
watch: {
|
||||
active(active) {
|
||||
KeybindSetting.paused = active;
|
||||
if (active) combokeys.record(this.recorded);
|
||||
}
|
||||
},
|
||||
|
@ -65,7 +67,7 @@
|
|||
this.active = false;
|
||||
this.recordingValue = undefined;
|
||||
this.setting.value = sequence.join(' ');
|
||||
console.log('keypress', sequence);
|
||||
Logger.log('Keybind', ['Recorded sequence', sequence]);
|
||||
},
|
||||
getDisplayString(value) {
|
||||
if (!value) return;
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
import { EmoteModule } from 'builtin';
|
||||
import { Events } from 'modules';
|
||||
import { DOM } from 'ui';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
|
@ -44,7 +45,7 @@
|
|||
open: false,
|
||||
selectedIndex: 0,
|
||||
sterm: ''
|
||||
}
|
||||
};
|
||||
},
|
||||
props: ['initial'],
|
||||
beforeMount() {
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
export { default as BdSettingsWrapper } from './BdSettingsWrapper.vue';
|
||||
export { default as BdSettings } from './BdSettings.vue';
|
||||
export { default as BdModals } from './BdModals.vue';
|
||||
|
|
|
@ -186,4 +186,5 @@ export default class DOM {
|
|||
node.setAttribute(attribute.name, attribute.value);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -209,7 +209,7 @@ export default class Modals {
|
|||
ThemeManager._errors = [];
|
||||
}
|
||||
|
||||
return modal;
|
||||
return modal;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ import { EventListener } from 'modules';
|
|||
import DOM from './dom';
|
||||
import { BdBadge, BdMessageBadge } from './components/bd';
|
||||
import VueInjector from './vueinjector';
|
||||
import contributors from '../data/contributors';
|
||||
|
||||
export default class extends EventListener {
|
||||
|
||||
|
@ -55,43 +56,39 @@ export default class extends EventListener {
|
|||
if (msgGroup.dataset.hasBadges) return;
|
||||
msgGroup.setAttribute('data-has-badges', true);
|
||||
if (!msgGroup.dataset.authorId) return;
|
||||
const c = this.contributors.find(c => c.id === msgGroup.dataset.authorId);
|
||||
const c = contributors.find(c => c.id === msgGroup.dataset.authorId);
|
||||
if (!c) return;
|
||||
const root = document.createElement('span');
|
||||
const wrapperParent = msgGroup.querySelector('.username-wrapper').parentElement;
|
||||
const usernameWrapper = msgGroup.querySelector('.username-wrapper');
|
||||
if (!usernameWrapper) return;
|
||||
const wrapperParent = usernameWrapper.parentElement;
|
||||
if (!wrapperParent || wrapperParent.children.length < 2) return;
|
||||
wrapperParent.insertBefore(root, wrapperParent.children[1]);
|
||||
const { developer, contributor, webdev } = c;
|
||||
VueInjector.inject(
|
||||
root,
|
||||
DOM.createElement('div', null, 'bdmessagebadges'),
|
||||
{ BdMessageBadge },
|
||||
`<BdMessageBadge developer="${developer}" webdev="${webdev}" contributor="${contributor}"/>`,
|
||||
true
|
||||
);
|
||||
VueInjector.inject(root, {
|
||||
components: { BdMessageBadge },
|
||||
data: { c },
|
||||
template: '<BdMessageBadge :developer="c.developer" :webdev="c.webdev" :contributor="c.contributor" />'
|
||||
});
|
||||
}
|
||||
|
||||
userlistBadge(e) {
|
||||
const c = this.contributors.find(c => c.id === e.dataset.userId);
|
||||
const c = contributors.find(c => c.id === e.dataset.userId);
|
||||
if (!c) return;
|
||||
const memberUsername = e.querySelector('.member-username');
|
||||
if (!memberUsername) return;
|
||||
const root = document.createElement('span');
|
||||
memberUsername.append(root);
|
||||
const { developer, contributor, webdev } = c;
|
||||
VueInjector.inject(
|
||||
root,
|
||||
DOM.createElement('div', null, 'bdmessagebadges'),
|
||||
{ BdMessageBadge },
|
||||
`<BdMessageBadge developer="${developer}" webdev="${webdev}" contributor="${contributor}"/>`,
|
||||
true
|
||||
);
|
||||
VueInjector.inject(root, {
|
||||
components: { BdMessageBadge },
|
||||
data: { c },
|
||||
template: '<BdMessageBadge :developer="c.developer" :webdev="c.webdev" :contributor="c.contributor" />'
|
||||
});
|
||||
}
|
||||
|
||||
inject(userid) {
|
||||
const c = this.contributors.find(c => c.id === userid);
|
||||
const c = contributors.find(c => c.id === userid);
|
||||
if (!c) return;
|
||||
|
||||
|
||||
setTimeout(() => {
|
||||
let hasBadges = false;
|
||||
let root = document.querySelector('[class*="profileBadges"]');
|
||||
|
@ -101,29 +98,16 @@ export default class extends EventListener {
|
|||
root = document.querySelector('[class*="headerInfo"]');
|
||||
}
|
||||
|
||||
const { developer, contributor, webdev } = c;
|
||||
|
||||
VueInjector.inject(
|
||||
root,
|
||||
DOM.createElement('div', null, 'bdprofilebadges'),
|
||||
{ BdBadge },
|
||||
`<BdBadge hasBadges="${hasBadges}" developer="${developer}" webdev="${webdev}" contributor="${contributor}"/>`
|
||||
);
|
||||
VueInjector.inject(root, {
|
||||
components: { BdBadge },
|
||||
data: { hasBadges, c },
|
||||
template: '<BdBadge :hasBadges="hasBadges" :developer="c.developer" :webdev="c.webdev" :contributor="c.contributor" />',
|
||||
}, DOM.createElement('div', null, 'bdprofilebadges'));
|
||||
}, 400);
|
||||
}
|
||||
|
||||
get contributors() {
|
||||
return [
|
||||
{ 'id': '81388395867156480', 'webdev': true, 'developer': true, 'contributor': true }, // Jiiks
|
||||
{ 'id': '98003542823944192', 'webdev': false, 'developer': true, 'contributor': true }, // Pohky
|
||||
{ 'id': '138850472541814784', 'webdev': true, 'developer': false, 'contributor': true }, // Hammock
|
||||
{ 'id': '249746236008169473', 'webdev': false, 'developer': true, 'contributor': true }, // Zerebos
|
||||
{ 'id': '125367412370440192', 'webdev': false, 'developer': true, 'contributor': true }, // Pierce
|
||||
{ 'id': '284056145272766465', 'webdev': false, 'developer': false, 'contributor': true }, // Samuel Elliott
|
||||
{ 'id': '184021060562321419', 'webdev': false, 'developer': false, 'contributor': true }, // Lilian Tedone
|
||||
{ 'id': '76052829285916672', 'webdev': false, 'developer': false, 'contributor': true }, // samfun123
|
||||
{ 'id': '171005991272316937', 'webdev': false, 'developer': false, 'contributor': true }, // samogot
|
||||
];
|
||||
return contributors;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,8 +16,9 @@ Vue.use(VTooltip, {
|
|||
defaultContainer: 'bd-tooltips',
|
||||
defaultClass: 'bd-tooltip',
|
||||
defaultTargetClass: 'bd-has-tooltip',
|
||||
defaultArrowSelector: '.bd-tooltip-arrow',
|
||||
defaultInnerSelector: '.bd-tooltip-inner',
|
||||
defaultTemplate: '<div class="bd-tooltip"><span class="bd-tooltip-inner"></span></div>',
|
||||
defaultTemplate: '<div class="bd-tooltip"><div class="bd-tooltip-arrow"></div><span class="bd-tooltip-inner"></span></div>',
|
||||
defaultBoundariesElement: DOM.getElement('#app-mount')
|
||||
});
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* BetterDiscord VUE Injector Module
|
||||
* BetterDiscord Vue Injector Module
|
||||
* Copyright (c) 2015-present Jiiks/JsSucks - https://github.com/Jiiks / https://github.com/JsSucks
|
||||
* All rights reserved.
|
||||
* https://betterdiscord.net
|
||||
|
@ -11,15 +11,21 @@
|
|||
import Vue from './vue';
|
||||
|
||||
export default class {
|
||||
|
||||
static inject(root, bdnode, components, template, replaceRoot) {
|
||||
if(!replaceRoot) bdnode.appendTo(root);
|
||||
|
||||
return new Vue({
|
||||
el: replaceRoot ? root : bdnode.element,
|
||||
components,
|
||||
template
|
||||
});
|
||||
/**
|
||||
* Creates a new Vue object and mounts it in the passed element.
|
||||
* @param {HTMLElement} root The element to mount the new Vue object at
|
||||
* @param {Object} options Options to pass to Vue
|
||||
* @param {BdNode} bdnode The element to append
|
||||
* @return {Vue}
|
||||
*/
|
||||
static inject(root, options, bdnode) {
|
||||
if(bdnode) bdnode.appendTo(root);
|
||||
|
||||
const vue = new Vue(options);
|
||||
|
||||
vue.$mount(bdnode ? bdnode.element : root);
|
||||
return vue;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
const
|
||||
path = require('path'),
|
||||
webpack = require('webpack');
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
|
||||
const jsLoader = {
|
||||
test: /\.(js|jsx)$/,
|
||||
|
@ -9,18 +8,18 @@ const jsLoader = {
|
|||
query: {
|
||||
presets: ['react']
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const vueLoader = {
|
||||
test: /\.(vue)$/,
|
||||
loader: 'vue-loader'
|
||||
}
|
||||
};
|
||||
|
||||
const scssLoader = {
|
||||
test: /\.scss$/,
|
||||
exclude: /node_modules/,
|
||||
loader: ['css-loader', 'sass-loader']
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
entry: './src/index.js',
|
||||
|
@ -32,9 +31,11 @@ module.exports = {
|
|||
loaders: [jsLoader, vueLoader, scssLoader]
|
||||
},
|
||||
externals: {
|
||||
'electron': 'window.require("electron")',
|
||||
'fs': 'window.require("fs")',
|
||||
'path': 'window.require("path")'
|
||||
electron: 'window.require("electron")',
|
||||
fs: 'window.require("fs")',
|
||||
path: 'window.require("path")',
|
||||
node_utils: 'window.require("util")',
|
||||
sparkplug: 'require("../../core/dist/sparkplug")'
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
|
@ -49,15 +50,10 @@ module.exports = {
|
|||
path.resolve('src', 'structs'),
|
||||
path.resolve('src', 'builtin')
|
||||
]
|
||||
},
|
||||
node: {
|
||||
process: false,
|
||||
__filename: false,
|
||||
__dirname: false
|
||||
}
|
||||
/* resolve: {
|
||||
alias: {
|
||||
'momentjs': 'vendor/moment.min.js'
|
||||
},
|
||||
modules: [
|
||||
path.resolve('./node_modules'),
|
||||
path.resolve(__dirname, '..'),
|
||||
path.resolve(__dirname, '..', 'node_modules')
|
||||
]
|
||||
}*/
|
||||
};
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
|
||||
|
||||
const jsLoader = {
|
||||
test: /\.(js|jsx)$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'babel-loader',
|
||||
query: {
|
||||
presets: ['react']
|
||||
}
|
||||
};
|
||||
|
||||
const vueLoader = {
|
||||
test: /\.(vue)$/,
|
||||
loader: 'vue-loader'
|
||||
};
|
||||
|
||||
const scssLoader = {
|
||||
test: /\.scss$/,
|
||||
exclude: /node_modules/,
|
||||
loader: ['css-loader', 'sass-loader']
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
entry: './src/index.js',
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: 'betterdiscord.client-release.js'
|
||||
},
|
||||
module: {
|
||||
loaders: [jsLoader, vueLoader, scssLoader]
|
||||
},
|
||||
externals: {
|
||||
electron: 'window.require("electron")',
|
||||
fs: 'window.require("fs")',
|
||||
path: 'window.require("path")',
|
||||
node_utils: 'window.require("util")',
|
||||
sparkplug: 'require("./sparkplug")'
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
vue$: path.resolve('..', 'node_modules', 'vue', 'dist', 'vue.esm.js')
|
||||
},
|
||||
modules: [
|
||||
path.resolve('..', 'node_modules'),
|
||||
path.resolve('..', 'common', 'modules'),
|
||||
path.resolve('src', 'modules'),
|
||||
path.resolve('src', 'ui'),
|
||||
path.resolve('src', 'plugins'),
|
||||
path.resolve('src', 'structs'),
|
||||
path.resolve('src', 'builtin')
|
||||
]
|
||||
},
|
||||
node: {
|
||||
process: false,
|
||||
__filename: false,
|
||||
__dirname: false
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
PRODUCTION: JSON.stringify(true)
|
||||
}),
|
||||
new UglifyJsPlugin()
|
||||
]
|
||||
};
|
|
@ -8,51 +8,155 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
const { ipcRenderer, ipcMain } = require('electron');
|
||||
import { ipcRenderer } from 'electron';
|
||||
|
||||
export class ClientIPC {
|
||||
static on(channel, cb) {
|
||||
ipcRenderer.on(channel, (event, message) => cb(event, message));
|
||||
const callbacks = new WeakMap();
|
||||
|
||||
const ClientIPC = new class ClientIPC {
|
||||
|
||||
constructor() {
|
||||
this.on('ping', () => 'pong', true);
|
||||
}
|
||||
|
||||
static async send(channel, message) {
|
||||
/**
|
||||
* Adds an IPC event listener.
|
||||
* @param {String} channel The channel to listen on
|
||||
* @param {Function} callback A function that will be called when a message is received
|
||||
* @param {Boolean} reply Whether to automatically reply to the message with the callback's return value
|
||||
* @return {Promise}
|
||||
*/
|
||||
on(channel, callback, reply) {
|
||||
channel = channel.startsWith('bd-') ? channel : `bd-${channel}`;
|
||||
const __eid = Date.now().toString();
|
||||
ipcRenderer.send(channel, Object.assign(message ? message : {}, { __eid }));
|
||||
|
||||
const boundCallback = async (event, args) => {
|
||||
const ipcevent = new BDIpcEvent(event, args);
|
||||
try {
|
||||
const r = callback(ipcevent, ipcevent.message);
|
||||
if (reply) ipcevent.reply(await r);
|
||||
} catch (err) {
|
||||
console.error('Error in IPC callback:', err);
|
||||
if (reply) ipcevent.reject(err);
|
||||
}
|
||||
};
|
||||
|
||||
callbacks.set(callback, boundCallback);
|
||||
ipcRenderer.on(channel, boundCallback);
|
||||
}
|
||||
|
||||
off(channel, callback) {
|
||||
ipcRenderer.removeListener(channel, callbacks.get(callback));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message to the main process and returns a promise that is resolved when the main process replies.
|
||||
* @param {String} channel The channel to send a message to
|
||||
* @param {Any} message Data to send to the main process
|
||||
* @param {Boolean} error Whether to mark the message as an error
|
||||
* @return {Promise}
|
||||
*/
|
||||
async send(channel, message, error) {
|
||||
channel = channel.startsWith('bd-') ? channel : `bd-${channel}`;
|
||||
|
||||
const eid = 'bd-' + Date.now().toString();
|
||||
ipcRenderer.send(channel, { eid, message, error });
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
ipcRenderer.once(__eid, (event, arg) => {
|
||||
if (arg.err) {
|
||||
return reject(arg.err);
|
||||
}
|
||||
resolve(arg);
|
||||
ipcRenderer.once(eid, (event, arg) => {
|
||||
if (arg.error) reject(arg.message);
|
||||
else resolve(arg.message);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message to the Discord window and returns a promise that is resolved when it replies.
|
||||
* @param {String} channel The channel to send a message to
|
||||
* @param {Any} message Data to send to the renderer process
|
||||
* @return {Promise}
|
||||
*/
|
||||
sendToDiscord(channel, message) {
|
||||
return this.send('bd-sendToDiscord', {
|
||||
channel, message
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message to the CSS editor window and returns a promise that is resolved when it replies.
|
||||
* @param {String} channel The channel to send a message to
|
||||
* @param {Any} message Data to send to the CSS editor window
|
||||
* @return {Promise}
|
||||
*/
|
||||
sendToCssEditor(channel, message) {
|
||||
return this.send('bd-sendToCssEditor', {
|
||||
channel, message
|
||||
});
|
||||
}
|
||||
|
||||
ping() {
|
||||
return this.send('ping');
|
||||
}
|
||||
|
||||
getConfig() {
|
||||
return this.send('getConfig');
|
||||
}
|
||||
|
||||
showOpenDialog(options) {
|
||||
return this.send('native-open', options);
|
||||
}
|
||||
|
||||
compileSass(options) {
|
||||
return this.send('compileSass', options);
|
||||
}
|
||||
|
||||
dba(command) {
|
||||
return this.send('dba', command);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default ClientIPC;
|
||||
|
||||
/**
|
||||
* An IPC event.
|
||||
*/
|
||||
class BDIpcEvent {
|
||||
|
||||
constructor(event, args) {
|
||||
this.args = args;
|
||||
this.ipcEvent = event;
|
||||
this.replied = false;
|
||||
}
|
||||
|
||||
bindings() {
|
||||
this.send = this.send.bind(this);
|
||||
this.reply = this.reply.bind(this);
|
||||
}
|
||||
|
||||
send(message) {
|
||||
this.ipcEvent.sender.send(this.args.__eid, message);
|
||||
/**
|
||||
* Sends a message back to the message's sender.
|
||||
* @param {Any} message Data to send to this message's sender
|
||||
*/
|
||||
get send() { return this.reply }
|
||||
reply(message, error) {
|
||||
if (this.replied)
|
||||
throw {message: 'This message has already been replied to.'};
|
||||
|
||||
this.replied = true;
|
||||
return ClientIPC.send(this.eid, message, error);
|
||||
}
|
||||
|
||||
reply(message) {
|
||||
this.send(message);
|
||||
reject(err) {
|
||||
return this.reply(err, true);
|
||||
}
|
||||
}
|
||||
|
||||
export class CoreIPC {
|
||||
static on(channel, cb) {
|
||||
ipcMain.on(channel, (event, args) => cb(new BDIpcEvent(event, args)));
|
||||
get message() {
|
||||
return this.args.message;
|
||||
}
|
||||
|
||||
get error() {
|
||||
return this.args.error;
|
||||
}
|
||||
|
||||
get eid() {
|
||||
return this.args.eid;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export { ClientIPC } from './bdipc';
|
||||
export { default as ClientIPC } from './bdipc';
|
||||
export * from './utils';
|
||||
export { ClientLogger } from './logger';
|
||||
export { default as AsyncEventEmitter } from './async-eventemitter';
|
||||
|
|
|
@ -9,34 +9,45 @@
|
|||
*/
|
||||
|
||||
import { Vendor } from 'modules';
|
||||
import { FileUtils } from './utils';
|
||||
import node_utils from 'node_utils';
|
||||
|
||||
const logs = [];
|
||||
export const logLevels = {
|
||||
'log': 'log',
|
||||
'warn': 'warn',
|
||||
'err': 'error',
|
||||
'error': 'error',
|
||||
'debug': 'debug',
|
||||
'dbg': 'debug',
|
||||
'info': 'info'
|
||||
};
|
||||
|
||||
export class ClientLogger {
|
||||
export default class Logger {
|
||||
|
||||
static err(module, message) { this.log(module, message, 'err'); }
|
||||
static warn(module, message) { this.log(module, message, 'warn'); }
|
||||
static info(module, message) { this.log(module, message, 'info'); }
|
||||
static dbg(module, message) { this.log(module, message, 'dbg'); }
|
||||
constructor(file) {
|
||||
this.logs = [];
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
static log(module, message, level = 'log') {
|
||||
level = this.parseLevel(level);
|
||||
if (typeof message === 'object' && !(message instanceof Array)) {
|
||||
console[level]('[%cBetter%cDiscord:%s]', 'color: #3E82E5', '', `${module}${level === 'debug' ? '|DBG' : ''}`, message);
|
||||
let message_string = message.toString();
|
||||
if (message_string === '[object Object]')
|
||||
message_string += ' ' + JSON.stringify(message, null, 4);
|
||||
err(module, message) { this.log(module, message, 'err'); }
|
||||
warn(module, message) { this.log(module, message, 'warn'); }
|
||||
info(module, message) { this.log(module, message, 'info'); }
|
||||
dbg(module, message) { this.log(module, message, 'dbg'); }
|
||||
|
||||
logs.push(`${level.toUpperCase()} : [${Vendor.moment().format('DD/MM/YY hh:mm:ss')}|${module}] ${message_string}${message_string === '[object Object]' ? ' ' + JSON.stringify(message, null, 4) : ''}`);
|
||||
return;
|
||||
}
|
||||
log(module, message, level = 'log') {
|
||||
level = Logger.parseLevel(level);
|
||||
|
||||
message = typeof message === 'object' && message instanceof Array ? message : [message];
|
||||
console[level]('[%cBetter%cDiscord:%s]', 'color: #3E82E5', '', `${module}${level === 'debug' ? '|DBG' : ''}`, ...message);
|
||||
logs.push(`${level.toUpperCase()} : [${Vendor.moment().format('DD/MM/YY hh:mm:ss')}|${module}] ${message.join(' ')}`);
|
||||
|
||||
const message_string = message.map(m => typeof m === 'string' ? m : node_utils.inspect(m)).join(' ');
|
||||
this.logs.push(`${level.toUpperCase()} : [${Logger.timestamp}|${module}] ${message_string}`);
|
||||
|
||||
if (this.file)
|
||||
FileUtils.appendToFile(this.file, `${level.toUpperCase()} : [${Logger.timestamp}|${module}] ${message_string}\n`);
|
||||
}
|
||||
|
||||
static logError(err) {
|
||||
logError(err) {
|
||||
if (!err.module && !err.message) {
|
||||
console.log(err);
|
||||
return;
|
||||
|
@ -44,24 +55,14 @@ export class ClientLogger {
|
|||
this.err(err.module, err.message);
|
||||
}
|
||||
|
||||
static get logs() {
|
||||
return logs;
|
||||
}
|
||||
|
||||
static get levels() {
|
||||
return {
|
||||
'log': 'log',
|
||||
'warn': 'warn',
|
||||
'err': 'error',
|
||||
'error': 'error',
|
||||
'debug': 'debug',
|
||||
'dbg': 'debug',
|
||||
'info': 'info'
|
||||
};
|
||||
}
|
||||
|
||||
static parseLevel(level) {
|
||||
return this.levels.hasOwnProperty(level) ? this.levels[level] : 'log';
|
||||
return logLevels.hasOwnProperty(level) ? logLevels[level] : 'log';
|
||||
}
|
||||
|
||||
static get timestamp() {
|
||||
return Vendor.moment().format('DD/MM/YY hh:mm:ss');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const ClientLogger = new Logger();
|
||||
|
|
|
@ -8,19 +8,13 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
const
|
||||
path = require('path'),
|
||||
fs = require('fs'),
|
||||
_ = require('lodash');
|
||||
|
||||
import { PatchedFunction, Patch } from './monkeypatch';
|
||||
import { Vendor } from 'modules';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import _ from 'lodash';
|
||||
import filetype from 'file-type';
|
||||
|
||||
export class Utils {
|
||||
static isArrowFunction(fn) {
|
||||
return !fn.toString().startsWith('function');
|
||||
}
|
||||
static overload(fn, cb) {
|
||||
const orig = fn;
|
||||
return function (...args) {
|
||||
|
@ -31,6 +25,10 @@ export class Utils {
|
|||
|
||||
/**
|
||||
* Monkey-patches an object's method.
|
||||
* @param {Object} object The object containing the function to monkey patch
|
||||
* @param {String} methodName The name of the method to monkey patch
|
||||
* @param {Object|String|Function} options Options to pass to the Patch constructor
|
||||
* @param {Function} function If {options} is either "before" or "after", this function will be used as that hook
|
||||
*/
|
||||
static monkeyPatch(object, methodName, options, f) {
|
||||
const patchedFunction = new PatchedFunction(object, methodName);
|
||||
|
@ -41,12 +39,31 @@ export class Utils {
|
|||
|
||||
/**
|
||||
* Monkey-patches an object's method and returns a promise that will be resolved with the data object when the method is called.
|
||||
* You will have to call data.callOriginalMethod() if it wants the original method to be called.
|
||||
* This can only be used to get the arguments and return data. If you want to change anything, call Utils.monkeyPatch with the once option set to true.
|
||||
*/
|
||||
static monkeyPatchOnce(object, methodName) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.monkeyPatch(object, methodName, 'after', data => {
|
||||
data.patch.cancel();
|
||||
resolve(data);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Monkey-patches an object's method and returns a promise that will be resolved with the data object when the method is called.
|
||||
* You will have to call data.callOriginalMethod() if you wants the original method to be called.
|
||||
*/
|
||||
static monkeyPatchAsync(object, methodName, callback) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.monkeyPatch(object, methodName, data => {
|
||||
data.patch.cancel();
|
||||
|
||||
data.promise = data.return = callback ? Promise.all(callback.call(global, data, ...data.arguments)) : new Promise((resolve, reject) => {
|
||||
data.resolve = resolve;
|
||||
data.reject = reject;
|
||||
});
|
||||
|
||||
resolve(data);
|
||||
});
|
||||
});
|
||||
|
@ -81,15 +98,20 @@ export class Utils {
|
|||
};
|
||||
|
||||
const patch = this.monkeyPatch(what, methodName, {
|
||||
before: before ? compatible_function(before) : undefined,
|
||||
before: !instead && before ? compatible_function(before) : undefined,
|
||||
instead: instead ? compatible_function(instead) : undefined,
|
||||
after: after ? compatible_function(after) : undefined,
|
||||
after: !instead && after ? compatible_function(after) : undefined,
|
||||
once
|
||||
});
|
||||
|
||||
return cancelPatch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to parse a string as JSON.
|
||||
* @param {String} json The string to parse
|
||||
* @return {Any}
|
||||
*/
|
||||
static async tryParseJson(jsonString) {
|
||||
try {
|
||||
return JSON.parse(jsonString);
|
||||
|
@ -101,6 +123,11 @@ export class Utils {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new object with normalised keys.
|
||||
* @param {Object} object
|
||||
* @return {Object}
|
||||
*/
|
||||
static toCamelCase(o) {
|
||||
const camelCased = {};
|
||||
_.forEach(o, (value, key) => {
|
||||
|
@ -112,17 +139,20 @@ export class Utils {
|
|||
return camelCased;
|
||||
}
|
||||
|
||||
static compare(value1, value2) {
|
||||
/**
|
||||
* Checks if two or more values contain the same data.
|
||||
* @param {Any} ...value The value to compare
|
||||
* @return {Boolean}
|
||||
*/
|
||||
static compare(value1, value2, ...values) {
|
||||
// Check to see if value1 and value2 contain the same data
|
||||
if (typeof value1 !== typeof value2) return false;
|
||||
if (value1 === null && value2 === null) return true;
|
||||
if (value1 === null || value2 === null) return false;
|
||||
|
||||
if (typeof value1 === 'object' || typeof value1 === 'array') {
|
||||
if (typeof value1 === 'object') {
|
||||
// Loop through the object and check if everything's the same
|
||||
let value1array = typeof value1 === 'array' ? value1 : Object.keys(value1);
|
||||
let value2array = typeof value2 === 'array' ? value2 : Object.keys(value2);
|
||||
if (value1array.length !== value2array.length) return false;
|
||||
if (Object.keys(value1).length !== Object.keys(value2).length) return false;
|
||||
|
||||
for (let key in value1) {
|
||||
if (!this.compare(value1[key], value2[key])) return false;
|
||||
|
@ -130,9 +160,20 @@ export class Utils {
|
|||
} else if (value1 !== value2) return false;
|
||||
|
||||
// value1 and value2 contain the same data
|
||||
// Check any more values
|
||||
for (let value3 of values) {
|
||||
if (!this.compare(value1, value3))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones an object and all it's properties.
|
||||
* @param {Any} value The value to clone
|
||||
* @return {Any} The cloned value
|
||||
*/
|
||||
static deepclone(value) {
|
||||
if (typeof value === 'object') {
|
||||
if (value instanceof Array) return value.map(i => this.deepclone(i));
|
||||
|
@ -149,6 +190,11 @@ export class Utils {
|
|||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Freezes an object and all it's properties.
|
||||
* @param {Any} object The object to freeze
|
||||
* @param {Function} exclude A function to filter object that shouldn't be frozen
|
||||
*/
|
||||
static deepfreeze(object, exclude) {
|
||||
if (exclude && exclude(object)) return;
|
||||
|
||||
|
@ -165,38 +211,57 @@ export class Utils {
|
|||
return object;
|
||||
}
|
||||
|
||||
static filterArray(array, filter) {
|
||||
const indexes = [];
|
||||
for (let index in array) {
|
||||
if (!filter(array[index], index))
|
||||
indexes.push(index);
|
||||
}
|
||||
|
||||
for (let i in indexes)
|
||||
array.splice(indexes[i] - i, 1);
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an item from an array. This differs from Array.prototype.filter as it mutates the original array instead of creating a new one.
|
||||
* @param {Array} array The array to filter
|
||||
* @param {Any} item The item to remove from the array
|
||||
* @return {Array}
|
||||
*/
|
||||
static removeFromArray(array, item) {
|
||||
let index;
|
||||
while ((index = array.indexOf(item)) > -1)
|
||||
array.splice(index, 1);
|
||||
return array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a property with a getter that can be changed like a normal property.
|
||||
* @param {Object} object The object to define a property on
|
||||
* @param {String} property The property to define
|
||||
* @param {Function} getter The property's getter
|
||||
* @return {Object}
|
||||
*/
|
||||
static defineSoftGetter(object, property, get) {
|
||||
return Object.defineProperty(object, property, {
|
||||
get,
|
||||
set: value => Object.defineProperty(object, property, {
|
||||
value,
|
||||
writable: true,
|
||||
configurable: true,
|
||||
enumerable: true
|
||||
}),
|
||||
configurable: true,
|
||||
enumerable: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class FileUtils {
|
||||
/**
|
||||
* Checks if a file exists and is a file.
|
||||
* @param {String} path The file's path
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async fileExists(path) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.stat(path, (err, stats) => {
|
||||
if (err) return reject({
|
||||
'message': `No such file or directory: ${err.path}`,
|
||||
message: `No such file or directory: ${err.path}`,
|
||||
err
|
||||
});
|
||||
|
||||
if (!stats.isFile()) return reject({
|
||||
'message': `Not a file: ${path}`,
|
||||
message: `Not a file: ${path}`,
|
||||
stats
|
||||
});
|
||||
|
||||
|
@ -205,16 +270,21 @@ export class FileUtils {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a directory exists and is a directory.
|
||||
* @param {String} path The directory's path
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async directoryExists(path) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.stat(path, (err, stats) => {
|
||||
if (err) return reject({
|
||||
'message': `Directory does not exist: ${path}`,
|
||||
message: `Directory does not exist: ${path}`,
|
||||
err
|
||||
});
|
||||
|
||||
if (!stats.isDirectory()) return reject({
|
||||
'message': `Not a directory: ${path}`,
|
||||
message: `Not a directory: ${path}`,
|
||||
stats
|
||||
});
|
||||
|
||||
|
@ -223,18 +293,25 @@ export class FileUtils {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a directory.
|
||||
* @param {String} path The directory's path
|
||||
* @return {Promise}
|
||||
*/
|
||||
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();
|
||||
if (err) reject(err);
|
||||
else resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a directory exists and creates it if it doesn't.
|
||||
* @param {String} path The directory's path
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async ensureDirectory(path) {
|
||||
try {
|
||||
await this.directoryExists(path);
|
||||
|
@ -249,17 +326,22 @@ export class FileUtils {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the contents of a file.
|
||||
* @param {String} path The file's path
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async readFile(path) {
|
||||
try {
|
||||
await this.fileExists(path);
|
||||
} catch (err) {
|
||||
throw (err);
|
||||
throw err;
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.readFile(path, 'utf-8', (err, data) => {
|
||||
if (err) reject({
|
||||
'message': `Could not read file: ${path}`,
|
||||
if (err) return reject({
|
||||
message: `Could not read file: ${path}`,
|
||||
err
|
||||
});
|
||||
|
||||
|
@ -268,24 +350,62 @@ export class FileUtils {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the contents of a file.
|
||||
* @param {String} path The file's path
|
||||
* @param {Object} options Additional options to pass to fs.readFile
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async readFileBuffer(path, options) {
|
||||
try {
|
||||
await this.fileExists(path);
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.readFile(path, options || {}, (err, data) => {
|
||||
if (err) return reject(err);
|
||||
resolve(data);
|
||||
if (err) reject(err);
|
||||
else resolve(data);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes to a file.
|
||||
* @param {String} path The file's path
|
||||
* @param {String} data The file's new contents
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async writeFile(path, data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.writeFile(path, data, err => {
|
||||
if (err) return reject(err);
|
||||
resolve();
|
||||
if (err) reject(err);
|
||||
else resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes to the end of a file.
|
||||
* @param {String} path The file's path
|
||||
* @param {String} data The data to append to the file
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async appendToFile(path, data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.appendFile(path, data, err => {
|
||||
if (err) reject(err);
|
||||
else resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the contents of a file parsed as JSON.
|
||||
* @param {String} path The file's path
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async readJsonFromFile(path) {
|
||||
let readFile;
|
||||
try {
|
||||
|
@ -295,41 +415,57 @@ export class FileUtils {
|
|||
}
|
||||
|
||||
try {
|
||||
const parsed = await Utils.tryParseJson(readFile);
|
||||
return parsed;
|
||||
return await Utils.tryParseJson(readFile);
|
||||
} catch (err) {
|
||||
throw (Object.assign(err, { path }));
|
||||
throw Object.assign(err, { path });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes to a file as JSON.
|
||||
* @param {String} path The file's path
|
||||
* @param {Any} data The file's new contents
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async writeJsonToFile(path, json) {
|
||||
return this.writeFile(path, JSON.stringify(json));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of items in a directory.
|
||||
* @param {String} path The directory's path
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async listDirectory(path) {
|
||||
try {
|
||||
await this.directoryExists(path);
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.readdir(path, (err, files) => {
|
||||
if (err) return reject(err);
|
||||
resolve(files);
|
||||
});
|
||||
await this.directoryExists(path);
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.readdir(path, (err, files) => {
|
||||
if (err) reject(err);
|
||||
else resolve(files);
|
||||
});
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static async readDir(path) {
|
||||
return this.listDirectory(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a file or buffer's MIME type and typical file extension.
|
||||
* @param {String|Buffer} buffer A buffer or the path of a file
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async getFileType(buffer) {
|
||||
if (typeof buffer === 'string') buffer = await this.readFileBuffer(buffer);
|
||||
|
||||
return filetype(buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a file's contents as a data URI.
|
||||
* @param {String} path The directory's path
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async toDataURI(buffer, type) {
|
||||
if (typeof buffer === 'string') buffer = await this.readFileBuffer(buffer);
|
||||
if (!type) type = this.getFileType(buffer).mime;
|
||||
|
|
|
@ -7,4 +7,4 @@
|
|||
"debug": true
|
||||
}]
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ const
|
|||
plumber = require('gulp-plumber'),
|
||||
watch = require('gulp-watch');
|
||||
|
||||
const task_babel = function () {
|
||||
const task_build = function () {
|
||||
return pump([
|
||||
gulp.src('src/**/*js'),
|
||||
plumber(),
|
||||
|
@ -14,7 +14,7 @@ const task_babel = function () {
|
|||
]);
|
||||
}
|
||||
|
||||
const watch_babel = function () {
|
||||
const task_watch = function () {
|
||||
return pump([
|
||||
watch('src/**/*js'),
|
||||
plumber(),
|
||||
|
@ -23,5 +23,5 @@ const watch_babel = function () {
|
|||
]);
|
||||
}
|
||||
|
||||
gulp.task('build', task_babel);
|
||||
gulp.task('watch', watch_babel);
|
||||
gulp.task('build', task_build);
|
||||
gulp.task('watch', task_watch);
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
module.exports = require('./main.js');
|
|
@ -5,21 +5,18 @@
|
|||
"version": "2.0.0b",
|
||||
"homepage": "https://betterdiscord.net",
|
||||
"license": "MIT",
|
||||
"main": "index.js",
|
||||
"main": "dist/main.js",
|
||||
"contributors": [
|
||||
"Jiiks",
|
||||
"Pohky"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Jiiks/BetterDiscordApp.git"
|
||||
"url": "https://github.com/JsSucks/BetterDiscordApp.git"
|
||||
},
|
||||
"private": false,
|
||||
"devDependencies": {
|
||||
|
||||
},
|
||||
"scripts": {
|
||||
"build": "gulp build",
|
||||
"watch": "gulp watch"
|
||||
"watch": "gulp watch"
|
||||
}
|
||||
}
|
||||
|
|
196
core/src/main.js
196
core/src/main.js
|
@ -10,48 +10,47 @@
|
|||
|
||||
const path = require('path');
|
||||
const sass = require('node-sass');
|
||||
|
||||
const { FileUtils, BDIpc, Config, WindowUtils, CSSEditor, Database } = require('./modules');
|
||||
const { BrowserWindow, dialog } = require('electron');
|
||||
|
||||
const tests = true;
|
||||
const _basePath = __dirname;
|
||||
const { FileUtils, BDIpc, Config, WindowUtils, CSSEditor, Database } = require('./modules');
|
||||
|
||||
const tests = typeof PRODUCTION === 'undefined';
|
||||
|
||||
const _basePath = tests ? path.resolve(__dirname, '..', '..') : __dirname;
|
||||
const _baseDataPath = tests ? path.resolve(_basePath, 'tests') : _basePath;
|
||||
|
||||
const sparkplug = path.resolve(__dirname, 'sparkplug.js');
|
||||
|
||||
const _clientScript = tests
|
||||
? path.resolve(__dirname, '..', '..', 'client', 'dist', 'betterdiscord.client.js')
|
||||
: path.resolve(__dirname, 'betterdiscord.client.js');
|
||||
const _dataPath = tests
|
||||
? path.resolve(__dirname, '..', '..', 'tests', 'data')
|
||||
: path.resolve(__dirname, 'data');
|
||||
const _extPath = tests
|
||||
? path.resolve(__dirname, '..', '..', 'tests', 'ext')
|
||||
: path.resolve(__dirname, 'ext');
|
||||
const _pluginPath = path.resolve(_extPath, 'plugins');
|
||||
const _themePath = path.resolve(_extPath, 'themes');
|
||||
const _modulePath = path.resolve(_extPath, 'modules');
|
||||
? path.resolve(_basePath, 'client', 'dist', 'betterdiscord.client.js')
|
||||
: path.resolve(_basePath, 'betterdiscord.client.js');
|
||||
const _cssEditorPath = tests
|
||||
? path.resolve(__dirname, '..', '..', 'csseditor', 'dist')
|
||||
: path.resolve(__dirname, 'csseditor');
|
||||
|
||||
const _dataPath = path.resolve(_baseDataPath, 'data');
|
||||
const _extPath = path.resolve(_baseDataPath, 'ext');
|
||||
const _pluginPath = path.resolve(_extPath, 'plugins');
|
||||
const _themePath = path.resolve(_extPath, 'themes');
|
||||
const _modulePath = path.resolve(_extPath, 'modules');
|
||||
|
||||
const version = require(path.resolve(_basePath, 'package.json')).version;
|
||||
|
||||
const paths = [
|
||||
{ id: 'base', path: _basePath.replace(/\\/g, '/') },
|
||||
{ id: 'cs', path: _clientScript.replace(/\\/g, '/') },
|
||||
{ id: 'data', path: _dataPath.replace(/\\/g, '/') },
|
||||
{ id: 'ext', path: _extPath.replace(/\\/g, '/') },
|
||||
{ id: 'plugins', path: _pluginPath.replace(/\\/g, '/') },
|
||||
{ id: 'themes', path: _themePath.replace(/\\/g, '/') },
|
||||
{ id: 'modules', path: _modulePath.replace(/\\/g, '/') },
|
||||
{ id: 'csseditor', path: _cssEditorPath.replace(/\\/g, '/') }
|
||||
{ id: 'base', path: _basePath },
|
||||
{ id: 'cs', path: _clientScript },
|
||||
{ id: 'data', path: _dataPath },
|
||||
{ id: 'ext', path: _extPath },
|
||||
{ id: 'plugins', path: _pluginPath },
|
||||
{ id: 'themes', path: _themePath },
|
||||
{ id: 'modules', path: _modulePath },
|
||||
{ id: 'csseditor', path: _cssEditorPath }
|
||||
];
|
||||
|
||||
const sparkplug = path.resolve(__dirname, 'sparkplug.js').replace(/\\/g, '/');
|
||||
|
||||
const Common = {};
|
||||
const globals = {
|
||||
version: '2.0.0a',
|
||||
version,
|
||||
paths
|
||||
}
|
||||
|
||||
const dbInstance = new Database(paths.find(path => path.id === 'data').path);
|
||||
};
|
||||
|
||||
class Comms {
|
||||
|
||||
|
@ -61,67 +60,48 @@ class Comms {
|
|||
}
|
||||
|
||||
initListeners() {
|
||||
BDIpc.on('bd-getConfig', o => {
|
||||
o.reply(Common.Config.config);
|
||||
});
|
||||
BDIpc.on('ping', () => 'pong', true);
|
||||
|
||||
BDIpc.on('bd-sendToDiscord', event => this.bd.windowUtils.send(event.args.channel, event.args.message));
|
||||
BDIpc.on('bd-getConfig', () => this.bd.config.config, true);
|
||||
|
||||
BDIpc.on('bd-openCssEditor', o => this.bd.csseditor.openEditor(o));
|
||||
// BDIpc.on('bd-setScss', o => this.bd.csseditor.setSCSS(o.args.scss));
|
||||
BDIpc.on('bd-sendToCssEditor', o => this.bd.csseditor.send(o.args.channel, o.args.data));
|
||||
BDIpc.on('bd-sendToDiscord', (event, m) => this.sendToDiscord(m.channel, m.message), true);
|
||||
|
||||
BDIpc.on('bd-readFile', this.readFile);
|
||||
BDIpc.on('bd-readJson', o => this.readFile(o, true));
|
||||
BDIpc.on('bd-openCssEditor', (event, options) => this.bd.csseditor.openEditor(options), true);
|
||||
BDIpc.on('bd-sendToCssEditor', (event, m) => this.sendToCssEditor(m.channel, m.message), true);
|
||||
|
||||
BDIpc.on('bd-native-open', o => {
|
||||
dialog.showOpenDialog(BrowserWindow.fromWebContents(o.ipcEvent.sender), o.args, filenames => {
|
||||
o.reply(filenames);
|
||||
BDIpc.on('bd-native-open', (event, options) => {
|
||||
dialog.showOpenDialog(BrowserWindow.fromWebContents(event.ipcEvent.sender), options, filenames => {
|
||||
event.reply(filenames);
|
||||
});
|
||||
});
|
||||
|
||||
BDIpc.on('bd-compileSass', o => {
|
||||
if (!o.args.path && !o.args.data) return o.reply('');
|
||||
if (typeof o.args.path === 'string' && typeof o.args.data === 'string') {
|
||||
o.args.data = `${o.args.data} @import '${o.args.path.replace(/\\/g, '\\\\').replace(/'/g, '\\\'')}';`;
|
||||
o.args.path = undefined;
|
||||
BDIpc.on('bd-compileSass', (event, options) => {
|
||||
if (typeof options.path === 'string' && typeof options.data === 'string') {
|
||||
options.data = `${options.data} @import '${options.path.replace(/\\/g, '\\\\').replace(/'/g, '\\\'')}';`;
|
||||
options.path = undefined;
|
||||
}
|
||||
|
||||
sass.render(o.args, (err, result) => {
|
||||
if (err) {
|
||||
o.reply({ err });
|
||||
return;
|
||||
}
|
||||
o.reply(result);
|
||||
sass.render(options, (err, result) => {
|
||||
if (err) event.reject(err);
|
||||
else event.reply(result);
|
||||
});
|
||||
});
|
||||
|
||||
BDIpc.on('bd-dba', o => {
|
||||
(async () => {
|
||||
try {
|
||||
const ret = await dbInstance.exec(o.args);
|
||||
o.reply(ret);
|
||||
} catch (err) {
|
||||
o.reply({err});
|
||||
}
|
||||
})();
|
||||
});
|
||||
}
|
||||
|
||||
async readFile(o, json) {
|
||||
const { path } = o.args;
|
||||
try {
|
||||
const readFile = json ? await FileUtils.readJsonFromFile(path) : await FileUtils.readFile(path);
|
||||
o.reply(readFile);
|
||||
} catch (err) {
|
||||
o.reply(err);
|
||||
}
|
||||
BDIpc.on('bd-dba', (event, options) => this.bd.dbInstance.exec(options), true);
|
||||
}
|
||||
|
||||
async send(channel, message) {
|
||||
BDIpc.send(channel, message);
|
||||
}
|
||||
|
||||
async sendToDiscord(channel, message) {
|
||||
return this.bd.windowUtils.send(channel, message);
|
||||
}
|
||||
|
||||
async sendToCssEditor(channel, message) {
|
||||
return this.bd.csseditor.send(channel, message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class BetterDiscord {
|
||||
|
@ -135,23 +115,32 @@ class BetterDiscord {
|
|||
|
||||
this.injectScripts = this.injectScripts.bind(this);
|
||||
this.ignite = this.ignite.bind(this);
|
||||
Common.Config = new Config(globals);
|
||||
|
||||
this.config = new Config(args || globals);
|
||||
this.dbInstance = new Database(this.config.getPath('data'));
|
||||
this.comms = new Comms(this);
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
async init() {
|
||||
const window = await this.waitForWindow();
|
||||
this.windowUtils = new WindowUtils({ window });
|
||||
await this.waitForWindowUtils();
|
||||
|
||||
await FileUtils.ensureDirectory(paths.find(path => path.id === 'ext').path);
|
||||
if (!tests) {
|
||||
const basePath = this.config.getPath('base');
|
||||
const files = await FileUtils.listDirectory(basePath);
|
||||
const latestCs = FileUtils.resolveLatest(files, file => file.endsWith('.js') && file.startsWith('client.'), file => file.replace('client.', '').replace('.js', ''), 'client.', '.js');
|
||||
this.config.getPath('cs', true).path = path.resolve(basePath, latestCs);
|
||||
}
|
||||
|
||||
this.csseditor = new CSSEditor(this, paths.find(path => path.id === 'csseditor').path);
|
||||
await FileUtils.ensureDirectory(this.config.getPath('ext'));
|
||||
|
||||
this.windowUtils.events('did-get-response-details', () => this.ignite(this.windowUtils.window));
|
||||
this.windowUtils.events('did-finish-load', e => this.injectScripts(true));
|
||||
this.csseditor = new CSSEditor(this, this.config.getPath('csseditor'));
|
||||
|
||||
this.windowUtils.events('did-navigate-in-page', (event, url, isMainFrame) => {
|
||||
this.windowUtils.on('did-get-response-details', () => this.ignite());
|
||||
this.windowUtils.on('did-finish-load', () => this.injectScripts(true));
|
||||
|
||||
this.windowUtils.on('did-navigate-in-page', (event, url, isMainFrame) => {
|
||||
this.windowUtils.send('did-navigate-in-page', { event, url, isMainFrame });
|
||||
});
|
||||
|
||||
|
@ -166,10 +155,8 @@ class BetterDiscord {
|
|||
const defer = setInterval(() => {
|
||||
const windows = BrowserWindow.getAllWindows();
|
||||
|
||||
if (windows.length > 0) {
|
||||
windows.forEach(window => {
|
||||
self.ignite(window);
|
||||
});
|
||||
for (let window of windows) {
|
||||
if (window) BetterDiscord.ignite(window);
|
||||
}
|
||||
|
||||
if (windows.length === 1 && windows[0].webContents.getURL().includes('discordapp.com')) {
|
||||
|
@ -180,23 +167,40 @@ class BetterDiscord {
|
|||
});
|
||||
}
|
||||
|
||||
ignite(window) {
|
||||
//Hook things that Discord removes from global. These will be removed again in the client script
|
||||
window.webContents.executeJavaScript(`require("${sparkplug}");`);
|
||||
async waitForWindowUtils() {
|
||||
if (this.windowUtils) return this.windowUtils;
|
||||
const window = await this.waitForWindow();
|
||||
return this.windowUtils = new WindowUtils({ window });
|
||||
}
|
||||
|
||||
get window() {
|
||||
return this.windowUtils ? this.windowUtils.window : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks things that Discord removes from global. These will be removed again in the client script.
|
||||
*/
|
||||
ignite() {
|
||||
return BetterDiscord.ignite(this.window);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks things that Discord removes from global. These will be removed again in the client script.
|
||||
* @param {BrowserWindow} window The window to inject the sparkplug script into
|
||||
*/
|
||||
static ignite(window) {
|
||||
return WindowUtils.injectScript(window, sparkplug);
|
||||
}
|
||||
|
||||
/**
|
||||
* Injects the client script into the main window.
|
||||
* @param {Boolean} reload Whether the main window was reloaded
|
||||
*/
|
||||
async injectScripts(reload = false) {
|
||||
console.log(`RELOAD? ${reload}`);
|
||||
if (!tests) {
|
||||
const files = await FileUtils.listDirectory(paths.find(path => path.id === 'base').path);
|
||||
const latestCs = FileUtils.resolveLatest(files, file => file.endsWith('.js') && file.startsWith('client.'), file => file.replace('client.', '').replace('.js', ''), 'client.', '.js');
|
||||
paths.find(path => path.id === 'cs').path = path.resolve(paths.find(path => path.id === 'base').path, latestCs).replace(/\\/g, '/');
|
||||
}
|
||||
this.windowUtils.injectScript(paths.find(path => path.id === 'cs').path);
|
||||
return this.windowUtils.injectScript(this.config.getPath('cs'));
|
||||
}
|
||||
|
||||
get fileUtils() { return FileUtils; }
|
||||
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
|
@ -5,39 +5,117 @@
|
|||
* https://github.com/JsSucks - 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.
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
const { Module } = require('./modulebase');
|
||||
|
||||
const { ipcMain } = require('electron');
|
||||
|
||||
const { Module } = require('./modulebase');
|
||||
|
||||
const callbacks = new WeakMap();
|
||||
|
||||
/**
|
||||
* The IPC module used in the main process.
|
||||
*/
|
||||
class BDIpc {
|
||||
|
||||
/**
|
||||
* Adds an IPC event listener.
|
||||
* @param {String} channel The channel to listen on
|
||||
* @param {Function} callback A function that will be called when a message is received
|
||||
* @param {Boolean} reply Whether to automatically reply to the message with the callback's return value
|
||||
* @return {Promise}
|
||||
*/
|
||||
static on(channel, callback, reply) {
|
||||
channel = channel.startsWith('bd-') ? channel : `bd-${channel}`;
|
||||
|
||||
const boundCallback = async (event, args) => {
|
||||
const ipcevent = new BDIpcEvent(event, args);
|
||||
try {
|
||||
const r = callback(ipcevent, ipcevent.message);
|
||||
if (reply) ipcevent.reply(await r);
|
||||
} catch (err) {
|
||||
console.error('Error in IPC callback:', err);
|
||||
if (reply) ipcevent.reject(err);
|
||||
}
|
||||
};
|
||||
|
||||
callbacks.set(callback, boundCallback);
|
||||
ipcMain.on(channel, boundCallback);
|
||||
}
|
||||
|
||||
static off(channel, callback) {
|
||||
ipcMain.removeListener(channel, callbacks.get(callback));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message to the main process and returns a promise that is resolved when the main process replies.
|
||||
* @param {BrowserWindow} window The window to send a message to
|
||||
* @param {String} channel The channel to send a message to
|
||||
* @param {Any} message Data to send to the main process
|
||||
* @param {Boolean} error Whether to mark the message as an error
|
||||
* @return {Promise}
|
||||
*/
|
||||
static send(window, channel, message, error) {
|
||||
channel = channel.startsWith('bd-') ? channel : `bd-${channel}`;
|
||||
|
||||
const eid = 'bd-' + Date.now().toString();
|
||||
window.send(channel, { eid, message, error });
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
ipcMain.once(eid, (event, arg) => {
|
||||
if (arg.error) reject(arg.message);
|
||||
else resolve(arg.message);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
static ping(window) {
|
||||
return this.send(window, 'ping');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class BDIpcEvent extends Module {
|
||||
|
||||
|
||||
constructor(event, args) {
|
||||
super(args);
|
||||
this.ipcEvent = event;
|
||||
this.replied = false;
|
||||
}
|
||||
|
||||
bindings() {
|
||||
this.send = this.send.bind(this);
|
||||
this.reply = this.reply.bind(this);
|
||||
}
|
||||
|
||||
send(message) {
|
||||
this.ipcEvent.sender.send(this.args.__eid, message);
|
||||
/**
|
||||
* Sends a message back to the message's sender.
|
||||
* @param {Any} message Data to send to this message's sender
|
||||
*/
|
||||
reply(message, error) {
|
||||
if (this.replied)
|
||||
throw {message: 'This message has already been replied to.'};
|
||||
|
||||
this.replied = true;
|
||||
return BDIpc.send(this.ipcEvent.sender, this.eid, message, error);
|
||||
}
|
||||
|
||||
reply(message) {
|
||||
this.send(message);
|
||||
reject(err) {
|
||||
return this.reply(err, true);
|
||||
}
|
||||
|
||||
get message() {
|
||||
return this.args.message;
|
||||
}
|
||||
|
||||
get error() {
|
||||
return this.args.error;
|
||||
}
|
||||
|
||||
get eid() {
|
||||
return this.args.eid;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class BDIpc {
|
||||
|
||||
static on(channel, cb) {
|
||||
ipcMain.on(channel, (event, args) => cb(new BDIpcEvent(event, args)));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { BDIpc };
|
||||
module.exports = { BDIpc };
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* https://github.com/JsSucks - 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.
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
const { Module } = require('./modulebase');
|
||||
|
@ -20,13 +20,18 @@ class Config extends Module {
|
|||
return this.args.paths;
|
||||
}
|
||||
|
||||
getPath(id, full) {
|
||||
const path = this.paths.find(path => path.id === id);
|
||||
return full ? path : path.path;
|
||||
}
|
||||
|
||||
get config() {
|
||||
return {
|
||||
'version': this.version,
|
||||
'paths': this.paths
|
||||
version: this.version,
|
||||
paths: this.paths
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = { Config };
|
||||
module.exports = { Config };
|
||||
|
|
|
@ -13,6 +13,7 @@ const { BrowserWindow } = require('electron');
|
|||
|
||||
const { Module } = require('./modulebase');
|
||||
const { WindowUtils } = require('./utils');
|
||||
const { BDIpc } = require('./bdipc');
|
||||
|
||||
class CSSEditor extends Module {
|
||||
|
||||
|
@ -22,57 +23,64 @@ class CSSEditor extends Module {
|
|||
this.bd = bd;
|
||||
}
|
||||
|
||||
openEditor(o) {
|
||||
if (this.editor) {
|
||||
if (this.editor.isFocused()) return;
|
||||
/**
|
||||
* Opens an editor.
|
||||
* @return {Promise}
|
||||
*/
|
||||
openEditor(options) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this.editor) {
|
||||
if (this.editor.isFocused()) return;
|
||||
|
||||
this.editor.focus();
|
||||
this.editor.flashFrame(true);
|
||||
o.reply(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const options = this.options;
|
||||
for (let option in o.args) {
|
||||
if (o.args.hasOwnProperty(option)) {
|
||||
options[option] = o.args[option];
|
||||
this.editor.focus();
|
||||
this.editor.flashFrame(true);
|
||||
return resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
this.editor = new BrowserWindow(options);
|
||||
this.editor.loadURL('about:blank');
|
||||
this.editor.setSheetOffset(33);
|
||||
this.editorUtils = new WindowUtils({ window: this.editor });
|
||||
options = Object.assign({}, this.options, options);
|
||||
|
||||
this.editor.on('close', () => {
|
||||
this.bd.windowUtils.send('bd-save-csseditor-bounds', this.editor.getBounds());
|
||||
this.editor = null;
|
||||
});
|
||||
this.editor = new BrowserWindow(options);
|
||||
this.editor.loadURL('about:blank');
|
||||
this.editor.setSheetOffset(33);
|
||||
this.editorUtils = new WindowUtils({ window: this.editor });
|
||||
|
||||
this.editor.once('ready-to-show', () => {
|
||||
this.editor.show();
|
||||
});
|
||||
this.editor.on('close', () => {
|
||||
this.bd.windowUtils.send('bd-save-csseditor-bounds', this.editor.getBounds());
|
||||
this.editor = null;
|
||||
});
|
||||
|
||||
this.editor.webContents.on('did-finish-load', () => {
|
||||
this.editorUtils.injectScript(path.join(this.editorPath, 'csseditor.js'));
|
||||
o.reply(true);
|
||||
});
|
||||
}
|
||||
|
||||
setSCSS(scss) {
|
||||
this.send('set-scss', scss);
|
||||
this.editor.once('ready-to-show', () => {
|
||||
this.editor.show();
|
||||
});
|
||||
|
||||
this.editor.webContents.on('did-finish-load', () => {
|
||||
this.editorUtils.injectScript(path.join(this.editorPath, 'csseditor.js'));
|
||||
resolve(true);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends data to the editor.
|
||||
* @param {String} channel
|
||||
* @param {Any} data
|
||||
*/
|
||||
send(channel, data) {
|
||||
if (!this.editor) return;
|
||||
this.editor.webContents.send(channel, data);
|
||||
if (!this.editor) throw {message: 'The CSS editor is not open.'};
|
||||
return BDIpc.send(this.editor, channel, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the CSS editor's always on top flag.
|
||||
*/
|
||||
set alwaysOnTop(state) {
|
||||
if (!this.editor) return;
|
||||
this.editor.setAlwaysOnTop(state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Default options to pass to BrowserWindow.
|
||||
*/
|
||||
get options() {
|
||||
return {
|
||||
width: 800,
|
||||
|
@ -81,7 +89,7 @@ class CSSEditor extends Module {
|
|||
frame: false
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
module.exports = { CSSEditor };
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
const Datastore = require('nedb');
|
||||
|
||||
class Database {
|
||||
class Database {
|
||||
|
||||
constructor(dbPath) {
|
||||
this.exec = this.exec.bind(this);
|
||||
|
|
|
@ -5,20 +5,19 @@
|
|||
* https://github.com/JsSucks - 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
Base Module that every non-static module should extend
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Base Module that every non-static module should extend.
|
||||
*/
|
||||
class Module {
|
||||
|
||||
constructor(args) {
|
||||
this.__ = {
|
||||
state: args,
|
||||
args
|
||||
}
|
||||
};
|
||||
this.init();
|
||||
}
|
||||
|
||||
|
@ -33,4 +32,4 @@ class Module {
|
|||
|
||||
}
|
||||
|
||||
module.exports = { Module };
|
||||
module.exports = { Module };
|
||||
|
|
|
@ -10,43 +10,36 @@
|
|||
|
||||
// TODO Use common
|
||||
|
||||
const
|
||||
path = require('path'),
|
||||
fs = require('fs');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const { Module } = require('./modulebase');
|
||||
const { BDIpc } = require('./bdipc');
|
||||
|
||||
class Utils {
|
||||
|
||||
static async tryParseJson(jsonString) {
|
||||
try {
|
||||
return JSON.parse(jsonString);
|
||||
}catch(err) {
|
||||
} catch (err) {
|
||||
throw ({
|
||||
'message': 'Failed to parse json',
|
||||
message: 'Failed to parse json',
|
||||
err
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static get timestamp() {
|
||||
return 'Timestamp';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class FileUtils {
|
||||
|
||||
static async fileExists(path) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.stat(path, (err, stats) => {
|
||||
if(err) return reject({
|
||||
'message': `No such file or directory: ${err.path}`,
|
||||
message: `No such file or directory: ${err.path}`,
|
||||
err
|
||||
});
|
||||
|
||||
if(!stats.isFile()) return reject({
|
||||
'message': `Not a file: ${path}`,
|
||||
message: `Not a file: ${path}`,
|
||||
stats
|
||||
});
|
||||
|
||||
|
@ -59,12 +52,12 @@ class FileUtils {
|
|||
return new Promise((resolve, reject) => {
|
||||
fs.stat(path, (err, stats) => {
|
||||
if(err) return reject({
|
||||
'message': `Directory does not exist: ${path}`,
|
||||
message: `Directory does not exist: ${path}`,
|
||||
err
|
||||
});
|
||||
|
||||
if(!stats.isDirectory()) return reject({
|
||||
'message': `Not a directory: ${path}`,
|
||||
message: `Not a directory: ${path}`,
|
||||
stats
|
||||
});
|
||||
|
||||
|
@ -76,14 +69,14 @@ class FileUtils {
|
|||
static async readFile(path) {
|
||||
try {
|
||||
await this.fileExists(path);
|
||||
} catch(err) {
|
||||
throw(err);
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.readFile(path, 'utf-8', (err, data) => {
|
||||
if(err) reject({
|
||||
'message': `Could not read file: ${path}`,
|
||||
if(err) return reject({
|
||||
message: `Could not read file: ${path}`,
|
||||
err
|
||||
});
|
||||
|
||||
|
@ -97,14 +90,13 @@ class FileUtils {
|
|||
try {
|
||||
readFile = await this.readFile(path);
|
||||
} catch(err) {
|
||||
throw(err);
|
||||
throw err;
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = await Utils.tryParseJson(readFile);
|
||||
return parsed;
|
||||
} catch(err) {
|
||||
throw(Object.assign(err, { path }));
|
||||
return await Utils.tryParseJson(readFile);
|
||||
} catch (err) {
|
||||
throw Object.assign(err, { path });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -113,8 +105,8 @@ class FileUtils {
|
|||
await this.directoryExists(path);
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.readdir(path, (err, files) => {
|
||||
if (err) return reject(err);
|
||||
resolve(files);
|
||||
if (err) reject(err);
|
||||
else resolve(files);
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
|
@ -145,11 +137,8 @@ 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();
|
||||
if (err) reject(err);
|
||||
else resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -170,7 +159,6 @@ class FileUtils {
|
|||
}
|
||||
|
||||
class WindowUtils extends Module {
|
||||
|
||||
bindings() {
|
||||
this.openDevTools = this.openDevTools.bind(this);
|
||||
this.executeJavascript = this.executeJavascript.bind(this);
|
||||
|
@ -190,28 +178,32 @@ class WindowUtils extends Module {
|
|||
}
|
||||
|
||||
executeJavascript(script) {
|
||||
this.webContents.executeJavaScript(script);
|
||||
return this.webContents.executeJavaScript(script);
|
||||
}
|
||||
|
||||
injectScript(fpath, variable) {
|
||||
console.log(`Injecting: ${fpath}`);
|
||||
return WindowUtils.injectScript(this.window, fpath, variable);
|
||||
}
|
||||
|
||||
static injectScript(window, fpath, variable) {
|
||||
window = window.webContents || window;
|
||||
if (!window) return;
|
||||
// console.log(`Injecting: ${fpath} to`, window);
|
||||
|
||||
const escaped_path = fpath.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
||||
const escaped_variable = variable ? variable.replace(/\\/g, '\\\\').replace(/"/g, '\\"') : null;
|
||||
|
||||
if (variable) this.executeJavascript(`window["${escaped_variable}"] = require("${escaped_path}");`);
|
||||
else this.executeJavascript(`require("${escaped_path}");`);
|
||||
if (variable) return window.executeJavaScript(`window["${escaped_variable}"] = require("${escaped_path}");`);
|
||||
else return window.executeJavaScript(`require("${escaped_path}");`);
|
||||
}
|
||||
|
||||
events(event, callback) {
|
||||
on(event, callback) {
|
||||
this.webContents.on(event, callback);
|
||||
}
|
||||
|
||||
send(channel, message) {
|
||||
channel = channel.startsWith('bd-') ? channel : `bd-${channel}`;
|
||||
this.webContents.send(channel, message);
|
||||
return BDIpc.send(this.window, channel, message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
|
@ -1,5 +1,17 @@
|
|||
/**
|
||||
* BetterDiscord Sparkplug
|
||||
* Copyright (c) 2015-present JsSucks - https://github.com/JsSucks
|
||||
* All rights reserved.
|
||||
* https://github.com/JsSucks - 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.
|
||||
*
|
||||
* This file is evaluated in the renderer process!
|
||||
*/
|
||||
|
||||
(() => {
|
||||
if (window.__bd && window.__bd.ignited) return;
|
||||
if (module.exports.bd) return;
|
||||
|
||||
console.log('[BetterDiscord|Sparkplug]');
|
||||
|
||||
|
@ -7,12 +19,12 @@
|
|||
if (!ls) console.warn('[BetterDiscord|Sparkplug] Failed to hook localStorage :(');
|
||||
const wsOrig = window.WebSocket;
|
||||
|
||||
window.__bd = {
|
||||
const bd = module.exports.bd = {
|
||||
localStorage: ls,
|
||||
wsHook: null,
|
||||
wsOrig,
|
||||
ignited: true
|
||||
}
|
||||
};
|
||||
|
||||
class WSHook extends window.WebSocket {
|
||||
|
||||
|
@ -25,14 +37,14 @@
|
|||
console.info(`[BetterDiscord|WebSocket Proxy] new WebSocket detected, url: ${url}`);
|
||||
if (!url.includes('gateway.discord.gg')) return;
|
||||
|
||||
if (window.__bd.setWS) {
|
||||
window.__bd.setWS(this);
|
||||
if (bd.setWS) {
|
||||
bd.setWS(this);
|
||||
console.info(`[BetterDiscord|WebSocket Proxy] WebSocket sent to instance`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.info(`[BetterDiscord|WebSocket Proxy] WebSocket stored to __bd['wsHook']`);
|
||||
window.__bd.wsHook = this;
|
||||
console.info(`[BetterDiscord|WebSocket Proxy] WebSocket stored to bd.wsHook`);
|
||||
bd.wsHook = this;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -5,21 +5,19 @@
|
|||
"version": "0.4.0",
|
||||
"homepage": "https://betterdiscord.net",
|
||||
"license": "MIT",
|
||||
"main": "index.js",
|
||||
"main": "dist/csseditor.js",
|
||||
"contributors": [
|
||||
"Jiiks",
|
||||
"Pohky"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Jiiks/BetterDiscordApp.git"
|
||||
"url": "https://github.com/JsSucks/BetterDiscordApp.git"
|
||||
},
|
||||
"private": false,
|
||||
"devDependencies": {
|
||||
|
||||
},
|
||||
"scripts": {
|
||||
"build": "webpack --progress --colors",
|
||||
"watch": "webpack --progress --colors --watch"
|
||||
"watch": "webpack --progress --colors --watch",
|
||||
"release": "webpack --progress --colors --config=webpack.production.config.js"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
const { ipcRenderer } = window.require('electron');
|
||||
|
||||
export default class {
|
||||
|
||||
static on(channel, cb) {
|
||||
ipcRenderer.on(channel, (event, args) => cb(event, args));
|
||||
}
|
||||
|
||||
static async send(channel, message) {
|
||||
const __eid = Date.now().toString();
|
||||
ipcRenderer.send(
|
||||
channel.startsWith('bd-') ? channel: `bd-${channel}`,
|
||||
message === undefined ? { __eid } : Object.assign(message, { __eid })
|
||||
);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
ipcRenderer.once(__eid, (event, arg) => {
|
||||
if (arg.err) return reject(arg);
|
||||
resolve(arg);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
static sendToDiscord(channel, message) {
|
||||
this.send('bd-sendToDiscord', { channel, message });
|
||||
}
|
||||
|
||||
}
|
|
@ -33,18 +33,18 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import '../../node_modules/codemirror/addon/scroll/simplescrollbars.js';
|
||||
import '../../node_modules/codemirror/mode/css/css.js';
|
||||
import '../../node_modules/codemirror/addon/hint/css-hint.js';
|
||||
import '../../node_modules/codemirror/addon/search/search.js';
|
||||
import '../../node_modules/codemirror/addon/search/searchcursor.js';
|
||||
import '../../node_modules/codemirror/addon/search/jump-to-line.js';
|
||||
import '../../node_modules/codemirror/addon/dialog/dialog.js';
|
||||
import '../../node_modules/codemirror/addon/hint/show-hint.js';
|
||||
import ClientIPC from 'bdipc';
|
||||
|
||||
import BDIpc from './BDIpc';
|
||||
import { remote } from 'electron';
|
||||
|
||||
const { remote } = window.require('electron');
|
||||
import 'codemirror/addon/scroll/simplescrollbars.js';
|
||||
import 'codemirror/mode/css/css.js';
|
||||
import 'codemirror/addon/hint/css-hint.js';
|
||||
import 'codemirror/addon/search/search.js';
|
||||
import 'codemirror/addon/search/searchcursor.js';
|
||||
import 'codemirror/addon/search/jump-to-line.js';
|
||||
import 'codemirror/addon/dialog/dialog.js';
|
||||
import 'codemirror/addon/hint/show-hint.js';
|
||||
|
||||
const ExcludedIntelliSenseTriggerKeys = {
|
||||
'8': 'backspace',
|
||||
|
@ -131,42 +131,38 @@
|
|||
}
|
||||
},
|
||||
created() {
|
||||
BDIpc.on('set-scss', (_, data) => {
|
||||
if (data.error) {
|
||||
console.log(data.error);
|
||||
return;
|
||||
}
|
||||
console.log(data);
|
||||
this.setScss(data.scss);
|
||||
});
|
||||
ClientIPC.on('set-scss', (_, scss) => this.setScss(scss));
|
||||
|
||||
BDIpc.on('scss-error', (_, err) => {
|
||||
ClientIPC.on('scss-error', (_, err) => {
|
||||
this.error = err;
|
||||
this.$forceUpdate();
|
||||
if (err)
|
||||
console.error('SCSS parse error:', err);
|
||||
});
|
||||
|
||||
BDIpc.on('set-liveupdate', (e, liveUpdate) => this.liveUpdate = liveUpdate);
|
||||
ClientIPC.on('set-liveupdate', (e, liveUpdate) => this.liveUpdate = liveUpdate);
|
||||
},
|
||||
mounted() {
|
||||
this.codemirror.on('keyup', this.cmOnKeyUp);
|
||||
BDIpc.sendToDiscord('get-scss');
|
||||
BDIpc.sendToDiscord('get-liveupdate');
|
||||
|
||||
(async () => {
|
||||
this.setScss(await ClientIPC.sendToDiscord('get-scss'));
|
||||
this.liveUpdate = await ClientIPC.sendToDiscord('get-liveupdate');
|
||||
})();
|
||||
},
|
||||
watch: {
|
||||
liveUpdate(liveUpdate) {
|
||||
BDIpc.sendToDiscord('set-liveupdate', liveUpdate);
|
||||
ClientIPC.sendToDiscord('set-liveupdate', liveUpdate);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
save() {
|
||||
const scss = this.codemirror.getValue();
|
||||
BDIpc.sendToDiscord('save-scss', scss);
|
||||
ClientIPC.sendToDiscord('save-scss', scss);
|
||||
},
|
||||
update() {
|
||||
const scss = this.codemirror.getValue();
|
||||
BDIpc.sendToDiscord('update-scss', scss);
|
||||
ClientIPC.sendToDiscord('update-scss', scss);
|
||||
},
|
||||
toggleaot() {
|
||||
this.alwaysOnTop = !this.alwaysOnTop;
|
||||
|
@ -180,7 +176,7 @@
|
|||
this.codemirror.setValue(scss || '');
|
||||
},
|
||||
cmOnChange(value) {
|
||||
if(this.liveUpdate) BDIpc.sendToDiscord('update-scss', value);
|
||||
if(this.liveUpdate) ClientIPC.sendToDiscord('update-scss', value);
|
||||
},
|
||||
cmOnKeyUp(editor, event) {
|
||||
if (event.ctrlKey) return;
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
const
|
||||
path = require('path'),
|
||||
webpack = require('webpack');
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
|
||||
const vueLoader = {
|
||||
test: /\.(vue)$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'vue-loader'
|
||||
}
|
||||
};
|
||||
|
||||
const scssLoader = {
|
||||
test: /\.(css|scss)$/,
|
||||
loader: ['css-loader', 'sass-loader']
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
entry: './src/index.js',
|
||||
|
@ -21,9 +21,17 @@ module.exports = {
|
|||
module: {
|
||||
loaders: [vueLoader, scssLoader]
|
||||
},
|
||||
externals: {
|
||||
electron: 'window.require("electron")',
|
||||
fs: 'window.require("fs")'
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
vue$: path.resolve('..', 'node_modules', 'vue', 'dist', 'vue.esm.js')
|
||||
}
|
||||
},
|
||||
modules: [
|
||||
path.resolve('..', 'node_modules'),
|
||||
path.resolve('..', 'common', 'modules')
|
||||
]
|
||||
}
|
||||
};
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
|
||||
|
||||
const vueLoader = {
|
||||
test: /\.(vue)$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'vue-loader'
|
||||
};
|
||||
|
||||
const scssLoader = {
|
||||
test: /\.(css|scss)$/,
|
||||
loader: ['css-loader', 'sass-loader']
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
entry: './src/index.js',
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: 'csseditor-release.js'
|
||||
},
|
||||
module: {
|
||||
loaders: [vueLoader, scssLoader]
|
||||
},
|
||||
externals: {
|
||||
electron: 'window.require("electron")',
|
||||
fs: 'window.require("fs")'
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
vue$: path.resolve('..', 'node_modules', 'vue', 'dist', 'vue.esm.js')
|
||||
},
|
||||
modules: [
|
||||
path.resolve('..', 'node_modules'),
|
||||
path.resolve('..', 'common', 'modules')
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
PRODUCTION: JSON.stringify(true)
|
||||
}),
|
||||
new UglifyJsPlugin()
|
||||
]
|
||||
};
|
53
gulpfile.js
53
gulpfile.js
|
@ -6,64 +6,75 @@ const
|
|||
merge = require('gulp-merge'),
|
||||
copy = require('gulp-copy'),
|
||||
rename = require('gulp-rename'),
|
||||
inject = require('gulp-inject-string'),
|
||||
copydeps = require('gulp-npm-copy-deps');
|
||||
|
||||
const mainpkg = require('./package.json');
|
||||
const corepkg = require('./core/package.json');
|
||||
const clientpkg = require('./client/package.json');
|
||||
const editorpkg = require('./csseditor/package.json');
|
||||
|
||||
const releasepkg = function() {
|
||||
delete mainpkg.main;
|
||||
delete mainpkg.devDependencies;
|
||||
delete mainpkg.scripts;
|
||||
return fs.writeFileSync('./release/package.json', JSON.stringify(mainpkg, null, 2));
|
||||
};
|
||||
|
||||
const client = function() {
|
||||
return pump([
|
||||
gulp.src('./client/dist/*.client.js'),
|
||||
gulp.src('./client/dist/*.client-release.js'),
|
||||
rename(`client.${clientpkg.version}.js`),
|
||||
gulp.dest('./release')
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
const core = function() {
|
||||
return pump([
|
||||
gulp.src('./core/dist/modules/**/*'),
|
||||
copy('release/', { prefix: 2 })
|
||||
]);
|
||||
}
|
||||
|
||||
const core2 = function() {
|
||||
return pump([
|
||||
gulp.src('./core/dist/main.js'),
|
||||
inject.after("'use strict';\n", 'const PRODUCTION = true;\n'),
|
||||
rename(`core.${corepkg.version}.js`),
|
||||
gulp.dest('./release')
|
||||
]);
|
||||
}
|
||||
|
||||
const core3 = function() {
|
||||
return fs.writeFileSync('./release/index.js', `module.exports = require('./core.${corepkg.version}.js');`);
|
||||
}
|
||||
};
|
||||
|
||||
const sparkplug = function() {
|
||||
return pump([
|
||||
gulp.src('./core/dist/sparkplug.js'),
|
||||
gulp.dest('./release')
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
const core_modules = function() {
|
||||
return pump([
|
||||
gulp.src('./core/dist/modules/**/*'),
|
||||
copy('release/', { prefix: 2 })
|
||||
]);
|
||||
};
|
||||
|
||||
const index = function() {
|
||||
return fs.writeFileSync('./release/index.js', `module.exports = require('./core.${corepkg.version}.js');`);
|
||||
};
|
||||
|
||||
const cssEditor = function() {
|
||||
return pump([
|
||||
gulp.src('./csseditor/dist/**/*'),
|
||||
gulp.src('./csseditor/dist/csseditor-release.js'),
|
||||
rename('csseditor.js'),
|
||||
copy('release/csseditor', { prefix: 2 })
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
const deps = function() {
|
||||
return copydeps('./', './release');
|
||||
}
|
||||
};
|
||||
|
||||
const bindings = function() {
|
||||
const node_sass_bindings = function() {
|
||||
return pump([
|
||||
gulp.src('./other/node_sass_bindings/**/*'),
|
||||
copy('release/node_modules/node-sass/vendor', { prefix: 2 })
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
gulp.task('release', function () {
|
||||
del(['./release/**/*']).then(() => merge(client(), core(), core2(), core3(), sparkplug(), cssEditor(), deps(), bindings()));
|
||||
del(['./release/**/*']).then(() => merge(releasepkg(), client(), core(), sparkplug(), core_modules(), index(), cssEditor(), deps(), node_sass_bindings()));
|
||||
});
|
||||
|
|
|
@ -5,19 +5,16 @@
|
|||
"version": "2.0.0",
|
||||
"homepage": "https://betterdiscord.net",
|
||||
"license": "MIT",
|
||||
"main": "index.js",
|
||||
"main": "dist/installer.js",
|
||||
"contributors": [
|
||||
"Jiiks",
|
||||
"Pohky"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Jiiks/BetterDiscordApp.git"
|
||||
"url": "https://github.com/JsSucks/BetterDiscordApp.git"
|
||||
},
|
||||
"private": false,
|
||||
"devDependencies": {
|
||||
|
||||
},
|
||||
"scripts": {
|
||||
"build": "webpack --progress --colors",
|
||||
"watch": "webpack --progress --colors --watch"
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue