Refactor base, add DOM

This commit is contained in:
Jiiks 2018-01-29 19:34:31 +02:00
parent 28c573e5c5
commit 4c5ff964e6
57 changed files with 96 additions and 2010 deletions

View File

@ -1,55 +0,0 @@
[
{
"id": "core",
"text": "Core",
"settings": [
{
"id": "test-setting",
"text": "Test Setting",
"hint": "Test Setting",
"enabled": false,
"disabled": false
},
{
"id": "test-setting2",
"text": "Test Setting 2",
"hint": "Test Setting 2",
"enabled": true,
"disabled": false
},
{
"id": "voice-disconnect",
"text": "Voice Disconnect",
"hint": "Disconnect from voice server when Discord closes",
"enabled": false,
"disabled": true
},
{
"id": "developer-mode",
"text": "Developer Mode",
"hint": "BetterDiscord developer mode",
"enabled": false,
"disabled": true
}
]
},
{
"id": "ui",
"text": "UI",
"settings": [
]
},
{
"id": "emotes",
"text": "Emotes",
"settings": []
},
{
"id": "security",
"text": "Security",
"settings": [
]
}
]

View File

@ -8,47 +8,19 @@
* LICENSE file in the root directory of this source tree.
*/
'use strict';
const styles = require('./styles/index.scss');
const { Global, Logger, Utils, PluginManager, BDIpc, WebpackModules, SocketProxy, Events, Vendor } = require('./modules');
//const { UI } = require('./modules/ui/index.jsx');
import { DOM } from './ui';
import BdCss from './styles/index.scss';
class BetterDiscord {
constructor() {
window.bdUtils = Utils;
window.wpm = WebpackModules;
Events.on('global-ready', e => {
const { UI } = require('./modules/ui/vueui.js');
this.ui = new UI();
this.init();
});
//Inject styles to head for now
const style = document.createElement('style');
style.id = 'bd-main';
style.type = 'text/css';
style.appendChild(document.createTextNode(styles));
document.head.appendChild(style);
this.init();
window.DOM = DOM;
DOM.injectStyle(BdCss, 'bdmain');
}
async init() {
try {
await PluginManager.loadAllPlugins();
} catch (err) {
}
Events.emit('ready');
}
}
if (window.BetterDiscord) {
Logger.log('main', 'Attempting to inject again?');
} else {
let bdInstance = new BetterDiscord();
window.BetterDiscord = {'vendor': Vendor};
window.BetterDiscord = { 'vendor': Vendor };
}

View File

@ -1,29 +0,0 @@
/**
* BetterDiscord Client IPC Module
* 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.
*/
const { ipcRenderer } = require('electron');
class BDIpc {
static on(channel, cb) {
ipcRenderer.on(channel, (event, message) => cb(event, message));
}
static async send(channel, message) {
channel = channel.startsWith('bd-') ? channel : `bd-${channel}`;
const __eid = Date.now().toString();
ipcRenderer.send(channel, Object.assign(message ? message : {}, { __eid }));
return new Promise((resolve, reject) => {
ipcRenderer.once(__eid, (event, arg) => resolve(arg));
});
}
}
module.exports = { BDIpc };

View File

@ -1,34 +0,0 @@
/**
* BetterDiscord CSS Editor
* 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.
*/
const { Module } = require('./modulebase');
const { BDIpc } = require('./bdipc');
const $ = require('jquery');
class CssEditor extends Module {
setInitialState() {
this.state = {
css: ''
}
this.customcss = $('<style id="customcss">').appendTo("head");
window.cssEditor = this;
BDIpc.on("bd-update-css", (_, css) => this.customcss.text(css));
BDIpc.on("bd-save-css", (_, css) => this.setState({css}));
}
show() {
BDIpc.send('openCssEditor', {}).then(() => BDIpc.send('setCss', {css: this.state.css}));
}
}
const _instance = new CssEditor();
module.exports = { 'CssEditor': _instance }

View File

@ -1,60 +0,0 @@
/**
* BetterDiscord Discord Socket Proxy
* 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.
*/
const { Events } = require('./events');
const { Module } = require('./modulebase');
const { Global } = require('./global');
const { Utils } = require('./utils');
class SocketProxy extends Module {
events() {
Events.on('socket-created', this.socketCreated);
}
bindings() {
this.socketCreated = this.socketCreated.bind(this);
this.onmessage = this.onmessage.bind(this);
}
socketCreated() {
const wsHook = Global.getObject('wsHook');
wsHook.addEventListener('message', this.onmessage);
}
onmessage(e) {
console.log(e);
//TODO fix unpacking
const unpacked = this.erlpack.unpack(e.data);
console.log(unpacked);
}
get erlpack() {
if (this._erlpack) return this._erlpack;
try {
this._erlpack = window.require('erlpack');
} catch (err) {
console.log(err);
try {
this._erlpack = window.require('discord_erlpack');
} catch (err) {
console.log(err);
}
}
return this._erlpack;
}
}
const _instance = new SocketProxy();
module.exports = { 'SocketProxy': _instance }

View File

@ -1,31 +0,0 @@
/**
* BetterDiscord Events
* 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.
*/
const { EventEmitter } = require('events');
const emitter = new EventEmitter();
class Events {
static on(eventName, callBack) {
emitter.on(eventName, callBack);
}
static off(eventName, callBack) {
emitter.removeListener(eventName, callBack);
}
static emit(...args) {
emitter.emit(...args);
}
}
module.exports = { Events }

View File

@ -1,70 +0,0 @@
/**
* BetterDiscord Client Globals
* 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.
*/
const { Module } = require('./modulebase');
const { Events } = require('./events');
const { BDIpc } = require('./bdipc');
const { WebpackModules } = require('./webpackmodules');
class Global extends Module {
constructor(args) {
super(args);
this.first();
window.gl = this;
}
bindings() {
this.first = this.first.bind(this);
this.setWS = this.setWS.bind(this);
this.getObject = this.getObject.bind(this);
}
first() {
(async () => {
const config = await BDIpc.send('getConfig');
this.setState(config);
/* const getReact = await WebpackModules.getModuleByProps(('createElement', 'cloneElement'));
this.React = getReact[0].exports;
window.React = this.React;
const getReactDom = await WebpackModules.getModuleByProps(('render', 'findDOMNode'));
this.reactDOM = getReactDom[0].exports;*/
// this.setState(Object.assign(config, { React, reactDOM }));
Events.emit('global-ready');
})();
if (window.__bd) {
this.setState(window.__bd);
window.__bd = {
setWS: this.setWS
}
Events.emit('socket-created');
}
}
setWS(wSocket) {
const state = this.state;
state.wsHook = wSocket;
this.setState(state);
Events.emit('socket-created');
}
getObject(name) {
return this.state[name];
}
getLoadedModule(name) {
return this[name];
}
}
const _instance = new Global();
module.exports = { 'Global': _instance }

View File

@ -1,47 +0,0 @@
/**
* BetterDiscord Module Base
* 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.
*/
/*
Base Module that every non-static module should extend
*/
class Module {
constructor(args) {
this.__ = {
state: args || {},
args
}
this.setState = this.setState.bind(this);
this.init();
}
init() {
if (this.bindings) this.bindings();
if (this.setInitialState) this.setInitialState(this.state);
if (this.events) this.events();
}
setState(newState) {
const oldState = this.state;
Object.assign(this.state, newState);
if (this.stateChanged) this.stateChanged(oldState, newState);
}
set args(t) { }
get args() { return this.__.args; }
set state(state) { return this.__.state = state; }
get state() { return this.__.state; }
}
module.exports = { Module };

View File

@ -1,17 +0,0 @@
/**
* BetterDiscord Plugin Base Class
* 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.
*/
class Plugin {
constructor() {}
}
module.exports = { Plugin }

View File

@ -1,275 +0,0 @@
/**
* BetterDiscord Plugin Manager
* 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.
*/
const { Module } = require('./modulebase');
const { FileUtils, Logger } = require('./utils');
const { Global } = require('./global');
const path = window.require('path');
class Plugin {
constructor(pluginInternals) {
this.__pluginInternals = pluginInternals;
this.hasSettings = this.pluginConfig && this.pluginConfig.length > 0;
this.start = this.start.bind(this);
this.stop = this.stop.bind(this);
}
get configs() { return this.__pluginInternals.configs }
get info() { return this.__pluginInternals.info }
get paths() { return this.__pluginInternals.paths }
get main() { return this.__pluginInternals.main }
get defaultConfig() { return this.configs.defaultConfig }
get userConfig() { return this.configs.userConfig }
get name() { return this.info.name }
get authors() { return this.info.authors }
get version() { return this.info.version }
get pluginPath() { return this.paths.pluginPath }
get dirName() { return this.paths.dirName }
get enabled() { return this.userConfig.enabled }
get pluginConfig() { return this.userConfig.pluginConfig }
start() {
if (this.onStart) {
const started = this.onStart();
if (started) {
return this.userConfig.enabled = true;
}
return false;
}
return this.userConfig.enabled = true; //Assume plugin started since it doesn't have onStart
}
stop() {
if (this.onStop) {
const stopped = this.onStop();
if (stopped) {
this.userConfig.enabled = false;
return true;
}
return false;
}
this.userConfig.enabled = false;
return true; //Assume plugin stopped since it doesn't have onStop
}
}
class PluginManager extends Module {
setInitialState() {
window.pm = this;
this.setState({
plugins: []
});
}
get plugins() {
return this.state.plugins;
}
get pluginsPath() {
return Global.getObject('paths').find(path => path.id === 'plugins').path;
}
async loadAllPlugins() {
try {
const directories = await FileUtils.readDir(this.pluginsPath);
for (let dir of directories) {
try {
await this.loadPlugin(dir);
} catch (err) {
//We don't want every plugin to fail loading when one does
Logger.err('PluginManager', err);
}
}
return this.plugins;
} catch (err) {
throw err;
}
}
async refreshPlugins() {
if (this.plugins.length <= 0) return this.loadAllPlugins();
try {
const directories = await FileUtils.readDir(this.pluginsPath);
for (let dir of directories) {
//If a plugin is already loaded this should resolve.
if (this.getPluginByDirName(dir)) continue;
try {
//Load the plugin if not
await this.loadPlugin(dir);
} catch (err) {
//We don't want every plugin to fail loading when one does
Logger.err('PluginManager', err);
}
}
for (let plugin of this.plugins) {
if (directories.includes(plugin.dirName)) continue;
//Plugin was deleted manually, stop it and remove any reference
try {
if (plugin.enabled) plugin.stop();
const { pluginPath } = plugin;
const index = this.getPluginIndex(plugin);
delete window.require.cache[window.require.resolve(pluginPath)];
this.plugins.splice(index, 1);
} catch (err) {
//This might fail but we don't have any other option at this point
Logger.err('PluginManager', err);
}
}
} catch (err) {
throw err;
}
}
async loadPlugin(pluginPath, reload = false, index) {
const { plugins } = this.state;
const dirName = pluginPath;
try {
pluginPath = path.join(this.pluginsPath, pluginPath);
// Make sure this is a directory
await FileUtils.directoryExists(pluginPath);
if (!reload) {
const loaded = plugins.find(plugin => plugin.pluginPath === pluginPath);
if (loaded) {
throw { 'message': 'Attempted to load an already loaded plugin' };
}
}
const readConfig = await this.readConfig(pluginPath);
const mainPath = path.join(pluginPath, readConfig.main);
const userConfigPath = path.join(pluginPath, 'user.config.json');
const userConfig = {
enabled: false,
pluginConfig: readConfig.defaultConfig
};
try {
const readUserConfig = await FileUtils.readJsonFromFile(userConfigPath);
//userConfig = Object.assign({}, userConfig, readUserConfig);
userConfig.pluginConfig = readConfig.defaultConfig.map(config => {
const userSet = readUserConfig.pluginConfig.find(c => c.id === config.id);
return userSet || config;
});
} catch (err) {/*We don't care if this fails it either means that user config doesn't exist or there's something wrong with it so we revert to default config*/}
const configs = {
defaultConfig: readConfig.defaultConfig,
userConfig
};
const plugin = window.require(mainPath)(Plugin, {}, {});
const instance = new plugin({configs, info: readConfig.info, main: readConfig.main, paths: { pluginPath, dirName }});
if (instance.enabled) instance.start();
if (reload) plugins[index] = instance;
else plugins.push(instance);
this.setState(plugins);
return instance;
} catch (err) {
throw err;
}
}
async reloadPlugin(plugin) {
const _plugin = plugin instanceof Plugin ? plugin : this.findPlugin(plugin);
if (!_plugin) throw { 'message': 'Attempted to reload a plugin that is not loaded?' };
if (!_plugin.stop()) throw { 'message': 'Plugin failed to stop!' };
const index = this.getPluginIndex(_plugin);
const { pluginPath, dirName } = _plugin;
delete window.require.cache[window.require.resolve(pluginPath)];
// this.plugins.splice(index, 1);
return this.loadPlugin(dirName, true, index);
}
//TODO make this nicer
findPlugin(wild) {
let plugin = this.getPluginByName(wild);
if (plugin) return plugin;
plugin = this.getPluginById(wild);
if (plugin) return plugin;
plugin = this.getPluginByPath(wild);
if (plugin) return plugin;
return this.getPluginByDirName(wild);
}
getPluginIndex(plugin) { return this.plugins.findIndex(p => p === plugin) }
getPluginByName(name) { return this.plugins.find(p => p.name === name) }
getPluginById(id) { return this.plugins.find(p => p.id === id) }
getPluginByPath(path) { return this.plugins.find(p => p.pluginPath === path) }
getPluginByDirName(dirName) { return this.plugins.find(p => p.dirName === dirName) }
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
}
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
}
async readConfig(path) {
path = `${path}/config.json`;
return FileUtils.readJsonFromFile(path);
}
}
const _instance = new PluginManager();
async function pluginManager(pluginName) {
try {
//Load test plugin
const plugin = await _instance.loadPlugin(pluginName);
//Attempt to load the same plugin again
const plugin2 = await _instance.loadPlugin(pluginName);
return true;
} catch (err) {
console.log(`Failed to load plugin! ${err.message}`);
}
try {
//Reload test plugin
const reloadedPlugin = await _instance.reloadPlugin('Example Plugin');
} catch (err) {
console.log(`Failed to reload plugin! ${err.message}`);
}
}
if (window.bdTests) window.bdTests.pluginManager = pluginManager;
else window.bdTests = { pluginManager };
module.exports = { PluginManager: _instance, Plugin };

View File

@ -1,11 +0,0 @@
const defaultSettings = require('../../data/user.settings.default');
class Settings {
static get getSettings() {
return defaultSettings;
}
}
module.exports = { Settings };

View File

@ -1,225 +0,0 @@
/**
* BetterDiscord Client Utils Module
* 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.
*/
const { Module } = require('./modulebase');
const { Vendor } = require('./vendor');
const fs = window.require('fs');
const path = window.require('path');
const logs = [];
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'); }
static log(module, message, level = 'log') {
message = message.message || message;
if (typeof message === 'object') {
//TODO object handler for logs
console.log(message);
return;
}
level = this.parseLevel(level);
console[level]('[%cBetter%cDiscord:%s] %s', 'color: #3E82E5', '', `${module}${level === 'debug' ? '|DBG' : ''}`, message);
logs.push(`[${Vendor.moment().format('DD/MM/YY hh:mm:ss')}|${module}|${level}] ${message}`);
window.bdlogs = logs;
}
static logError(err) {
if (!err.module && !err.message) {
console.log(err);
return;
}
this.err(err.module, err.message);
}
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';
}
}
class Utils {
static overload(fn, cb) {
const orig = fn;
return function(...args) {
orig(...args);
cb(...args);
}
}
static async tryParseJson(jsonString) {
try {
return JSON.parse(jsonString);
} catch (err) {
throw ({
'message': 'Failed to parse json',
err
});
}
}
}
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}`,
err
});
if (!stats.isFile()) return reject({
'message': `Not a file: ${path}`,
stats
});
resolve();
});
});
}
static async directoryExists(path) {
return new Promise((resolve, reject) => {
fs.stat(path, (err, stats) => {
if (err) return reject({
'message': `Directory does not exist: ${path}`,
err
});
if (!stats.isDirectory()) return reject({
'message': `Not a directory: ${path}`,
stats
});
resolve();
});
});
}
static async readFile(path) {
try {
await this.fileExists(path);
} 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}`,
err
});
resolve(data);
});
});
}
static async writeFile(path, data) {
return new Promise((resolve, reject) => {
fs.writeFile(path, data, err => {
if (err) return reject(err);
resolve();
});
});
}
static async readJsonFromFile(path) {
let readFile;
try {
readFile = await this.readFile(path);
} catch (err) {
throw (err);
}
try {
const parsed = await Utils.tryParseJson(readFile);
return parsed;
} catch (err) {
throw (Object.assign(err, { path }));
}
}
static async writeJsonToFile(path, json) {
return this.writeFile(path, JSON.stringify(json));
}
static async readDir(path) {
try {
await this.directoryExists(path);
return new Promise((resolve, reject) => {
fs.readdir(path, (err, files) => {
if (err) return reject(err);
resolve(files);
});
});
} catch (err) {
throw err;
}
}
}
class Filters {
static byProperties(props, selector = m => m) {
return module => {
const component = selector(module);
if (!component) return false;
return props.every(property => component[property] !== undefined);
};
}
static byPrototypeFields(fields, selector = m => m) {
return module => {
const component = selector(module);
if (!component) return false;
if (!component.prototype) return false;
return fields.every(field => component.prototype[field] !== undefined);
};
}
static byCode(search, selector = m => m) {
return module => {
const method = selector(module);
if (!method) return false;
return method.toString().search(search) !== -1;
};
}
static byDisplayName(name) {
return module => {
return module && module.displayName === name;
};
}
static combine(...filters) {
return module => {
return filters.every(filter => filter(module));
};
}
}
module.exports = { Logger, Utils, FileUtils, Filters };

View File

@ -1,30 +0,0 @@
/**
* BetterDiscord Client Utils Module
* 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.
*/
const {WebpackModules} = require('./webpackmodules');
const jQuery = require('jquery');
class Vendor {
static get jQuery() {
return jQuery;
}
static get $() {
return jQuery;
}
static get moment() {
return WebpackModules.getModuleByName('Moment');
}
}
module.exports = {Vendor};

View File

@ -1,228 +0,0 @@
/**
* BetterDiscord Client WebpackModules Module
* 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.
*/
const { Filters } = require('./utils');
const KnownModules = {
React: Filters.byProperties(['createElement', 'cloneElement']),
ReactDOM: Filters.byProperties(['render', 'findDOMNode']),
/* Guild Info, Stores, and Utilities */
GuildStore: Filters.byProperties(['getGuild']),
SortedGuildStore: Filters.byProperties(['getSortedGuilds']),
SelectedGuildStore: Filters.byProperties(['getLastSelectedGuildId']),
GuildSync: Filters.byProperties(["getSyncedGuilds"]),
GuildInfo: Filters.byProperties(["getAcronym"]),
GuildChannelsStore: Filters.byProperties(['getChannels', 'getDefaultChannel']),
GuildMemberStore: Filters.byProperties(['getMember']),
MemberCountStore: Filters.byProperties(["getMemberCounts"]),
GuildEmojiStore: Filters.byProperties(['getEmojis']),
GuildActions: Filters.byProperties(['markGuildAsRead']),
GuildPermissions: Filters.byProperties(['getGuildPermissions']),
/* Channel Store & Actions */
ChannelStore: Filters.byProperties(['getChannels', 'getDMFromUserId']),
SelectedChannelStore: Filters.byProperties(['getLastSelectedChannelId']),
ChannelActions: Filters.byProperties(["selectChannel"]),
/* Current User Info, State and Settings */
UserInfoStore: Filters.byProperties(["getToken"]),
UserSettingsStore: Filters.byProperties(["guildPositions"]),
AccountManager: Filters.byProperties(['register', 'login']),
UserSettingsUpdater: Filters.byProperties(['updateRemoteSettings']),
OnlineWatcher: Filters.byProperties(['isOnline']),
CurrentUserIdle: Filters.byProperties(['getIdleTime']),
RelationshipStore: Filters.byProperties(['isBlocked']),
MentionStore: Filters.byProperties(["getMentions"]),
/* User Stores and Utils */
UserStore: Filters.byProperties(['getCurrentUser']),
UserStatusStore: Filters.byProperties(['getStatuses']),
UserTypingStore: Filters.byProperties(['isTyping']),
UserActivityStore: Filters.byProperties(['getActivity']),
UserNameResolver: Filters.byProperties(['getName']),
/* Emoji Store and Utils */
EmojiInfo: Filters.byProperties(['isEmojiDisabled']),
EmojiUtils: Filters.byProperties(['diversitySurrogate']),
EmojiStore: Filters.byProperties(['getByCategory', 'EMOJI_NAME_RE']),
/* Invite Store and Utils */
InviteStore: Filters.byProperties(["getInvites"]),
InviteResolver: Filters.byProperties(['findInvite']),
InviteActions: Filters.byProperties(['acceptInvite']),
/* Discord Objects & Utils */
DiscordConstants: Filters.byProperties(["Permissions", "ActivityTypes", "StatusTypes"]),
Permissions: Filters.byProperties(['getHighestRole']),
ColorConverter: Filters.byProperties(['hex2int']),
ColorShader: Filters.byProperties(['darken']),
ClassResolver: Filters.byProperties(["getClass"]),
ButtonData: Filters.byProperties(["ButtonSizes"]),
IconNames: Filters.byProperties(["IconNames"]),
/* Discord Messages */
HistoryUtils: Filters.byProperties(['transitionTo', 'replaceWith', 'getHistory']),
MessageActions: Filters.byProperties(['jumpToMessage', '_sendMessage']),
MessageQueue: Filters.byProperties(['enqueue']),
MessageParser: Filters.byProperties(['createMessage', 'parse', 'unparse']),
/* In-Game Overlay */
OverlayUserPopoutSettings: Filters.byProperties(['openUserPopout']),
OverlayUserPopoutInfo: Filters.byProperties(['getOpenedUserPopout']),
/* Experiments */
ExperimentStore: Filters.byProperties(['getExperimentOverrides']),
ExperimentsManager: Filters.byProperties(['isDeveloper']),
CurrentExperiment: Filters.byProperties(['getExperimentId']),
/* Images, Avatars and Utils */
ImageResolver: Filters.byProperties(["getUserAvatarURL"]),
ImageUtils: Filters.byProperties(['getSizedImageSrc']),
AvatarDefaults: Filters.byProperties(["getUserAvatarURL", "DEFAULT_AVATARS"]),
/* Drag & Drop */
DNDActions: Filters.byProperties(["beginDrag"]),
DNDSources: Filters.byProperties(["addTarget"]),
DNDObjects: Filters.byProperties(["DragSource"]),
/* Electron & Other Internals with Utils*/
ElectronModule: Filters.byProperties(["_getMainWindow"]),
Dispatcher: Filters.byProperties(['dirtyDispatch']),
PathUtils: Filters.byProperties(["hasBasename"]),
NotificationModule: Filters.byProperties(["showNotification"]),
RouterModule: Filters.byProperties(["Router"]),
APIModule: Filters.byProperties(["getAPIBaseURL"]),
AnalyticEvents: Filters.byProperties(["AnalyticEventConfigs"]),
KeyGenerator: Filters.byCode(/"binary"/),
Buffers: Filters.byProperties(['Buffer', 'kMaxLength']),
DeviceStore: Filters.byProperties(['getDevices']),
SoftwareInfo: Filters.byProperties(["os"]),
CurrentContext: Filters.byProperties(["setTagsContext"]),
/* Media Stuff (Audio/Video) */
MediaDeviceInfo: Filters.byProperties(["Codecs", "SUPPORTED_BROWSERS"]),
MediaInfo: Filters.byProperties(["getOutputVolume"]),
MediaEngineInfo: Filters.byProperties(['MediaEngineFeatures']),
VoiceInfo: Filters.byProperties(["EchoCancellation"]),
VideoStream: Filters.byProperties(["getVideoStream"]),
SoundModule: Filters.byProperties(["playSound"]),
/* Window, DOM, HTML */
WindowInfo: Filters.byProperties(['isFocused', 'windowSize']),
TagInfo: Filters.byProperties(['VALID_TAG_NAMES']),
DOMInfo: Filters.byProperties(['canUseDOM']),
HTMLUtils: Filters.byProperties(['htmlFor', 'sanitizeUrl']),
/* Locale/Location and Time */
LocaleManager: Filters.byProperties(['setLocale']),
Moment: Filters.byProperties(['parseZone']),
LocationManager: Filters.byProperties(["createLocation"]),
Timestamps: Filters.byProperties(["fromTimestamp"]),
/* Strings and Utils */
Strings: Filters.byProperties(["TEXT", "TEXTAREA_PLACEHOLDER"]),
StringFormats: Filters.byProperties(['a', 'z']),
StringUtils: Filters.byProperties(["toASCII"]),
/* URLs and Utils */
URLParser: Filters.byProperties(['Url', 'parse']),
ExtraURLs: Filters.byProperties(['getArticleURL']),
/* DOM/React Components */
/* ==================== */
UserSettingsWindow: Filters.byProperties(['open', 'updateAccount']),
LayerManager: Filters.byProperties(['popLayer', 'pushLayer']),
/* Modals */
ModalStack: Filters.byProperties(['push', 'update', 'pop', 'popWithKey']),
UserProfileModals: Filters.byProperties(['fetchMutualFriends', 'setSection']),
ConfirmModal: Filters.byPrototypeFields(['handleCancel', 'handleSubmit', 'handleMinorConfirm']),
/* Popouts */
PopoutStack: Filters.byProperties(['open', 'close', 'closeAll']),
PopoutOpener: Filters.byProperties(['openPopout']),
EmojiPicker: Filters.byPrototypeFields(['onHoverEmoji', 'selectEmoji']),
/* Context Menus */
ContextMenuActions: Filters.byCode(/CONTEXT_MENU_CLOSE/, c => c.close),
ContextMenuItemsGroup: Filters.byCode(/itemGroup/),
ContextMenuItem: Filters.byCode(/\.label\b.*\.hint\b.*\.action\b/),
/* In-Message Links */
ExternalLink: Filters.byCode(/\.trusted\b/)
};
const Cache = {};
class WebpackModules {
/* Synchronous */
static getModuleByName(name, fallback) {
if (Cache.hasOwnProperty(name)) return Cache[name];
if (KnownModules.hasOwnProperty(name)) fallback = KnownModules[name];
if (!fallback) return null;
return Cache[name] = this.getModule(fallback, true);
}
static getModuleByDisplayName(name) {
return this.getModule(Filters.byDisplayName(name), true);
}
static getModuleByRegex(regex, first = true) {
return this.getModule(Filters.byCode(regex), first);
}
static getModuleByPrototypes(prototypes, first = true) {
return this.getModule(Filters.byPrototypeFields(prototypes), first);
}
static getModuleByProps(props, first = true) {
return this.getModule(Filters.byProperties(props), first);
}
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 ? null : rm;
}
static getAllModules() {
const id = 'bd-webpackmodules';
const __webpack_require__ = window['webpackJsonp'](
[],
{
[id]: (module, exports, __webpack_require__) => exports.default = __webpack_require__
},
[id]).default;
delete __webpack_require__.m[id];
delete __webpack_require__.c[id];
return __webpack_require__.c;
}
}
module.exports = { WebpackModules };

View File

@ -1,11 +0,0 @@
export { Global } from './core/global';
export { Logger, Utils, FileUtils } from './core/utils';
export { PluginManager, Plugin } from './core/pluginmanager';
export { Pluging } from './core/plugin';
export { BDIpc } from './core/bdipc';
export { WebpackModules } from './core/webpackmodules';
export { Events } from './core/events';
export { SocketProxy } from './core/discordsocket';
export { CssEditor } from './core/csseditor';
export { Settings } from './core/settings';
export { Vendor } from './core/vendor';

View File

@ -1,96 +0,0 @@
<template src="./templates/BdSettings.html"></template>
<script>
const { Settings } = require('../../');
/*Imports*/
import { SidebarView, Sidebar, SidebarItem, ContentColumn } from './sidebar';
import { CoreSettings, UISettings, EmoteSettings, PluginsView, CssEditorView } from './bd';
const components = { SidebarView, Sidebar, SidebarItem, ContentColumn, CoreSettings, UISettings, EmoteSettings, PluginsView, CssEditorView };
/*Constants*/
const sidebarItems = [
{ text: 'Internal', _type: 'header' },
{ id: 0, contentid: "core", text: 'Core', active: false, _type: 'button' },
{ id: 1, contentid: "ui", text: 'UI', active: false, _type: 'button' },
{ id: 2, contentid: "emotes", text: 'Emotes', active: false, _type: 'button' },
{ id: 3, contentid: "css", text: 'CSS Editor', active: false, _type: 'button' },
{ text: 'External', _type: 'header' },
{ id: 4, contentid: "plugins", text: 'Plugins', active: false, _type: 'button' },
{ id: 5, contentid: "themes", text: 'Themes', active: false, _type: 'button' }
];
/*Methods*/
function itemOnClick(id) {
if (this.animating || id === this.activeIndex) return;
if (this.activeIndex >= 0) this.sidebarItems.find(item => item.id === this.activeIndex).active = false;
this.sidebarItems.find(item => item.id === id).active = true;
this.animating = true;
this.lastActiveIndex = this.activeIndex;
this.activeIndex = id;
if (this.first) {
this.first = false;
}
setTimeout(() => {
this.animating = false;
this.lastActiveIndex = -1;
}, 400);
}
function animatingContent(s) {
const item = this.sidebarItems.find(item => item.contentid === s);
if (!item) return false;
return item.id === this.lastActiveIndex;
}
function activeContent(s) {
const item = this.sidebarItems.find(item => item.contentid === s);
if (!item) return false;
return item.id === this.activeIndex;
}
function enableSetting(cat, id) {
switch (cat) {
case 'core':
return this.coreSettings.find(setting => setting.id === id).enabled = true;
}
}
function disableSetting(cat, id) {
switch (cat) {
case 'core':
return this.coreSettings.find(setting => setting.id === id).enabled = false;
}
}
const methods = { itemOnClick, animatingContent, activeContent, enableSetting, disableSetting };
export default {
components,
props: ['active', 'close'],
methods,
data() {
return {
sidebarItems,
activeIndex: -1,
lastActiveIndex: -1,
animating: false,
first: true,
settings: Settings.getSettings
}
},
computed: {
coreSettings: function () {
return this.settings.find(settingset => settingset.id === 'core').settings;
}
},
updated: function () {
if (this.active) return;
this.activeIndex = this.lastActiveIndex = -1;
this.sidebarItems.forEach(item => item.active = false);
}
}
</script>

View File

@ -1,54 +0,0 @@
<template src="./templates/BdSettingsWrapper.html"></template>
<script>
const { Events } = require('../../');
/*Imports*/
import BdSettings from './BdSettings.vue';
const components = { BdSettings };
/*Methods*/
function showSettings() {
if (!this.loaded) return;
this.active = true;
}
function hideSettings() {
this.active = false;
}
const methods = { showSettings, hideSettings };
let globalKeyListener;
export default {
components,
methods,
data() {
return {
loaded: false,
active: false,
platform: global.process.platform
}
},
created: function () {
Events.on('ready', e => {
this.loaded = true;
});
window.addEventListener('keyup', globalKeyListener = e => {
if (this.active && e.which === 27) {
this.hideSettings();
return;
}
if (!e.metaKey && !e.ctrlKey || e.key !== 'b') return;
!this.active ? this.showSettings() : this.hideSettings();
e.stopImmediatePropagation();
});
},
destroyed: function () {
if (globalKeyListener) window.removeEventListener('keyup', globalKeyListener);
}
}
</script>

View File

@ -1,29 +0,0 @@
<template>
<SettingsWrapper headertext="Core Settings">
<div class="bd-form-item" v-for="setting in settings" :key="setting.id">
<SettingSwitch key="setting.id" :setting="setting" :onClick="settingOnClick" :disabled="setting.disabled"/>
<div class="bd-form-divider"/>
</div>
</SettingsWrapper>
</template>
<script>
/*Imports*/
import { SettingsWrapper } from './';
import { SettingSwitch } from '../generic';
const components = { SettingsWrapper, SettingSwitch };
/*Methods*/
function settingOnClick(setting) {
if (setting.enabled) return this.disableSetting('core', setting.id);
this.enableSetting('core', setting.id);
}
const methods = { settingOnClick };
export default {
components,
methods,
props: ['settings', 'enableSetting', 'disableSetting']
}
</script>

View File

@ -1,32 +0,0 @@
<template src="./templates/CssEditor.html"></template>
<script>
const { CssEditor } = require('../../../');
/*Imports*/
import { SettingsWrapper } from './';
import { SettingSwitch, FormButton } from '../generic';
const components = { SettingsWrapper, SettingSwitch, FormButton };
function openInternalEditor() {
CssEditor.show();
}
function settingClicked() {
this.dummySetting.checked = !this.dummySetting.checked;
}
export default {
components,
methods: { openInternalEditor, settingClicked },
data() {
return {
dummySetting: {
title: "Live Update",
hint: "Automatically update client css when saved.",
checked: true
}
}
}
}
</script>

View File

@ -1,14 +0,0 @@
<template>
<SettingsWrapper headertext="Emote Settings">
</SettingsWrapper>
</template>
<script>
/*Imports*/
import { SettingsWrapper } from './';
const components = { SettingsWrapper };
export default {
components
}
</script>

View File

@ -1,37 +0,0 @@
<template src="./templates/PluginCard.html"></template>
<script>
/*Imports*/
import { Button, ButtonGroup, SettingSwitch } from '../generic';
import MiSettings from 'vue-material-design-icons/settings.vue';
import MiReload from 'vue-material-design-icons/refresh.vue';
import MiEdit from 'vue-material-design-icons/pencil.vue';
import MiDelete from 'vue-material-design-icons/delete.vue';
const components = { MiSettings, Button, ButtonGroup, SettingSwitch, MiReload, MiEdit, MiDelete };
/*Methods*/
function showSettings() {
this.settingsOpen = true;
}
function editPlugin() {
try {
window.require('electron').shell.openItem(this.plugin.pluginPath);
} catch (err) {
console.log(err);
}
}
const methods = { editPlugin };
export default {
props: ['plugin', 'togglePlugin', 'reloadPlugin', 'showSettings'],
components,
name: "PluginCard",
methods,
data() {
return {
'settingsOpen': false
}
}
}
</script>

View File

@ -1,77 +0,0 @@
<template src="./templates/PluginSettingsModal.html"></template>
<script>
/*Imports*/
import { Modal } from '../generic';
const components = { Modal };
/*Methods*/
function textInputKd(e) {
console.log(e);
this.changed = true;
}
const methods = { textInputKd };
export default {
props: ['plugin'],
data() {
return {
'changed': false
}
},
components,
methods
}
</script>
<style>
.bd-modal-inner {
position: relative;
}
.bd-modal-footer .footer-alert {
opacity: 0;
transform: translateY(100%);
height: 30px;
display: flex;
background: rgba(32, 34, 37, 0.9);
position: absolute;
bottom: 20px;
left: 20px;
right: 20px;
box-shadow: 0 2px 10px 0 rgba(0,0,0,.2);
padding: 10px 10px 10px 16px;
border-radius: 5px;
transition: all .3s ease-in-out;
}
.bd-modal-footer .footer-alert.bd-active {
opacity: 1;
transform: none;
}
.bd-modal-footer .footer-alert .footer-alert-text {
color: #FFF;
flex: 1 1 auto;
font-weight: 500;
}
.bd-modal-footer .footer-alert {
height: 30px;
color: #FFF;
}
.bd-modal-footer .footer-alert .footer-alert-text {
line-height: 30px;
}
.bd-modal-footer .footer-alert .bd-button {
width: 120px;
border-radius: 3px;
}
.bd-modal-footer .footer-alert .bd-reset-button {
height: 30px;
line-height: 30px;
margin-right: 10px;
}
</style>

View File

@ -1,68 +0,0 @@
<template src="./templates/PluginsView.html"></template>
<script>
const { PluginManager, Plugin } = require('../../../');
/*Imports*/
import { SettingsWrapper } from './';
import PluginSettingsModal from './PluginSettingsModal.vue';
import PluginCard from './PluginCard.vue';
import Refresh from 'vue-material-design-icons/refresh.vue';
const components = { SettingsWrapper, PluginCard, Refresh, PluginSettingsModal };
/*Variables*/
/*Methods*/
async function refreshLocalPlugins() {
try {
await PluginManager.refreshPlugins();
this.$forceUpdate();
} catch (err) {
}
}
function showLocal() {
this.local = true;
}
function showOnline() {
this.local = false;
}
//TODO Display error if plugin fails to start/stop
function togglePlugin(plugin) {
if (plugin.enabled) {
this.pluginManager.stopPlugin(plugin);
} else {
this.pluginManager.startPlugin(plugin);
}
}
async function reloadPlugin(plugin) {
await this.pluginManager.reloadPlugin(plugin.name);
this.$forceUpdate();
}
function showSettings(plugin) {
this.settingsOpen = plugin;
}
const methods = { showLocal, showOnline, refreshLocalPlugins, togglePlugin, reloadPlugin, showSettings };
export default {
components,
data() {
return {
local: true,
pluginManager: PluginManager,
settingsOpen: null
}
},
computed: {
localPlugins: function () {
return this.pluginManager.plugins;
}
},
methods
}
</script>

View File

@ -1,11 +0,0 @@
<template src="./templates/SettingsWrapper.html"></template>
<script>
/*Imports*/
import { ScrollerWrap } from '../generic';
const components = { ScrollerWrap };
export default {
components,
props: ['headertext']
}
</script>

View File

@ -1,14 +0,0 @@
<template>
<SettingsWrapper headertext="UI Settings">
</SettingsWrapper>
</template>
<script>
/*Imports*/
import { SettingsWrapper } from './';
const components = { SettingsWrapper };
export default {
components
}
</script>

View File

@ -1,8 +0,0 @@
export { default as SettingsWrapper } from './SettingsWrapper.vue';
export { default as CoreSettings } from './CoreSettings.vue';
export { default as UISettings } from './UISettings.vue';
export { default as EmoteSettings } from './EmoteSettings.vue';
export { default as PluginsView } from './PluginsView.vue';
export { default as PluginCard } from './PluginCard.vue';
export { default as CssEditorView } from './CssEditor.vue';
export { default as PluginSettingsModal } from './PluginSettingsModal.vue';

View File

@ -1,33 +0,0 @@
<SettingsWrapper headertext="CSS Editor">
<div class="bd-css-editor">
<div class="bd-form-item">
<h5>Custom Editor</h5>
<div class="bd-form-warning">
<div class="bd-text">Custom Editor is not installed!</div>
<FormButton>
Install
</FormButton>
</div>
<span style="color: #FFF; font-size: 12px; font-weight: 700;">*This is displayed if editor is not installed</span>
<FormButton :onClick="openInternalEditor">
Open
</FormButton>
</div>
<div class="bd-form-divider"></div>
<SettingSwitch :setting="dummySetting" :onClick="settingClicked"/>
<div class="bd-form-item">
<h5>System Editor</h5>
<FormButton>
Open
</FormButton>
</div>
<div class="bd-form-divider"></div>
<FormButton :onClick="settingClicked">
Enabled
</FormButton>
<FormButton :disabled="true">
<span>Disabled</span>
</FormButton>
<FormButton :loading="true"/>
</div>
</SettingsWrapper>

View File

@ -1,34 +0,0 @@
<div class="bd-plugin-card">
<div class="bd-plugin-header">
<span v-tooltip="'wat'">{{plugin.name}}</span>
<div class="bd-flex-spacer"/>
<label class="bd-switch-wrapper" @click="togglePlugin(plugin)">
<input type="checkbox" class="bd-switch-checkbox" />
<div class="bd-switch" :class="{'bd-checked': plugin.enabled}" />
</label>
</div>
<div class="bd-plugin-body">
<div class="bd-plugin-description">{{plugin.description}}</div>
<div class="bd-plugin-footer">
<div class="bd-plugin-extra">v{{plugin.version}} by {{plugin.authors.join(', ').replace(/,(?!.*,)/gmi, ' and')}}</div>
<div class="bd-controls">
<ButtonGroup>
<Button v-tooltip="'Settings'" v-if="plugin.hasSettings" :onClick="() => showSettings(plugin)">
<MiSettings/>
</Button>
<Button v-tooltip="'Reload'" :onClick="() => reloadPlugin(plugin)">
<MiReload/>
</Button>
<Button v-tooltip="'Edit'" :onClick="editPlugin">
<MiEdit/>
</Button>
<Button v-tooltip="'Uninstall'" type="err">
<MiDelete/>
</Button>
</ButtonGroup>
</div>
</div>
</div>
</div>

View File

@ -1,38 +0,0 @@
<Modal :headerText="plugin.name + ' Settings'" :close="() => { }">
<div slot="body" v-for="setting in plugin.pluginConfig" class="bd-plugin-settings-body">
<div class="bd-form-item">
<div v-if="setting.type === 'bool'" class="bd-setting-switch">
<div class="bd-title">
<h3>{{setting.text}}</h3>
<label class="bd-switch-wrapper">
<input type="checkbox" class="bd-switch-checkbox" />
<div class="bd-switch" :class="{'bd-checked': setting.value}" />
</label>
</div>
<div class="bd-hint">{{setting.hint}}</div>
</div>
<div v-else-if="setting.type === 'text'" class="bd-form-textinput">
<div class="bd-title">
<h3>{{setting.text}}</h3>
<div class="bd-textinput-wrapper">
<input type="text" v-model="setting.value" @keyup.stop @keydown="textInputKd"/>
</div>
</div>
<div class="bd-hint">{{setting.hint}}</div>
</div>
<div class="bd-form-divider"></div>
</div>
</div>
<div slot="footer">
<div class="footer-alert" :class="{'bd-active': changed}">
<div class="footer-alert-text">Unsaved changes</div>
<div class="bd-reset-button">Reset</div>
<div class="bd-button bd-ok">Save Changes</div>
</div>
</div>
</Modal>

View File

@ -1,29 +0,0 @@
<SettingsWrapper headertext="Plugins">
<div class="bd-flex bd-flex-col bd-pluginsView">
<div class="bd-flex bd-tabheader">
<div class="bd-flex-grow bd-button" :class="{'bd-active': local}" @click="showLocal">
<h3>Local</h3>
<div class="bd-material-button" @click="refreshLocalPlugins">
<refresh/>
</div>
</div>
<div class="bd-flex-grow bd-button" :class="{'bd-active': !local}" @click="showOnline">
<h3>Online</h3>
<div class="bd-material-button">
<refresh/>
</div>
</div>
</div>
<div v-if="local" class="bd-flex bd-flex-grow bd-flex-col bd-plugins-container bd-local-plugins">
<PluginCard v-for="plugin in localPlugins" :plugin="plugin" :key="plugin.id" :togglePlugin="togglePlugin" :reloadPlugin="reloadPlugin" :showSettings="showSettings"/>
</div>
<div v-if="!local" class="bd-spinner-container">
<div class="bd-spinner-2"></div>
</div>
</div>
<div v-if="settingsOpen !== null" class="bd-backdrop" @click="settingsOpen = null"></div>
<div v-if="settingsOpen !== null" class="bd-modal">
<PluginSettingsModal :plugin="settingsOpen"/>
</div>
</SettingsWrapper>

View File

@ -1,8 +0,0 @@
<div class="bd-settingsWrap">
<div class="bd-settingsWrap-header">{{headertext}}</div>
<ScrollerWrap>
<div class="bd-scroller">
<slot/>
</div>
</ScrollerWrap>
</div>

View File

@ -1,11 +0,0 @@
<template>
<div class="bd-button" :class="[{'bd-disabled': disabled}, {'bd-err': type === 'err'}]" @click="!disabled && !loading ? onClick() : null">
<div v-if="loading" class="bd-spinner-7"></div>
<slot v-else />
</div>
</template>
<script>
export default {
props: ['loading', 'disabled', 'type', 'onClick', 'type']
}
</script>

View File

@ -1,9 +0,0 @@
<template>
<div class="bd-button-group">
<slot/>
</div>
</template>
<script>
export default {
}
</script>

View File

@ -1,11 +0,0 @@
<template>
<div class="bd-form-button bd-button" :class="{'bd-disabled': disabled}" @click="!disabled && !loading ? onClick() : null">
<div v-if="loading" class="bd-spinner-7"></div>
<slot v-else />
</div>
</template>
<script>
export default {
props: ['loading', 'disabled', 'type', 'onClick']
}
</script>

View File

@ -1,24 +0,0 @@
<template>
<div class="bd-modal">
<div class="bd-modal-inner">
<div class="bd-modal-header">
<span>{{headerText}}</span>
<div class="bd-modal-x" @click="close">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 12 12"><g fill="none" fill-rule="evenodd"><path d="M0 0h12v12H0"></path><path class="fill" fill="#dcddde" d="M9.5 3.205L8.795 2.5 6 5.295 3.205 2.5l-.705.705L5.295 6 2.5 8.795l.705.705L6 6.705 8.795 9.5l.705-.705L6.705 6"></path></g></svg>
</div>
</div>
<div class="bd-modal-body">
<slot name="body"></slot>
</div>
<div class="bd-modal-footer">
<slot name="footer"></slot>
</div>
</div>
</div>
</template>
<script>
export default {
props: ['headerText', 'close']
}
</script>

View File

@ -1,10 +0,0 @@
<template>
<div class="bd-scroller-wrap" :class="{'bd-dark': dark}">
<slot />
</div>
</template>
<script>
export default {
props: ['dark']
}
</script>

View File

@ -1,17 +0,0 @@
<template>
<div class="bd-setting-switch" :class="{'bd-disabled': disabled}">
<div class="bd-title">
<h3>{{setting.title || setting.text}}</h3>
<label class="bd-switch-wrapper" @click="!disabled ? onClick(setting) : null">
<input type="checkbox" class="bd-switch-checkbox" />
<div class="bd-switch" :class="{'bd-checked': (setting.checked || setting.enabled || setting.value)}" />
</label>
</div>
<div class="bd-hint">{{setting.hint}}</div>
</div>
</template>
<script>
export default {
props: ['setting', 'onClick', 'disabled']
}
</script>

View File

@ -1,6 +0,0 @@
export { default as ScrollerWrap } from './ScrollerWrap.vue';
export { default as SettingSwitch } from './SettingSwitch.vue';
export { default as FormButton } from './FormButton.vue';
export { default as ButtonGroup } from './ButtonGroup.vue';
export { default as Button } from './Button.vue';
export { default as Modal } from './Modal.vue';

View File

@ -1,6 +0,0 @@
<template src="./templates/Button.html"></template>
<script>
export default {
props: ['item', 'onClick']
}
</script>

View File

@ -1,2 +0,0 @@
<template src="./templates/ContentColumn.html"></template>
<script> export default { }</script>

View File

@ -1,6 +0,0 @@
<template src="./templates/Header.html"></template>
<script>
export default {
props: ['item']
}
</script>

View File

@ -1,11 +0,0 @@
<template src="./templates/Item.html"></template>
<script>
/*Imports*/
import { SidebarHeader, SidebarButton } from './';
const components = { SidebarHeader, SidebarButton };
export default {
components,
props: ['item', 'onClick']
}
</script>

View File

@ -1,2 +0,0 @@
<template src="./templates/Sidebar.html"></template>
<script> export default { }</script>

View File

@ -1,11 +0,0 @@
<template src="./templates/View.html"></template>
<script>
/*Imports*/
import { ScrollerWrap } from '../generic';
const components = { ScrollerWrap };
export default {
components,
props: ['contentVisible', 'animating']
}
</script>

View File

@ -1,6 +0,0 @@
export { default as SidebarView } from './View.vue';
export { default as Sidebar } from './Sidebar.vue';
export { default as SidebarHeader } from './Header.vue';
export { default as SidebarButton } from './Button.vue';
export { default as SidebarItem } from './Item.vue';
export { default as ContentColumn } from './ContentColumn.vue';

View File

@ -1 +0,0 @@
<div class="bd-item" :class="{active: item.active}" @click="onClick(item.id)">{{item.text}}</div>

View File

@ -1,3 +0,0 @@
<div class="bd-content bd-content-column">
<slot />
</div>

View File

@ -1 +0,0 @@
<div class='bd-header'>{{item.text}}</div>

View File

@ -1,2 +0,0 @@
<SidebarButton v-if="item._type == 'button'" :item="item" :onClick="onClick" />
<SidebarHeader v-else-if="item._type == 'header'" :item="item" />

View File

@ -1,3 +0,0 @@
<div class="bd-sidebar bd-scroller">
<slot/>
</div>

View File

@ -1,12 +0,0 @@
<div class="bd-sidebar-view" :class="{active: contentVisible, animating: animating}">
<div class="bd-sidebar-region">
<div class="bd-settingsWrap">
<ScrollerWrap dark="true">
<slot name="sidebar"/>
</ScrollerWrap>
</div>
</div>
<div class="bd-content-region">
<slot name="content"/>
</div>
</div>

View File

@ -1,32 +0,0 @@
<div class="bd-settings" :class="{active: active}" @keyup="close">
<SidebarView :contentVisible="this.activeIndex >= 0" :animating="this.animating">
<Sidebar slot="sidebar">
<div class="bd-settings-x" @click="close">
<svg xmlns="http://www.w3.org/2000/svg" width="17" height="17" viewBox="0 0 12 12"><g fill="none" fill-rule="evenodd"><path d="M0 0h12v12H0"></path><path class="fill" fill="#dcddde" d="M9.5 3.205L8.795 2.5 6 5.295 3.205 2.5l-.705.705L5.295 6 2.5 8.795l.705.705L6 6.705 8.795 9.5l.705-.705L6.705 6"></path></g></svg>
<span>ESC</span>
</div>
<SidebarItem v-for="item in sidebarItems" :item="item" :key="item.id" :onClick="itemOnClick" />
<div class="bd-info">
<span>v2.0.0a by Jiiks/JsSucks</span>
</div>
</Sidebar>
<ContentColumn slot="content">
<div v-if="activeContent('core') || animatingContent('core')" :class="{active: activeContent('core'), animating: animatingContent('core')}">
<CoreSettings :settings="coreSettings" :enableSetting="enableSetting" :disableSetting="disableSetting"/>
</div>
<div v-if="activeContent('ui') || animatingContent('ui')" :class="{active: activeContent('ui'), animating: animatingContent('ui')}">
<UISettings />
</div>
<div v-if="activeContent('css') || animatingContent('css')" :class="{active: activeContent('css'), animating: animatingContent('css')}">
<CssEditorView />
</div>
<div v-if="activeContent('emotes') || animatingContent('emotes')" :class="{active: activeContent('emotes'), animating: animatingContent('emotes')}">
<EmoteSettings />
</div>
<div v-if="activeContent('plugins') || animatingContent('plugins')" :class="{active: activeContent('plugins'), animating: animatingContent('plugins')}">
<PluginsView />
</div>
</ContentColumn>
</SidebarView>
</div>

View File

@ -1,6 +0,0 @@
<div class="bd-settings-wrapper" :class="[{active: active}, 'platform-' + this.platform]">
<div class="bd-settings-button" :class="{active: active}" @click="showSettings">
<div class="bd-settings-button-btn" :class="[{'bd-loading': !loaded}]"></div>
</div>
<BdSettings :active="active" :close="hideSettings"/>
</div>

View File

@ -1,35 +0,0 @@
/**
* BetterDiscord Client Renderer
* 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.
*/
const { WebpackModules } = require('../');
class Renderer {
static async render(component, root) {
if (!this.React) this.React = await this.getReact();
if (!this.reactDom) this.reactDom = await this.getReactDom();
const React = this.React;
window.React = React;
this.reactDom.render(component, root);
}
static async getReact() {
const getReact = await WebpackModules.getModuleByProps(('createElement', 'cloneElement'));
return getReact[0].exports;
}
static async getReactDom() {
const getReactDom = await WebpackModules.getModuleByProps(('render', 'findDOMNode'));
return getReactDom[0].exports;
}
}
module.exports = { Renderer };

View File

@ -1,45 +0,0 @@
/**
* BetterDiscord Client UI Module
* 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.
*/
const $ = require('jquery');
const Vue = require('vue').default;
const VTooltip = require('v-tooltip');
const BdSettingsWrapper = (require('./components/BdSettingsWrapper.vue')).default;
class UI {
constructor() {
$('body').append($('<bdbody/>')
.append($('<div/>', {
id: 'bd-settings'
})).append($('<bdtooltips/>')));
Vue.use(VTooltip, {
defaultContainer: 'bdtooltips',
defaultClass: 'bd-tooltip',
defaultTargetClass: 'bd-has-tooltip',
defaultInnerSelector: '.bd-tooltip-inner',
defaultTemplate: '<div class="bd-tooltip"><span class="bd-tooltip-inner"></span></div>'
});
this.vueInstance = new Vue({
el: '#bd-settings',
components: { BdSettingsWrapper },
template: '<BdSettingsWrapper/>'
});
}
}
module.exports = { UI }

90
client/src/ui/dom.js Normal file
View File

@ -0,0 +1,90 @@
/**
* BetterDiscord Client DOM Module
* 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.
*/
class BdNode {
constructor(tag, className) {
this.element = document.createElement(tag);
if (className) this.element.className = className;
}
appendTo(e) {
const el = DOM.getElement(e);
if (!el) return null;
el.append(this.element);
return this.element;
}
prependTo(e) {
const el = DOM.getElement(e);
if (!el) return null;
el.prepend(this.element);
return this.element;
}
}
class DOM {
static get bdHead() {
return this.getElement('bd-head') || this.createElement('bd-head').appendTo('head');
}
static get bdBody() {
return this.getElement('bd-body') || this.createElement('bd-body').appendTo('body');
}
static get bdStyles() {
return this.getElement('bd-styles') || this.createElement('bd-styles').appendTo(this.bdHead);
}
static get bdThemes() {
return this.getElement('bd-themes') || this.createElement('bd-themes').appendTo(this.bdHead);
}
static get bdTooltips() {
return this.getElement('bd-tooltips') || this.createElement('bd-tooltips').appendTo(this.bdBody);
}
static getElement(e) {
if (e instanceof BdNode) return e.element;
if (e instanceof window.Node) return e;
if ('string' !== typeof e) return null;
return document.querySelector(e);
}
static createElement(tag = 'div', className = null) {
return new BdNode(tag, className);
}
static deleteStyle(id) {
const exists = this.getElement(`bd-styles > #${id}`);
if (exists) exists.remove();
}
static injectStyle(css, id) {
this.deleteStyle(id);
this.bdStyles.append(this.createStyle(css, id));
}
static deleteTheme(id) {
const exists = this.getElement(`bd-themes > #${id}`);
if (exists) exists.remove();
}
static injectTheme(css, id) {
this.deleteTheme(id);
this.bdThemes.append(this.createStyle(css, id));
}
static createStyle(css, id) {
const style = document.createElement('style');
style.id = id;
style.type = 'text/css';
style.appendChild(document.createTextNode(css));
return style;
}
}
export default DOM;

1
client/src/ui/index.js Normal file
View File

@ -0,0 +1 @@
export { default as DOM } from './dom';