This commit is contained in:
Jean Ouina 2020-05-16 23:24:51 +02:00
commit 2ec3765616
378 changed files with 44058 additions and 0 deletions

111
.gitignore vendored Normal file
View File

@ -0,0 +1,111 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and *not* Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# no build on github
builds
# must build typescript itself
dist

View File

@ -0,0 +1,54 @@
{
"extends": ["eslint:recommended", "plugin:react/recommended"],
"plugins": [
"react"
],
"env": {
"browser": true,
"node": true,
"jquery": true
},
"parserOptions": {
"ecmaVersion": 8,
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
}
},
"rules": {
"semi": 2,
"space-infix-ops": ["error", {"int32Hint": false}],
"quotes": ["error", "double", {"allowTemplateLiterals": true}],
"no-console": 0,
"brace-style": ["error", "stroustrup", {"allowSingleLine": true}],
"keyword-spacing": 2,
"no-else-return": 2,
"curly": ["error", "multi-line", "consistent"],
"dot-notation": 2,
"yoda": 2,
"linebreak-style": ["error", "windows"],
"quote-props": ["error", "consistent-as-needed", {"keywords": true}],
"object-curly-spacing": ["error", "never", { "objectsInObjects": false }],
"no-var": "error",
"prefer-const": "error",
"react/jsx-uses-react": "error",
"react/jsx-uses-vars": "error",
"react/prop-types": "off",
"react/jsx-no-target-blank": "error",
"react/jsx-key": "off"
},
"globals": {
"webpackJsonp": false,
"Proxy": false,
"Set": false,
"WeakMap": false,
"Promise": false,
"ace": false,
"Reflect": false,
"Array": false,
"DiscordNative": false,
"self": "off",
"name": "off",
"__non_webpack_require__": false
}
}

1
BetterDiscordApp/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
node_modules

21
BetterDiscordApp/LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2015-present Jiiks | 2017-present Zack Rauen
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
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.

159
BetterDiscordApp/README.md Normal file
View File

@ -0,0 +1,159 @@
# BandagedBD [![Patreon][patreon-badge]][patreon-link] [![Paypal][paypal-badge]][paypal-link]
[patreon-badge]: https://img.shields.io/endpoint.svg?url=https%3A%2F%2Fshieldsio-patreon.herokuapp.com%2FZerebos&style=flat-square
[patreon-link]: https://patreon.com/Zerebos
[paypal-badge]: https://img.shields.io/badge/Paypal-Donate!-%2300457C.svg?logo=paypal&style=flat-square
[paypal-link]: https://paypal.me/ZackRauen
BandagedBD (Bandaged BetterDiscord) is a fork of the original [BetterDiscord](https://github.com/Jiiks/BetterDiscordApp) by Jiiks. This has a number of improvements over the original. The original version has been unmaintained hence this fork existing. There have been attempts to rewrite the original that I have been and will continue to be involved in, but in the meantime I will continue to maintain and improve BBD.
# Installation
## Auto Installers
### Windows
Grab the `exe` file from [here](https://github.com/rauenzi/BetterDiscordApp/releases/latest/download/BandagedBD_Windows.exe).
### macOS/OS X
Grab the `zip` file from [here](https://github.com/rauenzi/BetterDiscordApp/releases/latest/download/BandagedBD_Mac.zip).
### Linux
See this [gist](https://gist.github.com/ObserverOfTime/d7e60eb9aa7fe837545c8cb77cf31172).
## Manual Installation
### Windows
1. Download and extract this: https://github.com/rauenzi/BetterDiscordApp/archive/injector.zip
2. Rename `BetterDiscordApp-injector` to `app`.
3. Go to `%localappdata%\Discord\`, and locate the directory with the largest version number (e.g. `app-0.0.306`).
4. Within `app-0.0.306` navigate to `resources`.
5. If an `app` folder already exists inside `resources`, delete it.
6. Move the `app` folder (the one you downloaded and renamed) inside of `resources`.
7. Fully quit Discord and restart it.
### macOS/OS X
1. Download and extract this: https://github.com/rauenzi/BetterDiscordApp/archive/injector.zip
2. Rename `BetterDiscordApp-injector` to `app`.
3. Go to `/Applications/`, right click `Discord.app` and select `Show Package Contents`.
4. Within `Discord.app` navigate to `Contents` -> `Resources`.
5. If an `app` folder already exists inside `Resources`, delete it.
6. Move the `app` folder (the one you downloaded and renamed) inside of `Resources`.
7. Fully quit Discord and restart it.
# FAQ
### What is this?
This is a client modification for Discord. It allows you to add plugins and themes to your client. Plugins can add functionality and useful features. Themes can completely change the look and feel of Discord.
BBD has some other built-in features such as Emotes from Twitch, FFZ, and BBTV, as well as an in-client server browser.
### Where can I get plugins and themes?
In our support servers we have channels with lists of official plugins and themes. Please note we do not have an official listing on a website and are **not affiliated with any of those websites**.
### Support Servers?
There are two: [The main server](https://discord.gg/0Tmfo5ZbORCRqbAd), and [the backup](https://discord.gg/2HScm8j).
# Supporters
These people have all subscribed to the `True Supporter` tier on Patreon to support BandagedBD.
<table>
<tr>
<td align="center">
<img src="https://cdn.discordapp.com/avatars/196098063092154368/90f1a7202955dac7a6c685cca3181ab1.webp" width="100px;" alt="Kraken"/><br />
<strong>Kraken</strong><br />
</td>
<td align="center">
<img src="https://cdn.discordapp.com/attachments/585514483699417089/585552300354043915/34959069_500_500.jpg" width="100px;" alt="SPHHAX"/><br />
<a href="http://sphh.ax/" target="_blank" rel="noreferrer noopener"><strong>SPHHAX</strong></a><br />
</td>
<td align="center">
<img src="https://cdn.discordapp.com/attachments/622954403262889995/622957122765848587/5364774.jpg" width="100px;" alt="DefCon42"/><br />
<a href="https://twitter.com/def_con42" target="_blank" rel="noreferrer noopener"><strong>DefCon42</strong></a><br />
</td>
<td align="center">
<img src="https://cdn.discordapp.com/avatars/629231564261425163/a_36cc7d2940b4ffb8a660b1076ab2087f.webp" width="100px;" alt="Justxn"/><br />
<strong>Justxn</strong><br />
</td>
<td align="center">
<img src="https://cdn.discordapp.com/attachments/682750073448169513/682763113296429087/definitely_not_the_dick_police.png" width="100px;" alt="monkey"/><br />
<a href="https://heartunderbla.de" target="_blank" rel="noreferrer noopener"><strong>monkey</strong></a><br />
</td>
</tr>
</table>
# Bandagers
These people have all subscribed to the `Bandager` tier on Patreon to support BandagedBD.
<table>
<tr>
<td align="center">
<img src="https://cdn.discordapp.com/avatars/332199319169925120/4709f8f0c9cb7ababd85459bf71848b9.png" width="50px;" alt="William JCM"/><br />
<a href="https://github.com/williamjcm" target="_blank" rel="noreferrer noopener"><strong>William JCM</strong></a>
</td>
<td align="center">
<img src="https://avatars0.githubusercontent.com/u/24623601" width="50px;" alt="NFLD99"/><br />
<a href="https://github.com/NFLD99" target="_blank" rel="noreferrer noopener"><strong>NFLD99</strong></a>
</td>
<td align="center">
<img src="https://avatars3.githubusercontent.com/u/20338746?s=460&u=d9ebab4f6f0f5221390bca1eaf8f191acd275afe&v=4" width="50px;" alt="Gibbu"/><br />
<a href="https://github.com/Gibbu" target="_blank" rel="noreferrer noopener"><strong>Gibbu</strong></a>
</td>
<td align="center">
<img src="https://i.postimg.cc/5NVxqMnb/Cute-Squid-Circle.png" width="50px;" alt="Tenuit"/><br />
<strong>Tenuit</strong>
</td>
</tr>
</table>
# Donors
These people have either donated or subscribed to the most basic patron tier to support BandagedBD.
<table>
<tr>
<td align="center">
<img src="https://cdn.discordapp.com/avatars/284122164582416385/ebaa1b63191ce70e48ae24f32f452773.webp" width="25px;" /><br />
<strong>aetheryx</strong>
</td>
<td align="center">
<img src="https://cdn.discordapp.com/avatars/216782345779281921/d4b651b606f108cd2f96a19af68f942f.png" width="25px;" /><br />
<strong>JBeauDee</strong>
</td>
<td align="center">
<img src="https://cdn.discordapp.com/avatars/261673576216789004/31d590fb92329e270a6225a13d500c1d.png" width="25px;" /><br />
<strong>vantiss</strong>
</td>
<td align="center">
<img src="https://cdn.discordapp.com/avatars/122204411962327043/7f44a9b036b9e2691f4e81d9e34a78b4.webp" width="25px;" /><br />
<strong>xstefen</strong>
</td>
<td align="center">
<img src="https://cdn.discordapp.com/avatars/219400174869413888/7c88015869990ba97b614b1ac784f8e8.png" width="25px;" /><br />
<strong>『Sorey』</strong>
</td>
<td align="center">
<img src="https://cdn.discordapp.com/avatars/95263213842608128/5024b83e1bff3096d7fc93e8de09d582.gif" width="25px;" /><br />
<strong>LiVeR</strong>
</td>
<td align="center">
<img src="https://cdn.discordapp.com/avatars/144458450192171008/13a3e66d73d216974504b8aad257b7b4.png" width="25px;" /><br />
<strong>SweetLilyCake</strong>
</td>
<td align="center">
<img src="https://cdn.discordapp.com/avatars/398951709336010793/eb6f63eb2f3a5102fb900e60d1a26cdc.png" width="25px;" /><br />
<strong>GameKuchen</strong>
</td>
<td align="center">
<img src="https://i.imgur.com/qrWcKfH.png" width="25px;" /><br />
<strong>Lozo</strong>
</td>
<td align="center">
<img src="https://media.discordapp.net/attachments/575576868166828032/692136786893340752/pfp.gif" width="25px;" /><br />
<strong>Akira</strong>
</td>
</tr>
</table>

File diff suppressed because it is too large Load Diff

1
BetterDiscordApp/css/main.min.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,33 @@
const gulp = require("gulp");
const rename = require("gulp-rename");
const minify = require("gulp-babel-minify");
const csso = require("gulp-csso");
gulp.task("minify-js", minifyJS);
gulp.task("minify-css", minifyCSS);
gulp.task("watch-js", function() {
return gulp.watch(["./js/main.js"], minifyJS);
});
gulp.task("watch-css", function() {
return gulp.watch(["./css/main.css"], minifyCSS);
});
gulp.task("watch", function() {
return gulp.watch(["./js/main.js", "./css/main.css"], gulp.series(minifyJS, minifyCSS));
});
function minifyJS() {
return gulp.src("./js/main.js")
.pipe(minify({mangle: {keepClassName: true}}))
.pipe(rename("main.min.js"))
.pipe(gulp.dest("./js"));
}
function minifyCSS() {
return gulp.src("./css/main.css")
.pipe(csso({restructure: false}))
.pipe(rename("main.min.css"))
.pipe(gulp.dest("./css"));
}

933
BetterDiscordApp/js/main.js Normal file

File diff suppressed because one or more lines are too long

1
BetterDiscordApp/js/main.min.js vendored Normal file

File diff suppressed because one or more lines are too long

7588
BetterDiscordApp/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,48 @@
{
"name": "bandagedbd",
"version": "0.2.3",
"description": "Enhances Discord adding functionality and themes.",
"main": "js/main.js",
"scripts": {
"build": "webpack --progress --colors",
"watch": "webpack --progress --colors --watch",
"build-prod": "webpack --progress --colors --mode production -o js/main.min.js --devtool none",
"test": "echo \"Error: no test specified\" && exit 1",
"minify": "gulp minify-js && gulp minify-css",
"minify-js": "gulp minify-js",
"minify-css": "gulp minify-css",
"watch-js": "gulp watch-js",
"watch-css": "gulp watch-css"
},
"repository": {
"type": "git",
"url": "git+https://github.com/rauenzi/BetterDiscordApp.git"
},
"author": "rauenzi",
"license": "MIT",
"bugs": {
"url": "https://github.com/rauenzi/BetterDiscordApp/issues"
},
"homepage": "https://github.com/rauenzi/BetterDiscordApp#readme",
"devDependencies": {
"@babel/core": "^7.3.4",
"@babel/preset-env": "^7.3.4",
"@babel/preset-react": "^7.0.0",
"@babel/register": "^7.0.0",
"babel-loader": "^8.0.6",
"circular-dependency-plugin": "^5.0.2",
"gulp": "^4.0.0",
"gulp-babel-minify": "^0.5.1",
"gulp-csso": "^3.0.1",
"gulp-rename": "^1.4.0",
"webpack": "^4.29.6",
"webpack-cli": "^3.2.3"
},
"dependencies": {
"mkdirp": "^1.0.4",
"react": "^16.13.1",
"request": "^2.88.2",
"rimraf": "^3.0.2",
"yauzl": "^2.10.0"
}
}

View File

@ -0,0 +1,116 @@
// var settingsPanel, voiceMode, pluginModule, themeModule, dMode, publicServersModule, mainCore, BDV2;
export const minimumDiscordVersion = "0.0.306";
export const currentDiscordVersion = (window.DiscordNative && window.DiscordNative.remoteApp && window.DiscordNative.remoteApp.getVersion && window.DiscordNative.remoteApp.getVersion()) || "0.0.306";
export const minSupportedVersion = "0.3.0";
export const bbdVersion = "0.3.4";
export const bbdChangelog = {
description: "LightCord Edition.",
changes: [
{
title: "What's New?",
items: [
"**LightCord** is now using BandagedBD. That means all plugins you were using can be used too !",
"**Window Transparency** changes were made to more compatible with external window managers and addons like Glasscord.",
"Initialization sequence has once again been changed slightly to hopefully improve loading times.",
"We removed emotes. That's sad for people who were actually using it, but it was leading to problems like this https://github.com/rauenzi/BetterDiscordApp/issues/348."
]
},
{
title: "Bug Fixes",
type: "fixed",
items: [
"Some fixes related to showing modals in the `BdApi`."
]
}
]
};
export const settings = {
"Custom css live update": {id: "bda-css-0", info: "", implemented: true, hidden: true, cat: "core"},
"Custom css auto udpate": {id: "bda-css-1", info: "", implemented: true, hidden: true, cat: "core"},
"BetterDiscord Blue": {id: "bda-gs-b", info: "Replace Discord blue with BD Blue", implemented: false, hidden: false, cat: "core"},
/* Core */
/* ====== */
"Public Servers": {id: "bda-gs-1", info: "Display public servers button", implemented: true, hidden: false, cat: "core", category: "modules"},
"Minimal Mode": {id: "bda-gs-2", info: "Hide elements and reduce the size of elements.", implemented: true, hidden: false, cat: "core", category: "modules"},
"Voice Mode": {id: "bda-gs-4", info: "Only show voice chat", implemented: true, hidden: false, cat: "core", category: "modules"},
"Hide Channels": {id: "bda-gs-3", info: "Hide channels in minimal mode", implemented: true, hidden: false, cat: "core", category: "modules"},
"Dark Mode": {id: "bda-gs-5", info: "Make certain elements dark by default(wip)", implemented: true, hidden: false, cat: "core", category: "modules"},
"Voice Disconnect": {id: "bda-dc-0", info: "Disconnect from voice server when closing Discord", implemented: true, hidden: false, cat: "core", category: "modules"},
"24 Hour Timestamps": {id: "bda-gs-6", info: "Replace 12hr timestamps with proper ones", implemented: true, hidden: false, cat: "core", category: "modules"},
"Colored Text": {id: "bda-gs-7", info: "Make text color the same as role color", implemented: true, hidden: false, cat: "core", category: "modules"},
"Normalize Classes": {id: "fork-ps-4", info: "Adds stable classes to elements to help themes. (e.g. adds .da-channels to .channels-Ie2l6A)", implemented: true, hidden: false, cat: "core", category: "modules"},
/* Content */
"Content Error Modal": {id: "fork-ps-1", info: "Shows a modal with plugin/theme errors", implemented: true, hidden: false, cat: "core", category: "content manager"},
"Show Toasts": {id: "fork-ps-2", info: "Shows a small notification for important information", implemented: true, hidden: false, cat: "core", category: "content manager"},
"Scroll To Settings": {id: "fork-ps-3", info: "Auto-scrolls to a plugin's settings when the button is clicked (only if out of view)", implemented: true, hidden: false, cat: "core", category: "content manager"},
"Automatic Loading": {id: "fork-ps-5", info: "Automatically loads, reloads, and unloads plugins and themes", implemented: true, hidden: false, cat: "core", category: "content manager"},
/* Developer */
"Developer Mode": {id: "bda-gs-8", info: "Developer Mode", implemented: true, hidden: false, cat: "core", category: "developer settings"},
"Copy Selector": {id: "fork-dm-1", info: "Adds a \"Copy Selector\" option to context menus when developer mode is active", implemented: true, hidden: false, cat: "core", category: "developer settings"},
"React DevTools": {id: "reactDevTools", info: "Adds react developer tools to the devtools. Must be installed in Google Chrome on your pc.", implemented: true, hidden: true, cat: "core", category: "developer settings"},
/** LightCord */
"Disable BetterDiscord": {id: "bd-disable", info: "Disable Betterdiscord (plugins, themes, etc).", implemented: true, hidden: false, cat: "lightcord", category: "Lightcord"},
"Calling Ring Beat": {id: "lightcord-2", info: "Enable Discord's special calling beat.", implemented: true, hidden: false, cat: "lightcord", category: "Lightcord"},
"Developer Mode": {id: "lightcord-1", info: "Enable Discord's Internal Developer Options. This allow the \"Experiments\" tab and the \"Developer Options\" tab. (must close and reopen settings)", implemented: true, hidden: false, cat: "lightcord", category: "Lightcord"},
/** RichPresence */
"Enable": {id: "lightcord-presence-1", info: "Enable RichPresence below.", implemented: true, hidden: false, cat: "status"}
};
export const defaultCookie = {
"bda-gs-1": true,
"bda-gs-2": false,
"bda-gs-3": false,
"bda-gs-4": false,
"bda-gs-5": true,
"bda-gs-6": false,
"bda-gs-7": false,
"bda-gs-8": false,
"bda-es-0": true,
"bda-es-1": true,
"bda-es-2": true,
"bda-es-4": false,
"bda-es-6": true,
"bda-es-7": true,
"bda-gs-b": false,
"bda-es-8": true,
"bda-dc-0": false,
"bda-css-0": false,
"bda-css-1": false,
"bda-es-9": true,
"fork-dm-1": false,
"fork-ps-1": true,
"fork-ps-2": true,
"fork-ps-3": true,
"fork-ps-4": true,
"fork-ps-5": true,
"fork-es-2": false,
"fork-es-3": true,
"fork-wp-1": false,
"fork-wp-2": false,
"fork-beta": true,
"reactDevTools": false,
"lightcord-1": false,
"lightcord-2": true,
"lightcord-presence-1": false
};
export const settingsCookie = {};
export const bdpluginErrors = [];
export const bdthemeErrors = []; // define for backwards compatibility
export const bdConfig = Object.create(BetterDiscordConfig);
export const bdthemes = {};
export const bdplugins = {};
export const pluginCookie = {};
export const themeCookie = {};

View File

@ -0,0 +1,102 @@
import localStorageFix from "./localStorageFix";
import loadingIcon from "./loadingIcon";
localStorageFix();
loadingIcon();
const deprecateGlobal = (key, value) => {
// value = typeof(value) !== "object" ? value : new Proxy(value, {
// get: function(obj, mod) {
// if (!obj.hasOwnProperty(mod)) return undefined;
// return obj[mod];
// },
// set: function(obj, mod) {
// if (obj.hasOwnProperty(mod)) return Utils.err("Deprecated Global", "Trying to overwrite deprecated BD globals");
// }
// });
Object.defineProperty(window, key, {
get() {
Utils.warn("Deprecated Global", `"${key}" will be removed in future versions. Please only use BdApi.`);
return value;
}
});
};
import * as Globals from "./0globals";
const globalKeys = Object.keys(Globals);
for (const key of globalKeys) deprecateGlobal(key, Globals[key]);
import BdApi from "./modules/bdApi";
import BDV2 from "./modules/v2";
import pluginModule from "./modules/pluginModule";
import themeModule from "./modules/themeModule";
import Utils from "./modules/utils";
import BDEvents from "./modules/bdEvents";
import settingsPanel from "./modules/settingsPanel";
import DataStore from "./modules/dataStore";
import ContentManager from "./modules/contentManager";
import ClassNormalizer from "./modules/classNormalizer";
deprecateGlobal("BDV2", BDV2);
deprecateGlobal("pluginModule", pluginModule);
deprecateGlobal("themeModule", themeModule);
deprecateGlobal("Utils", Utils);
deprecateGlobal("BDEvents", BDEvents);
deprecateGlobal("settingsPanel", settingsPanel);
deprecateGlobal("DataStore", DataStore);
deprecateGlobal("ContentManager", ContentManager);
deprecateGlobal("ClassNormalizer", ClassNormalizer);
window.BdApi = BdApi;
import Core from "./modules/core";
deprecateGlobal("mainCore", Core);
export default class CoreWrapper {
constructor(bdConfig) {
Core.setConfig(bdConfig);
}
init() {
// deprecateGlobal("mainCore", this.mainCore);
Core.init();
}
}
// function patchModuleLoad() {
// const namespace = "betterdiscord";
// const prefix = `${namespace}/`;
// const Module = require("module");
// const load = Module._load;
// // const resolveFilename = Module._resolveFilename;
// Module._load = function(request) {
// if (request === namespace || request.startsWith(prefix)) {
// const requested = request.substr(prefix.length);
// if (requested == "api") return BdApi;
// }
// return load.apply(this, arguments);
// };
// // Module._resolveFilename = function (request, parent, isMain) {
// // if (request === "betterdiscord" || request.startsWith("betterdiscord/")) {
// // const contentPath = PluginManager.getPluginPathByModule(parent);
// // if (contentPath) return request;
// // }
// // return resolveFilename.apply(this, arguments);
// // };
// return function() {
// Module._load = load;
// };
// }
// patchModuleLoad();
// var settingsPanel, voiceMode,, dMode, publicServersModule;
// var bdConfig = null;
require("request")// just in cache so plugin can require it too

View File

@ -0,0 +1,6 @@
export default () => {
const v2Loader = document.createElement("div");
v2Loader.className = "bd-loaderv2";
v2Loader.title = "BandagedBD is loading...";
document.body.appendChild(v2Loader);
};

View File

@ -0,0 +1,31 @@
export default function() {
const contentWindowGetter = Object.getOwnPropertyDescriptor(HTMLIFrameElement.prototype, "contentWindow").get;
Object.defineProperty(HTMLIFrameElement.prototype, "contentWindow", {
get: function () {
const contentWindow = Reflect.apply(contentWindowGetter, this, arguments);
return new Proxy(contentWindow, {
getOwnPropertyDescriptor: function(obj, prop) {
if (prop === "localStorage") return undefined;
return Object.getOwnPropertyDescriptor(obj, prop);
},
get: function(obj, prop) {
if (prop === "localStorage") return null;
const val = obj[prop];
if (typeof val === "function") return val.bind(obj);
return val;
}
});
}
});
// Prevent interception by patching Reflect.apply and Function.prototype.bind
Object.defineProperty(Reflect, "apply", {value: Reflect.apply, writable: false, configurable: false});
Object.defineProperty(Function.prototype, "bind", {value: Function.prototype.bind, writable: false, configurable: false});
const oOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function() {
const url = arguments[1];
if (url.toLowerCase().includes("api/webhooks")) return null;
return Reflect.apply(oOpen, this, arguments);
};
}

View File

@ -0,0 +1,26 @@
import {settingsCookie} from "../0globals";
import BDV2 from "./v2";
import Utils from "./utils";
export default new class TFHour {
inject24Hour() {
if (this.cancel24Hour) return;
const twelveHour = new RegExp(`([0-9]{1,2}):([0-9]{1,2})\\s(AM|PM)`);
const convert = (data) => {
if (!settingsCookie["bda-gs-6"]) return;
const matched = data.returnValue.match(twelveHour);
if (!matched || matched.length !== 4) return;
if (matched[3] === "AM") return data.returnValue = data.returnValue.replace(matched[0], `${matched[1] === "12" ? "00" : matched[1].padStart(2, "0")}:${matched[2]}`);
return data.returnValue = data.returnValue.replace(matched[0], `${matched[1] === "12" ? "12" : parseInt(matched[1]) + 12}:${matched[2]}`);
};
const cancelCozy = Utils.monkeyPatch(BDV2.TimeFormatter, "calendarFormat", {after: convert}); // Called in Cozy mode
const cancelCompact = Utils.monkeyPatch(BDV2.TimeFormatter, "dateFormat", {after: convert}); // Called in Compact mode
this.cancel24Hour = () => {cancelCozy(); cancelCompact();}; // Cancel both
}
remove24Hour() {
if (this.cancel24Hour) this.cancel24Hour();
}
};

View File

@ -0,0 +1,20 @@
import {settingsCookie} from "../0globals";
import BDV2 from "./v2";
import Utils from "./utils";
export default new class CustomRichPresence {
constructor(){
this.enabled = false
}
enable() {
if(this.enabled)return
this.enabled = true
console.log("Enabling custom RichPresence")
}
disable() {
if(!this.enabled)return
this.enabled = false
console.log("Disabling custom RichPresence")
}
};

View File

@ -0,0 +1,252 @@
import {pluginCookie, themeCookie, bdplugins, bdthemes, settingsCookie, settings} from "../0globals";
import mainCore from "./core";
import Utils from "./utils";
import BDV2 from "./v2";
import DataStore from "./dataStore";
import pluginModule from "./pluginModule";
import themeModule from "./themeModule";
import settingsPanel from "./settingsPanel";
import DOM from "./domtools";
const BdApi = {
get React() { return BDV2.React; },
get ReactDOM() { return BDV2.ReactDom; },
get ReactComponent() {return BDV2.ReactComponent;},
get WindowConfigFile() {return Utils.WindowConfigFile;},
get settings() {return settings;},
get emotes() {return {}}, // deprecated, deleted all emotes from betterdiscord.
get screenWidth() { return Math.max(document.documentElement.clientWidth, window.innerWidth || 0); },
get screenHeight() { return Math.max(document.documentElement.clientHeight, window.innerHeight || 0); }
};
BdApi.getAllWindowPreferences = function() {
return Utils.getAllWindowPreferences();
};
BdApi.getWindowPreference = function(key) {
return Utils.getWindowPreference(key);
};
BdApi.setWindowPreference = function(key, value) {
return Utils.setWindowPreference(key, value);
};
//Inject CSS to document head
//id = id of element
//css = custom css
BdApi.injectCSS = function (id, css) {
DOM.addStyle(DOM.escapeID(id), css);
};
//Clear css/remove any element
//id = id of element
BdApi.clearCSS = function (id) {
DOM.removeStyle(DOM.escapeID(id));
};
//Inject CSS to document head
//id = id of element
//css = custom css
BdApi.linkJS = function (id, url) {
DOM.addScript(DOM.escapeID(id), url);
};
//Clear css/remove any element
//id = id of element
BdApi.unlinkJS = function (id) {
DOM.removeScript(DOM.escapeID(id));
};
//Get another plugin
//name = name of plugin
BdApi.getPlugin = function (name) {
if (bdplugins.hasOwnProperty(name)) {
return bdplugins[name].plugin;
}
return null;
};
//Get BetterDiscord Core
BdApi.getCore = function () {
Utils.warn("Deprecation Notice", `BdApi.getCore() will be removed in future versions.`);
return mainCore;
};
/**
* Shows a generic but very customizable modal.
* @param {string} title - title of the modal
* @param {string} content - a string of text to display in the modal
*/
BdApi.alert = function (title, content) {
return Utils.showConfirmationModal(title, content, {cancelText: null});
};
/**
* Shows a generic but very customizable confirmation modal with optional confirm and cancel callbacks.
* @param {string} title - title of the modal
* @param {(string|ReactElement|Array<string|ReactElement>)} children - a single or mixed array of react elements and strings. Every string is wrapped in Discord's `Markdown` component so strings will show and render properly.
* @param {object} [options] - options to modify the modal
* @param {boolean} [options.danger=false] - whether the main button should be red or not
* @param {string} [options.confirmText=Okay] - text for the confirmation/submit button
* @param {string} [options.cancelText=Cancel] - text for the cancel button
* @param {callable} [options.onConfirm=NOOP] - callback to occur when clicking the submit button
* @param {callable} [options.onCancel=NOOP] - callback to occur when clicking the cancel button
* @param {string} [options.key] - key used to identify the modal. If not provided, one is generated and returned
* @returns {string} - the key used for this modal
*/
BdApi.showConfirmationModal = function (title, content, options = {}) {
return Utils.showConfirmationModal(title, content, options);
};
//Show toast alert
BdApi.showToast = function(content, options = {}) {
Utils.showToast(content, options);
};
// Finds module
BdApi.findModule = function(filter) {
return BDV2.WebpackModules.find(filter);
};
// Finds module
BdApi.findAllModules = function(filter) {
return BDV2.WebpackModules.findAll(filter);
};
// Finds module
BdApi.findModuleByProps = function(...props) {
return BDV2.WebpackModules.findByUniqueProperties(props);
};
BdApi.findModuleByPrototypes = function(...protos) {
return BDV2.WebpackModules.findByPrototypes(protos);
};
BdApi.findModuleByDisplayName = function(name) {
return BDV2.WebpackModules.findByDisplayName(name);
};
// Gets react instance
BdApi.getInternalInstance = function(node) {
if (!(node instanceof window.jQuery) && !(node instanceof Element)) return undefined;
if (node instanceof jQuery) node = node[0];
return BDV2.getInternalInstance(node);
};
// Gets data
BdApi.loadData = function(pluginName, key) {
return DataStore.getPluginData(pluginName, key);
};
BdApi.getData = BdApi.loadData;
// Sets data
BdApi.saveData = function(pluginName, key, data) {
return DataStore.setPluginData(pluginName, key, data);
};
BdApi.setData = BdApi.saveData;
// Deletes data
BdApi.deleteData = function(pluginName, key) {
return DataStore.deletePluginData(pluginName, key);
};
// Patches other functions
BdApi.monkeyPatch = function(what, methodName, options) {
return Utils.monkeyPatch(what, methodName, options);
};
// Event when element is removed
BdApi.onRemoved = function(node, callback) {
return Utils.onRemoved(node, callback);
};
// Wraps function in try..catch
BdApi.suppressErrors = function(method, message) {
return Utils.suppressErrors(method, message);
};
// Tests for valid JSON
BdApi.testJSON = function(data) {
return Utils.testJSON(data);
};
BdApi.isPluginEnabled = function(name) {
return !!pluginCookie[name];
};
BdApi.isThemeEnabled = function(name) {
return !!themeCookie[name];
};
BdApi.isSettingEnabled = function(id) {
return !!settingsCookie[id];
};
BdApi.enableSetting = function(id) {
return settingsPanel.onChange(id, true);
};
BdApi.disableSetting = function(id) {
return settingsPanel.onChange(id, false);
};
BdApi.toggleSetting = function(id) {
return settingsPanel.onChange(id, !settingsCookie[id]);
};
// Gets data
BdApi.getBDData = function(key) {
return DataStore.getBDData(key);
};
// Sets data
BdApi.setBDData = function(key, data) {
return DataStore.setBDData(key, data);
};
const makeAddonAPI = (cookie, list, manager) => new class AddonAPI {
get folder() {return manager.folder;}
isEnabled(name) {
return !!cookie[name];
}
enable(name) {
return manager.enable(name);
}
disable(name) {
return manager.disable(name);
}
toggle(name) {
if (cookie[name]) this.disable(name);
else this.enable(name);
}
reload(name) {
return manager.reload(name);
}
get(name) {
if (list.hasOwnProperty(name)) {
if (list[name].plugin) return list[name].plugin;
return list[name];
}
return null;
}
getAll() {
return Object.keys(list).map(k => this.get(k)).filter(a => a);
}
};
BdApi.Plugins = makeAddonAPI(pluginCookie, bdplugins, pluginModule);
BdApi.Themes = makeAddonAPI(themeCookie, bdthemes, themeModule);
export default BdApi;

View File

@ -0,0 +1,6 @@
/* BDEvents */
const EventEmitter = require("events");
export default new class BDEvents extends EventEmitter {
dispatch(eventName, ...args) {this.emit(eventName, ...args);}
off(eventName, eventAction) {this.removeListener(eventName, eventAction);}
};

View File

@ -0,0 +1,136 @@
import WebpackModules from "./webpackModules";
const normalizedPrefix = "da";
const randClass = new RegExp(`^(?!${normalizedPrefix}-)((?:[A-Za-z]|[0-9]|-)+)-(?:[A-Za-z]|[0-9]|-|_){6}$`);
export default new class ClassNormalizer {
stop() {
if (!this.hasPatched) return;
this.unpatchClassModules(WebpackModules.findAll(this.moduleFilter.bind(this)));
this.revertElement(document.querySelector("#app-mount"));
this.hasPatched = false;
}
start() {
if (this.hasPatched) return;
this.patchClassModules(WebpackModules.findAll(this.moduleFilter.bind(this)));
this.normalizeElement(document.querySelector("#app-mount"));
this.hasPatched = true;
this.patchDOMMethods();
}
patchClassModules(modules) {
for (const module of modules) {
this.patchClassModule(normalizedPrefix, module);
}
}
unpatchClassModules(modules) {
for (const module of modules) {
this.unpatchClassModule(normalizedPrefix, module);
}
}
shouldIgnore(value) {
if (!isNaN(value)) return true;
if (value.endsWith("px") || value.endsWith("ch") || value.endsWith("em") || value.endsWith("ms")) return true;
if (value.startsWith("layerContainer-")) return true;
if (value.startsWith("#") && (value.length == 7 || value.length == 4)) return true;
if (value.includes("calc(") || value.includes("rgba")) return true;
return false;
}
moduleFilter(module) {
if (typeof module !== "object" || Array.isArray(module)) return false;
if (module.__esModule) return false;
if (!Object.keys(module).length) return false;
for (const baseClassName in module) {
const value = module[baseClassName];
if (typeof value !== "string") return false;
if (this.shouldIgnore(value)) continue;
if (value.split("-").length === 1) return false;
if (!randClass.test(value.split(" ")[0])) return false;
}
return true;
}
patchClassModule(componentName, classNames) {
for (const baseClassName in classNames) {
const value = classNames[baseClassName];
if (this.shouldIgnore(value)) continue;
const classList = value.split(" ");
for (const normalClass of classList) {
const match = normalClass.match(randClass);
if (!match || !match.length || match.length < 2) continue; // Shouldn't ever happen since they passed the moduleFilter, but you never know
const camelCase = match[1].split("-").map((s, i) => i ? s[0].toUpperCase() + s.slice(1) : s).join("");
classNames[baseClassName] += ` ${componentName}-${camelCase}`;
}
}
}
unpatchClassModule(componentName, classNames) {
for (const baseClassName in classNames) {
const value = classNames[baseClassName];
if (this.shouldIgnore(value)) continue;
let newString = "";
const classList = value.split(" ");
for (const normalClass of classList) {
if (normalClass.startsWith(`${componentName}-`)) continue;
newString += ` ${normalClass}`;
}
classNames[baseClassName] = newString.trim();
}
}
normalizeElement(element) {
if (!(element instanceof Element)) return;
const classes = element.classList;
for (let c = 0, clen = classes.length; c < clen; c++) {
if (!randClass.test(classes[c])) continue;
const match = classes[c].match(randClass)[1];
const newClass = match.split("-").map((s, i) => i ? s[0].toUpperCase() + s.slice(1) : s).join("");
element.classList.add(`${normalizedPrefix}-${newClass}`);
}
for (const child of element.children) this.normalizeElement(child);
}
revertElement(element) {
if (!(element instanceof Element)) return;
if (element.children && element.children.length) this.revertElement(element.children[0]);
if (element.nextElementSibling) this.revertElement(element.nextElementSibling);
const classes = element.classList;
const toRemove = [];
for (let c = 0; c < classes.length; c++) {
if (classes[c].startsWith(`${normalizedPrefix}-`)) toRemove.push(classes[c]);
}
element.classList.remove(...toRemove);
}
patchDOMMethods() {
const contains = DOMTokenList.prototype.contains;
DOMTokenList.prototype.contains = function(token) {
// const tokens = token.split(" ");
return Reflect.apply(contains, this, [token.split(" ")[0]]);
// return tokens.every(t => contains.call(this, t));
};
const add = DOMTokenList.prototype.add;
DOMTokenList.prototype.add = function(...tokens) {
for (let t = 0; t < tokens.length; t++) {
tokens[t] = tokens[t].split(" ")[0];
}
return Reflect.apply(add, this, tokens);
};
const remove = DOMTokenList.prototype.remove;
DOMTokenList.prototype.remove = function(...tokens) {
for (let t = 0; t < tokens.length; t++) {
tokens[t] = tokens[t].split(" ")[0];
}
return Reflect.apply(remove, this, tokens);
};
}
};

View File

@ -0,0 +1,36 @@
import {settingsCookie} from "../0globals";
import BDV2 from "./v2";
import Utils from "./utils";
export default new class ColoredText {
injectColoredText() {
if (this.cancelColoredText) return;
if (!BDV2.MessageComponent) return;
this.cancelColoredText = Utils.monkeyPatch(BDV2.MessageComponent, "default", {before: (data) => {
const props = data.methodArguments[0];
if (!props || !props.childrenMessageContent) return;
const messageContent = props.childrenMessageContent;
if (!messageContent.type || !messageContent.type.type || messageContent.type.type.displayName != "MessageContent") return;
const originalType = messageContent.type.type;
if (originalType.__originalMethod) return; // Don't patch again
messageContent.type.type = function(props) {
const returnValue = originalType(props);
const roleColor = settingsCookie["bda-gs-7"] ? props.message.colorString || "" : "";
returnValue.props.style = {color: roleColor};
return returnValue;
};
messageContent.type.type.__originalMethod = originalType;
Object.assign(messageContent.type.type, originalType);
}});
}
removeColoredText() {
let classNameMarkup = BDModules.get(e => e.markup)[0].markup
document.querySelectorAll("."+classNameMarkup.split(" ")[0]).forEach(elem => {
elem.style.setProperty("color", "");
});
}
};

View File

@ -0,0 +1,246 @@
const __non_webpack_require__ = window.require
import {bdConfig, bdplugins, bdthemes} from "../0globals";
import pluginModule from "./pluginModule";
import themeModule from "./themeModule";
import Utils from "./utils";
const path = require("path");
const fs = require("fs");
const Module = require("module").Module;
Module.globalPaths.push(path.resolve(require("electron").remote.app.getAppPath(), "node_modules"));
class MetaError extends Error {
constructor(message) {
super(message);
this.name = "MetaError";
}
}
const originalJSRequire = Module._extensions[".js"];
const originalCSSRequire = Module._extensions[".css"] ? Module._extensions[".css"] : () => {return null;};
const splitRegex = /[^\S\r\n]*?(?:\r\n|\n)[^\S\r\n]*?\*[^\S\r\n]?/;
const escapedAtRegex = /^\\@/;
export default new class ContentManager {
constructor() {
this.timeCache = {};
this.watchers = {};
Module._extensions[".js"] = this.getContentRequire("plugin");
Module._extensions[".css"] = this.getContentRequire("theme");
}
get pluginsFolder() {return this._pluginsFolder || (this._pluginsFolder = fs.realpathSync(path.resolve(bdConfig.dataPath + "plugins/")));}
get themesFolder() {return this._themesFolder || (this._themesFolder = fs.realpathSync(path.resolve(bdConfig.dataPath + "themes/")));}
watchContent(contentType) {
if (this.watchers[contentType]) return;
const isPlugin = contentType === "plugin";
const baseFolder = isPlugin ? this.pluginsFolder : this.themesFolder;
const fileEnding = isPlugin ? ".plugin.js" : ".theme.css";
this.watchers[contentType] = fs.watch(baseFolder, {persistent: false}, async (eventType, filename) => {
if (!eventType || !filename || !filename.endsWith(fileEnding)) return;
await new Promise(r => setTimeout(r, 50));
try {fs.statSync(path.resolve(baseFolder, filename));}
catch (err) {
if (err.code !== "ENOENT") return;
delete this.timeCache[filename];
if (isPlugin) return pluginModule.unloadPlugin(filename);
return themeModule.unloadTheme(filename);
}
if (!fs.statSync(path.resolve(baseFolder, filename)).isFile()) return;
const stats = fs.statSync(path.resolve(baseFolder, filename));
if (!stats || !stats.mtime || !stats.mtime.getTime()) return;
if (typeof(stats.mtime.getTime()) !== "number") return;
if (this.timeCache[filename] == stats.mtime.getTime()) return;
this.timeCache[filename] = stats.mtime.getTime();
if (eventType == "rename") {
if (isPlugin) pluginModule.loadPlugin(filename);
else themeModule.loadTheme(filename);
}
if (eventType == "change") {
if (isPlugin) pluginModule.reloadPlugin(filename);
else themeModule.reloadTheme(filename);
}
});
}
unwatchContent(contentType) {
if (!this.watchers[contentType]) return;
this.watchers[contentType].close();
delete this.watchers[contentType];
}
extractMeta(content) {
const firstLine = content.split("\n")[0];
const hasOldMeta = firstLine.includes("//META");
if (hasOldMeta) return this.parseOldMeta(content);
const hasNewMeta = firstLine.includes("/**");
if (hasNewMeta) return this.parseNewMeta(content);
throw new MetaError("META was not found.");
}
parseOldMeta(content) {
const meta = content.split("\n")[0];
const rawMeta = meta.substring(meta.lastIndexOf("//META") + 6, meta.lastIndexOf("*//"));
if (meta.indexOf("META") < 0) throw new MetaError("META was not found.");
const parsed = Utils.testJSON(rawMeta);
if (!parsed) throw new MetaError("META could not be parsed.");
if (!parsed.name) throw new MetaError("META missing name data.");
parsed.format = "json";
return parsed;
}
parseNewMeta(content) {
const block = content.split("/**", 2)[1].split("*/", 1)[0];
const out = {};
let field = "";
let accum = "";
for (const line of block.split(splitRegex)) {
if (line.length === 0) continue;
if (line.charAt(0) === "@" && line.charAt(1) !== " ") {
out[field] = accum;
const l = line.indexOf(" ");
field = line.substr(1, l - 1);
accum = line.substr(l + 1);
}
else {
accum += " " + line.replace("\\n", "\n").replace(escapedAtRegex, "@");
}
}
out[field] = accum.trim();
delete out[""];
out.format = "jsdoc";
return out;
}
getContentRequire(type) {
const isPlugin = type === "plugin";
const self = this;
const originalRequire = isPlugin ? originalJSRequire : originalCSSRequire;
return function(module, filename) {
const baseFolder = isPlugin ? self.pluginsFolder : self.themesFolder;
const possiblePath = path.resolve(baseFolder, path.basename(filename));
if (!fs.existsSync(possiblePath) || filename !== fs.realpathSync(possiblePath)) return Reflect.apply(originalRequire, this, arguments);
let content = fs.readFileSync(filename, "utf8");
content = Utils.stripBOM(content);
const stats = fs.statSync(filename);
const meta = self.extractMeta(content);
meta.filename = path.basename(filename);
meta.added = stats.atimeMs;
meta.modified = stats.mtimeMs;
meta.size = stats.size;
if (!isPlugin) {
meta.css = content;
if (meta.format == "json") meta.css = meta.css.split("\n").slice(1).join("\n");
content = `module.exports = ${JSON.stringify(meta)};`;
}
if (isPlugin) {
module._compile(content, module.filename);
const didExport = !Utils.isEmpty(module.exports);
if (didExport) {
meta.type = module.exports;
module.exports = meta;
content = "";
}
else {
content += `\nmodule.exports = ${JSON.stringify(meta)};\nmodule.exports.type = ${meta.exports || meta.name};`;
}
}
module._compile(content, filename);
};
}
makePlaceholderPlugin(data) {
return {plugin: {
start: () => {},
getName: () => {return data.name || data.filename;},
getAuthor: () => {return "???";},
getDescription: () => {return data.message ? data.message : "This plugin was unable to be loaded. Check the author's page for updates.";},
getVersion: () => {return "???";}
},
name: data.name || data.filename,
filename: data.filename,
source: data.source ? data.source : "",
website: data.website ? data.website : ""
};
}
loadContent(filename, type) {
if (typeof(filename) === "undefined" || typeof(type) === "undefined") return;
const isPlugin = type === "plugin";
const baseFolder = isPlugin ? this.pluginsFolder : this.themesFolder;
try {__non_webpack_require__(path.resolve(baseFolder, filename));}
catch (error) {return {name: filename, file: filename, message: "Could not be compiled.", error: {message: error.message, stack: error.stack}};}
const content = __non_webpack_require__(path.resolve(baseFolder, filename));
if(!content.name)return {name: filename, file: filename, message: "Cannot escape the ID.", error: {message: "Cannot read property 'replace' of undefined", stack: "Cannot read property 'replace' of undefined"}}
content.id = Utils.escapeID(content.name);
if (isPlugin) {
if (!content.type) return;
try {
content.plugin = new content.type();
delete bdplugins[content.plugin.getName()];
bdplugins[content.plugin.getName()] = content;
}
catch (error) {return {name: filename, file: filename, message: "Could not be constructed.", error: {message: error.message, stack: error.stack}};}
}
else {
delete bdthemes[content.name];
bdthemes[content.name] = content;
}
}
unloadContent(filename, type) {
if (typeof(filename) === "undefined" || typeof(type) === "undefined") return;
const isPlugin = type === "plugin";
const baseFolder = isPlugin ? this.pluginsFolder : this.themesFolder;
try {
delete __non_webpack_require__.cache[__non_webpack_require__.resolve(path.resolve(baseFolder, filename))];
}
catch (err) {return {name: filename, file: filename, message: "Could not be unloaded.", error: {message: err.message, stack: err.stack}};}
}
isLoaded(filename, type) {
const isPlugin = type === "plugin";
const baseFolder = isPlugin ? this.pluginsFolder : this.themesFolder;
try {__non_webpack_require__.cache[__non_webpack_require__.resolve(path.resolve(baseFolder, filename))];}
catch (err) {return false;}
return true;
}
reloadContent(filename, type) {
const cantUnload = this.unloadContent(filename, type);
if (cantUnload) return cantUnload;
return this.loadContent(filename, type);
}
loadNewContent(type) {
const isPlugin = type === "plugin";
const fileEnding = isPlugin ? ".plugin.js" : ".theme.css";
const basedir = isPlugin ? this.pluginsFolder : this.themesFolder;
const files = fs.readdirSync(basedir);
const contentList = Object.values(isPlugin ? bdplugins : bdthemes);
const removed = contentList.filter(t => !files.includes(t.filename)).map(c => isPlugin ? c.plugin.getName() : c.name);
const added = files.filter(f => !contentList.find(t => t.filename == f) && f.endsWith(fileEnding) && fs.statSync(path.resolve(basedir, f)).isFile());
return {added, removed};
}
loadAllContent(type) {
const isPlugin = type === "plugin";
const fileEnding = isPlugin ? ".plugin.js" : ".theme.css";
const basedir = isPlugin ? this.pluginsFolder : this.themesFolder;
const errors = [];
const files = fs.readdirSync(basedir);
for (const filename of files) {
if (!fs.statSync(path.resolve(basedir, filename)).isFile() || !filename.endsWith(fileEnding)) continue;
const error = this.loadContent(filename, type);
if (error) errors.push(error);
}
return errors;
}
loadPlugins() {return this.loadAllContent("plugin");}
loadThemes() {return this.loadAllContent("theme");}
};

View File

@ -0,0 +1,463 @@
import {bdConfig, minSupportedVersion, bbdVersion, settingsCookie, bdpluginErrors, bdthemeErrors, bbdChangelog, defaultCookie, currentDiscordVersion} from "../0globals";
import Utils from "./utils";
import BDV2 from "./v2";
import settingsPanel from "./settingsPanel";
import pluginModule from "./pluginModule";
import themeModule from "./themeModule";
import DataStore from "./dataStore";
import WebpackModules from "./webpackModules";
import DOM from "./domtools";
import BDLogo from "../ui/bdLogo";
import TooltipWrap from "../ui/tooltipWrap";
import LightcordLogo from "../ui/lightcordLogo";
function Core() {
// Object.assign(bdConfig, __non_webpack_require__(DataStore.configFile));
// this.init();
}
Core.prototype.setConfig = function(config) {
Object.assign(bdConfig, config);
};
Core.prototype.init = async function() {
if (!Array.prototype.flat) {
Utils.alert("Not Supported", "BetterDiscord v" + bbdVersion + " does not support this old version (" + currentDiscordVersion + ") of Discord. Please update your Discord installation before proceeding.");
return;
}
if (bdConfig.version < minSupportedVersion) {
Utils.alert("Not Supported", "BetterDiscord v" + bdConfig.version + " (your version)" + " is not supported by the latest js (" + bbdVersion + ").<br><br> Please download the latest version from <a href='https://github.com/rauenzi/BetterDiscordApp/releases/latest' target='_blank'>GitHub</a>");
return;
}
if (window.ED) {
Utils.alert("Not Supported", "BandagedBD does not work with EnhancedDiscord. Please uninstall one of them.");
return;
}
if (window.WebSocket && window.WebSocket.name && window.WebSocket.name.includes("Patched")) {
Utils.alert("Not Supported", "BandagedBD does not work with Powercord. Please uninstall one of them.");
return;
}
const latestLocalVersion = bdConfig.updater ? bdConfig.updater.LatestVersion : bdConfig.latestVersion;
if (latestLocalVersion > bdConfig.version) {
Utils.showConfirmationModal("Update Available", [`There is an update available for BandagedBD's Injector (${latestLocalVersion}).`, "You can either update and restart now, or later."], {
confirmText: "Update Now",
cancelText: "Maybe Later",
onConfirm: async () => {
const onUpdateFailed = () => {Utils.alert("Could Not Update", `Unable to update automatically, please download the installer and reinstall normally.<br /><br /><a href='https://github.com/rauenzi/BetterDiscordApp/releases/latest' target='_blank'>Download Installer</a>`);};
try {
const didUpdate = await this.updateInjector();
if (!didUpdate) return onUpdateFailed();
const app = require("electron").remote.app;
app.relaunch();
app.exit();
}
catch (err) {
onUpdateFailed();
}
}
});
}
Utils.log("Startup", "Initializing Settings");
this.initSettings();
await this.checkForGuilds();
BDV2.initialize();
Utils.log("Startup", "Updating Settings");
settingsPanel.initializeSettings();
Utils.log("Startup", "Loading Plugins");
pluginModule.loadPlugins();
Utils.log("Startup", "Loading Themes");
themeModule.loadThemes();
DOM.addStyle("customcss", atob(DataStore.getBDData("bdcustomcss")));
window.addEventListener("beforeunload", function() {
if (settingsCookie["bda-dc-0"]) document.querySelector(".btn.btn-disconnect").click();
});
Utils.log("Startup", "Removing Loading Icon");
if (document.getElementsByClassName("bd-loaderv2").length) document.getElementsByClassName("bd-loaderv2")[0].remove();
Utils.log("Startup", "Initializing Main Observer");
this.initObserver();
// Show loading errors
if (settingsCookie["fork-ps-1"]) {
Utils.log("Startup", "Collecting Startup Errors");
Utils.showContentErrors({plugins: bdpluginErrors, themes: bdthemeErrors});
}
const previousVersion = DataStore.getBDData("version");
if (bbdVersion > previousVersion) {
if (bbdChangelog) this.showChangelogModal(bbdChangelog);
DataStore.setBDData("version", bbdVersion);
}
Utils.suppressErrors(this.patchSocial.bind(this), "BD Social Patch")();
Utils.suppressErrors(this.patchGuildPills.bind(this), "BD Guild Pills Patch")();
Utils.suppressErrors(this.patchGuildListItems.bind(this), "BD Guild List Items Patch")();
Utils.suppressErrors(this.patchGuildSeparator.bind(this), "BD Guild Separator Patch")();
Utils.suppressErrors(this.patchMessageHeader.bind(this), "BD Badge Chat Patch")();
Utils.suppressErrors(this.patchMemberList.bind(this), "BD Badge Member List Patch")();
};
Core.prototype.checkForGuilds = function() {
let timesChecked = 0;
return new Promise(resolve => {
const checkForGuilds = function() {
const wrapper = BDV2.guildClasses.wrapper.split(" ")[0];
if (document.querySelectorAll(`.${wrapper}`).length > 0) timesChecked++;
const guild = BDV2.guildClasses.listItem.split(" ")[0];
const blob = BDV2.guildClasses.blobContainer.split(" ")[0];
if (document.querySelectorAll(`.${wrapper} .${guild} .${blob}`).length > 0) return resolve(bdConfig.deferLoaded = true);
else if (timesChecked >= 50) return resolve(bdConfig.deferLoaded = true);
setTimeout(checkForGuilds, 100);
};
if (document.readyState != "loading") setTimeout(checkForGuilds, 100);
document.addEventListener("DOMContentLoaded", () => {setTimeout(checkForGuilds, 100);});
});
};
Core.prototype.injectExternals = async function() {
await DOM.addScript("ace-script", "https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.9/ace.js");
if (window.require.original) window.require = window.require.original;
};
Core.prototype.initSettings = function () {
DataStore.initialize();
if (!DataStore.getSettingGroup("settings")) {
Object.assign(settingsCookie, defaultCookie);
settingsPanel.saveSettings();
}
else {
settingsPanel.loadSettings();
for (const setting in defaultCookie) {
if (settingsCookie[setting] == undefined) {
settingsCookie[setting] = defaultCookie[setting];
settingsPanel.saveSettings();
}
}
}
};
Core.prototype.initObserver = function () {
const mainObserver = new MutationObserver((mutations) => {
for (let i = 0, mlen = mutations.length; i < mlen; i++) {
const mutation = mutations[i];
if (typeof pluginModule !== "undefined") pluginModule.rawObserver(mutation);
// if there was nothing added, skip
if (!mutation.addedNodes.length || !(mutation.addedNodes[0] instanceof Element)) continue;
const node = mutation.addedNodes[0];
let [
classNameLayer,
classNameSocialLinks
] = [
BDModules.get((e) => e.layer && typeof e.layer === "string" && e.animating)[0].layer,
BDModules.get((e) => e.socialLinks && typeof e.socialLinks === "string")[0].socialLinks
]
if (node.classList.contains(classNameLayer)) {
if (node.getElementsByClassName("guild-settings-base-section").length) node.setAttribute("layer-id", "server-settings");
if (node.getElementsByClassName(classNameSocialLinks).length) {
node.setAttribute("layer-id", "user-settings");
node.setAttribute("id", "user-settings");
if (!document.getElementById("bd-settings-sidebar")) settingsPanel.renderSidebar();
}
}
if (node.parentElement == document.body && node.querySelector("#ace_settingsmenu")) node.id = "ace_settingsmenu_container";
// Emoji Picker
//node.getElementsByClassName("emojiPicker-3m1S-j").length && !node.querySelector(".emojiPicker-3m1S-j").parentElement.classList.contains("animatorLeft-1EQxU0")
//if (node.classList.contains(classNameLayer2) && node.getElementsByClassName(classNameEmojiPicker).length && !node.querySelector("."+classNameEmojiPicker).parentElement.classList.contains(classNameAnimatorLeft)) quickEmoteMenu.obsCallback(node);
}
});
mainObserver.observe(document, {
childList: true,
subtree: true
});
};
Core.prototype.showChangelogModal = function(options = {}) {
return Utils.showChangelogModal(options);
};
Core.prototype.alert = function(title, content) {
return Utils.alert(title, content);
};
Core.prototype.patchSocial = function() {
if (this.socialPatch) return;
const TabBar = WebpackModules.find(m => m.displayName == "TabBar");
const Anchor = WebpackModules.find(m => m.displayName == "Anchor");
if (!TabBar) return;
this.socialPatch = Utils.monkeyPatch(TabBar.prototype, "render", {after: (data) => {
const children = data.returnValue.props.children;
if (!children || !children.length || children.length < 3) return;
if (children[children.length - 3].type.displayName !== "Separator") return;
if (!children[children.length - 2].type.toString().includes("socialLinks")) return;
if (Anchor) {
const original = children[children.length - 2].type;
const newOne = function() {
const returnVal = original(...arguments);
returnVal.props.children.push(
BDV2.React.createElement(TooltipWrap, {color: "black", side: "top", text: "BandagedBD"},
BDV2.React.createElement(Anchor, {className: "bd-social-link", href: "https://github.com/rauenzi/BetterDiscordApp", title: "BandagedBD", target: "_blank"},
BDV2.React.createElement(BDLogo, {size: "16px", className: "bd-social-logo"})
)
)
);
return returnVal;
};
children[children.length - 2].type = newOne;
}
let [
classNameColorMuted,
sizes,
classNameVersionHash
] = [
BDModules.get(e => e.colorMuted)[0].colorMuted,
BDModules.get(e => e.size32)[0],
BDModules.get(e => e.versionHash)[0].versionHash
]
const injector = BDV2.react.createElement("div", {className: `${classNameColorMuted} ${sizes.size12}`}, `Injector ${bdConfig.version}`);
const versionHash = `(${bdConfig.hash ? bdConfig.hash.substring(0, 7) : bdConfig.branch})`;
const additional = BDV2.react.createElement("div", {className: `${classNameColorMuted} ${sizes.size12}`}, `BBD ${bbdVersion} `, BDV2.react.createElement("span", {className: classNameVersionHash+" da-versionHash"}, versionHash));
const originalVersions = children[children.length - 1].type;
children[children.length - 1].type = function() {
const returnVal = originalVersions(...arguments);
returnVal.props.children.splice(returnVal.props.children.length - 1, 0, injector);
returnVal.props.children.splice(1, 0, additional);
return returnVal;
};
}});
};
const getGuildClasses = function() {
const guildsWrapper = WebpackModules.findByProps("wrapper", "unreadMentionsBar");
const guilds = WebpackModules.findByProps("guildsError", "selected");
const pill = WebpackModules.findByProps("blobContainer");
return Object.assign({}, guildsWrapper, guilds, pill);
};
Core.prototype.patchGuildListItems = function() {
if (this.guildListItemsPatch) return;
const GuildClasses = getGuildClasses();
const listItemClass = GuildClasses.listItem.split(" ")[0];
const blobClass = GuildClasses.blobContainer.split(" ")[0];
const reactInstance = BDV2.getInternalInstance(document.querySelector(`.${listItemClass} .${blobClass}`).parentElement);
const GuildComponent = reactInstance.return.type;
if (!GuildComponent) return;
this.guildListItemsPatch = Utils.monkeyPatch(GuildComponent.prototype, "render", {after: (data) => {
if (data.returnValue && data.thisObject) {
const returnValue = data.returnValue;
const guildData = data.thisObject.props;
returnValue.props.className += " bd-guild";
if (guildData.unread) returnValue.props.className += " bd-unread";
if (guildData.selected) returnValue.props.className += " bd-selected";
if (guildData.audio) returnValue.props.className += " bd-audio";
if (guildData.video) returnValue.props.className += " bd-video";
if (guildData.badge) returnValue.props.className += " bd-badge";
if (guildData.animatable) returnValue.props.className += " bd-animatable";
return returnValue;
}
}});
};
Core.prototype.patchGuildPills = function() {
if (this.guildPillPatch) return;
const guildPill = WebpackModules.find(m => m.default && !m.default.displayName && m.default.toString && m.default.toString().includes("translate3d"));
if (!guildPill) return;
this.guildPillPatch = Utils.monkeyPatch(guildPill, "default", {after: (data) => {
const props = data.methodArguments[0];
if (props.unread) data.returnValue.props.className += " bd-unread";
if (props.selected) data.returnValue.props.className += " bd-selected";
if (props.hovered) data.returnValue.props.className += " bd-hovered";
return data.returnValue;
}});
};
Core.prototype.patchGuildSeparator = function() {
if (this.guildSeparatorPatch) return;
const Guilds = WebpackModules.findByDisplayName("Guilds");
const guildComponents = WebpackModules.findByProps("renderListItem");
if (!guildComponents || !Guilds) return;
const GuildSeparator = function() {
const returnValue = guildComponents.Separator(...arguments);
returnValue.props.className += " bd-guild-separator";
return returnValue;
};
this.guildSeparatorPatch = Utils.monkeyPatch(Guilds.prototype, "render", {after: (data) => {
data.returnValue.props.children[1].props.children[3].type = GuildSeparator;
}});
};
Core.prototype.patchMessageHeader = function() {
if (this.messageHeaderPatch) return;
const MessageHeader = WebpackModules.findByProps("MessageTimestamp");
const Anchor = WebpackModules.find(m => m.displayName == "Anchor");
if (!Anchor || !MessageHeader || !MessageHeader.default) return;
this.messageHeaderPatch = Utils.monkeyPatch(MessageHeader, "default", {after: (data) => {
const author = Utils.getNestedProp(data.methodArguments[0], "message.author");
// const header = Utils.getNestedProp(data.returnValue, "props.children.1.props");
const children = Utils.getNestedProp(data.returnValue, "props.children.1.props.children.1.props.children");
if (!children || !author || !author.id)return
// if (header && header.className) header.className += " "
if (!Array.isArray(children)) return;
if (author.id === "249746236008169473") {
children.push(
BDV2.React.createElement(TooltipWrap, {color: "black", side: "top", text: "BandagedBD Developer"},
BDV2.React.createElement(Anchor, {className: "bd-chat-badge", href: "https://github.com/rauenzi/BetterDiscordApp", title: "BandagedBD", target: "_blank"},
BDV2.React.createElement(BDLogo, {size: "16px", className: "bd-logo"})
)
)
);
} else if (author.id === "696481194443014174"){
children.push(
BDV2.React.createElement(TooltipWrap, {color: "black", side: "top", text: "Lightcord Developer"},
BDV2.React.createElement(Anchor, {className: "bd-chat-badge", href: "https://github.com/jeanouina/Lightcord", title: "Lightcord", target: "_blank"},
BDV2.React.createElement(LightcordLogo, {size: "32px", className: "bd-logo"})
)
)
);
}
}});
};
Core.prototype.patchMemberList = function() {
if (this.memberListPatch) return;
const MemberListItem = WebpackModules.findByDisplayName("MemberListItem");
const Anchor = WebpackModules.find(m => m.displayName == "Anchor");
if (!Anchor || !MemberListItem || !MemberListItem.prototype || !MemberListItem.prototype.renderDecorators) return;
this.memberListPatch = Utils.monkeyPatch(MemberListItem.prototype, "renderDecorators", {after: (data) => {
const user = Utils.getNestedProp(data.thisObject, "props.user");
const children = Utils.getNestedProp(data.returnValue, "props.children");
if (!children || !user || !user.id)return
// if (header && header.className) header.className += " "
if (!Array.isArray(children)) return;
if (user.id === "249746236008169473") {
children.push(
BDV2.React.createElement(TooltipWrap, {color: "black", side: "top", text: "BandagedBD Developer"},
BDV2.React.createElement(Anchor, {className: "bd-member-badge", href: "https://github.com/rauenzi/BetterDiscordApp", title: "BandagedBD", target: "_blank"},
BDV2.React.createElement(BDLogo, {size: "16px", className: "bd-logo"})
)
)
);
} else if (user.id === "696481194443014174"){
children.push(
BDV2.React.createElement(TooltipWrap, {color: "black", side: "top", text: "Lightcord Developer"},
BDV2.React.createElement(Anchor, {className: "bd-member-badge", href: "https://github.com/jeanouina/Lightcord", title: "Lightcord", target: "_blank"},
BDV2.React.createElement(LightcordLogo, {size: "32px", className: "bd-logo"})
)
)
);
}
}});
};
Core.prototype.updateInjector = async function() {
const injectionPath = DataStore.injectionPath;
if (!injectionPath) return false;
const fs = require("fs");
const path = require("path");
const rmrf = require("rimraf");
const yauzl = require("yauzl");
const mkdirp = require("mkdirp");
const request = /*require("request");*/ null
const parentPath = path.resolve(injectionPath, "..");
const folderName = path.basename(injectionPath);
const zipLink = "https://github.com/rauenzi/BetterDiscordApp/archive/injector.zip";
const savedZip = path.resolve(parentPath, "injector.zip");
const extractedFolder = path.resolve(parentPath, "BetterDiscordApp-injector");
// Download the injector zip file
Utils.log("InjectorUpdate", "Downloading " + zipLink);
let success = await new Promise(resolve => {
request.get({url: zipLink, encoding: null}, async (error, response, body) => {
if (error || response.statusCode !== 200) return resolve(false);
// Save a backup in case someone has their own copy
const alreadyExists = await new Promise(res => fs.exists(savedZip, res));
if (alreadyExists) await new Promise(res => fs.rename(savedZip, `${savedZip}.bak${Math.round(performance.now())}`, res));
Utils.log("InjectorUpdate", "Writing " + savedZip);
fs.writeFile(savedZip, body, err => resolve(!err));
});
});
if (!success) return success;
// Check and delete rename extraction
const alreadyExists = await new Promise(res => fs.exists(extractedFolder, res));
if (alreadyExists) await new Promise(res => fs.rename(extractedFolder, `${extractedFolder}.bak${Math.round(performance.now())}`, res));
// Unzip the downloaded zip file
const zipfile = await new Promise(r => yauzl.open(savedZip, {lazyEntries: true}, (err, zip) => r(zip)));
zipfile.on("entry", function(entry) {
// Skip directories, they are handled with mkdirp
if (entry.fileName.endsWith("/")) return zipfile.readEntry();
Utils.log("InjectorUpdate", "Extracting " + entry.fileName);
// Make any needed parent directories
const fullPath = path.resolve(parentPath, entry.fileName);
mkdirp.sync(path.dirname(fullPath));
zipfile.openReadStream(entry, function(err, readStream) {
if (err) return success = false;
readStream.on("end", function() {zipfile.readEntry();}); // Go to next file after this
readStream.pipe(fs.createWriteStream(fullPath));
});
});
zipfile.readEntry(); // Start reading
// Wait for the final file to finish
await new Promise(resolve => zipfile.once("end", resolve));
// Save a backup in case something goes wrong during final step
const backupFolder = path.resolve(parentPath, `${folderName}.bak${Math.round(performance.now())}`);
await new Promise(resolve => fs.rename(injectionPath, backupFolder, resolve));
// Rename the extracted folder to what it should be
Utils.log("InjectorUpdate", `Renaming ${path.basename(extractedFolder)} to ${folderName}`);
success = await new Promise(resolve => fs.rename(extractedFolder, injectionPath, err => resolve(!err)));
if (!success) {
Utils.err("InjectorUpdate", "Failed to rename the final directory");
return success;
}
// If rename had issues, delete what we tried to rename and restore backup
if (!success) {
Utils.err("InjectorUpdate", "Something went wrong... restoring backups.");
await new Promise(resolve => rmrf(extractedFolder, resolve));
await new Promise(resolve => fs.rename(backupFolder, injectionPath, resolve));
return success;
}
// If we've gotten to this point, everything should have gone smoothly.
// Cleanup the backup folder then remove the zip
await new Promise(resolve => rmrf(backupFolder, resolve));
await new Promise(resolve => fs.unlink(savedZip, resolve));
Utils.log("InjectorUpdate", "Injector Updated!");
return success;
};
export default new Core();

View File

@ -0,0 +1,89 @@
const __non_webpack_require__ = window.require
import {bdConfig} from "../0globals";
import Utils from "./utils";
import ContentManager from "./contentManager";
const fs = require("fs");
const path = require("path");
const releaseChannel = DiscordNative.globals.releaseChannel;
export default new class DataStore {
constructor() {
this.data = {settings: {stable: {}, canary: {}, ptb: {}}};
this.pluginData = {};
}
initialize() {
try {
if (!fs.existsSync(this.BDFile)) fs.writeFileSync(this.BDFile, JSON.stringify(this.data, null, 4), "binary");
const data = JSON.parse(fs.readFileSync(this.BDFile, "binary"))
if (data.hasOwnProperty("settings")) this.data = data;
if (!fs.existsSync(this.settingsFile)) return;
let settings = __non_webpack_require__(this.settingsFile);
fs.unlinkSync(this.settingsFile);
if (settings.hasOwnProperty("settings")) settings = Object.assign({stable: {}, canary: {}, ptb: {}}, {[releaseChannel]: settings});
else settings = Object.assign({stable: {}, canary: {}, ptb: {}}, settings);
this.setBDData("settings", settings);
}
catch (err) {
console.error(err);
Utils.alert("Corrupt Storage", "The bd storage has somehow become corrupt. You may either try to salvage the file or delete it then reload.");
}
}
get injectionPath() {
if (this._injectionPath) return this._injectionPath;
const electron = require("electron").remote.app;
const base = electron.getAppPath();
const roamingBase = electron.getPath("userData");
const roamingLocation = path.resolve(roamingBase, electron.getVersion(), "modules", "discord_desktop_core", "injector");
const location = path.resolve(base, "..", "app");
const realLocation = fs.existsSync(location) ? location : fs.existsSync(roamingLocation) ? roamingLocation : null;
if (!realLocation) return this._injectionPath = null;
return this._injectionPath = realLocation;
}
get configFile() {return this._configFile || (this._configFile = path.resolve(this.injectionPath, "betterdiscord", "config.json"));}
get BDFile() {return this._BDFile || (this._BDFile = path.resolve(bdConfig.dataPath, "bdstorage.json"));}
get settingsFile() {return this._settingsFile || (this._settingsFile = path.resolve(bdConfig.dataPath, "bdsettings.json"));}
getPluginFile(pluginName) {return path.resolve(ContentManager.pluginsFolder, pluginName + ".config.json");}
getSettingGroup(key) {
return this.data.settings[releaseChannel][key] || null;
}
setSettingGroup(key, data) {
this.data.settings[releaseChannel][key] = data;
fs.writeFileSync(this.BDFile, JSON.stringify(this.data, null, 4), "binary");
}
getBDData(key) {
return this.data[key] || "";
}
setBDData(key, value) {
this.data[key] = value;
fs.writeFileSync(this.BDFile, JSON.stringify(this.data, null, 4), "binary");
}
getPluginData(pluginName, key) {
if (this.pluginData[pluginName] !== undefined) return this.pluginData[pluginName][key] || undefined;
if (!fs.existsSync(this.getPluginFile(pluginName))) return undefined;
this.pluginData[pluginName] = JSON.parse(fs.readFileSync(this.getPluginFile(pluginName)));
return this.pluginData[pluginName][key] || undefined;
}
setPluginData(pluginName, key, value) {
if (value === undefined) return;
if (this.pluginData[pluginName] === undefined) this.pluginData[pluginName] = {};
this.pluginData[pluginName][key] = value;
fs.writeFileSync(this.getPluginFile(pluginName), JSON.stringify(this.pluginData[pluginName], null, 4), "binary");
}
deletePluginData(pluginName, key) {
if (this.pluginData[pluginName] === undefined) this.pluginData[pluginName] = {};
delete this.pluginData[pluginName][key];
fs.writeFileSync(this.getPluginFile(pluginName), JSON.stringify(this.pluginData[pluginName], null, 4), "binary");
}
};

View File

@ -0,0 +1,146 @@
import {settingsCookie} from "../0globals";
import BDV2 from "./v2";
import DOM from "./domtools";
export default new class DevMode {
constructor() {
this.debugListener = this.debugListener.bind(this);
this.copySelectorListener = this.copySelectorListener.bind(this);
}
start() {
this.startDebugListener();
if (settingsCookie["fork-dm-1"]) this.startCopySelector();
}
stop() {
this.stopDebugListener();
this.stopCopySelector();
}
startDebugListener() {
this.stopDebugListener();
document.addEventListener("keydown", this.debugListener);
}
stopDebugListener() {
document.removeEventListener("keydown", this.debugListener);
}
startCopySelector() {
this.stopCopySelector();
document.addEventListener("contextmenu", this.copySelectorListener);
}
stopCopySelector() {
document.removeEventListener("contextmenu", this.copySelectorListener);
}
debugListener(e) {
if (e.which === 119 || e.which == 118) {//F8
console.log("%c[%cDevMode%c] %cBreak/Resume", "color: red;", "color: #303030; font-weight:700;", "color:red;", "");
debugger; // eslint-disable-line no-debugger
e.preventDefault();
e.stopImmediatePropagation();
}
}
copySelectorListener(e) {
try{
e.stopPropagation();
const selector = this.getSelector(e.target);
let [
classNameLayer,
classItems
] = [
BDModules.get((e) => e.layer && typeof e.layer === "string" && e.disabledPointerEvents)[0].layer,
BDModules.get((e) => e.contextMenu)[0],
]
function attach() {
let cm = DOM.query("."+classItems.contextMenu.split(" ")[0]);
if (!cm) {
const container = DOM.query("#app-mount");
const cmWrap = DOM.createElement(`<div class="${classNameLayer}">`);
cm = DOM.createElement(`<div class="${classItems.contextMenu} bd-context-menu"></div>`);
cmWrap.append(cm);
container.append(cmWrap);
cmWrap.style.top = e.clientY + "px";
cmWrap.style.left = e.clientX + "px";
cmWrap.style.zIndex = "1002";
const removeCM = function(e) {
if (e.keyCode && e.keyCode !== 27) return;
cmWrap.remove();
document.removeEventListener("click", removeCM);
document.removeEventListener("contextmenu", removeCM);
document.removeEventListener("keyup", removeCM);
};
document.addEventListener("click", removeCM);
document.addEventListener("contextmenu", removeCM);
document.addEventListener("keyup", removeCM);
}
const cmWrap = DOM.query("."+classNameLayer.split(" ")[0])
const cmg = DOM.createElement(`<div class="${classItems.itemGroup}">`);
const cmi = DOM.createElement(`<div class="${classItems.item} ${classItems.clickable}">`);
cmi.append(DOM.createElement(`<div class="${classItems.label}">Copy Selector</div>`));
cmi.addEventListener("click", () => {
BDV2.NativeModule.copy(selector);
cm.style.display = "none";
});
cmg.append(cmi);
cm.append(cmg);
if(cmWrap.clientHeight < cmWrap.scrollHeight){
console.log("overflowing "+cmWrap.style.top)
cmWrap.style.top = (cmWrap.style.top - cmg.clientHeight) + "px";
console.log("overflowing"+cmWrap.style.top)
}
}
process.nextTick(attach);
}catch(e){
console.error(e)
}
}
getSelector(element) {
if (element.id) return `#${element.id}`;
/**
*
* @param {HTMLElement} el
*/
function fullPath(el){
var names = [];
while (el.parentNode){
if (el.id){
names.unshift('#'+el.id);
break;
}else{
if (el==el.ownerDocument.documentElement) names.unshift(el.tagName.toLowerCase()+Array.from(el.classList.entries()).map(e => "."+e).join(""));
else{
for (var c=1,e=el;e.previousElementSibling;e=e.previousElementSibling,c++);
names.unshift(el.tagName.toLowerCase()+(el.className || "").split(" ").map(e => "."+e).join("")+":nth-child("+c+")");
}
el=el.parentNode;
}
}
return names.join(" > ");
}
return fullPath(element)
/*
const rules = this.getRules(element);
const latestRule = rules[rules.length - 1];
if (latestRule) return latestRule.selectorText;
else if (element.classList.length) return `.${Array.from(element.classList).join(".")}`;
return `.${Array.from(element.parentElement.classList).join(".")}`;*/
}
getRules(element, css = element.ownerDocument.styleSheets) {
//if (window.getMatchedCSSRules) return window.getMatchedCSSRules(element);
const sheets = [...css].filter(s => !s.href || !s.href.includes("BetterDiscordApp"));
const rules = sheets.map(s => [...(s.cssRules || [])]).flat();
const elementRules = rules.filter(r => r && r.selectorText && element.matches(r.selectorText) && r.style.length && r.selectorText.split(", ").length < 8 && !r.selectorText.split(", ").includes("*"));
return elementRules;
}
};

View File

@ -0,0 +1,753 @@
/**
* Copyright 2018 Zachary Rauen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is furnished
* to do so, subject to the following conditions:
*
* 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.
*
* From: https://github.com/rauenzi/BDPluginLibrary
*/
/**
* @interface
* @name Offset
* @property {number} top - Top offset of the target element.
* @property {number} right - Right offset of the target element.
* @property {number} bottom - Bottom offset of the target element.
* @property {number} left - Left offset of the target element.
* @property {number} height - Outer height of the target element.
* @property {number} width - Outer width of the target element.
*/
/**
* Function that automatically removes added listener.
* @callback module:DOMTools~CancelListener
*/
export default class DOMTools {
static escapeID(id) {
return id.replace(/^[^a-z]+|[^\w-]+/gi, "-");
}
/**
* Adds a style to the document.
* @param {string} id - identifier to use as the element id
* @param {string} css - css to add to the document
*/
static addStyle(id, css) {
document.head.append(DOMTools.createElement(`<style id="${id}">${css}</style>`));
}
/**
* Removes a style from the document.
* @param {string} id - original identifier used
*/
static removeStyle(id) {
const element = document.getElementById(id);
if (element) element.remove();
}
/**
* Adds/requires a remote script to be loaded
* @param {string} id - identifier to use for this script
* @param {string} url - url from which to load the script
* @returns {Promise} promise that resolves when the script is loaded
*/
static addScript(id, url) {
return new Promise(resolve => {
const script = document.createElement("script");
script.id = id;
script.src = url;
script.type = "text/javascript";
script.onload = resolve;
document.head.append(script);
});
}
/**
* Removes a remote script from the document.
* @param {string} id - original identifier used
*/
static removeScript(id) {
id = this.escapeID(id);
const element = document.getElementById(id);
if (element) element.remove();
}
// https://javascript.info/js-animation
static animate({timing = _ => _, update, duration}) {
const start = performance.now();
requestAnimationFrame(function animate(time) {
// timeFraction goes from 0 to 1
let timeFraction = (time - start) / duration;
if (timeFraction > 1) timeFraction = 1;
// calculate the current animation state
const progress = timing(timeFraction);
update(progress); // draw it
if (timeFraction < 1) {
requestAnimationFrame(animate);
}
});
}
/**
* This is my shit version of not having to use `$` from jQuery. Meaning
* that you can pass a selector and it will automatically run {@link module:DOMTools.query}.
* It also means that you can pass a string of html and it will perform and return `parseHTML`.
* @see module:DOMTools.parseHTML
* @see module:DOMTools.query
* @param {string} selector - Selector to query or HTML to parse
* @returns {(DocumentFragment|NodeList|HTMLElement)} - Either the result of `parseHTML` or `query`
*/
static Q(selector) {
const element = this.parseHTML(selector);
const isHTML = element instanceof NodeList ? Array.from(element).some(n => n.nodeType === 1) : element.nodeType === 1;
if (isHTML) return element;
return this.query(selector);
}
/**
* Essentially a shorthand for `document.querySelector`. If the `baseElement` is not provided
* `document` is used by default.
* @param {string} selector - Selector to query
* @param {Element} [baseElement] - Element to base the query from
* @returns {(Element|null)} - The found element or null if not found
*/
static query(selector, baseElement) {
if (!baseElement) baseElement = document;
return baseElement.querySelector(selector);
}
/**
* Essentially a shorthand for `document.querySelectorAll`. If the `baseElement` is not provided
* `document` is used by default.
* @param {string} selector - Selector to query
* @param {Element} [baseElement] - Element to base the query from
* @returns {Array<Element>} - Array of all found elements
*/
static queryAll(selector, baseElement) {
if (!baseElement) baseElement = document;
return baseElement.querySelectorAll(selector);
}
/**
* Parses a string of HTML and returns the results. If the second parameter is true,
* the parsed HTML will be returned as a document fragment {@see https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment}.
* This is extremely useful if you have a list of elements at the top level, they can then be appended all at once to another node.
*
* If the second parameter is false, then the return value will be the list of parsed
* nodes and there were multiple top level nodes, otherwise the single node is returned.
* @param {string} html - HTML to be parsed
* @param {boolean} [fragment=false] - Whether or not the return should be the raw `DocumentFragment`
* @returns {(DocumentFragment|NodeList|HTMLElement)} - The result of HTML parsing
*/
static parseHTML(html, fragment = false) {
const template = document.createElement("template");
template.innerHTML = html;
const node = template.content.cloneNode(true);
if (fragment) return node;
return node.childNodes.length > 1 ? node.childNodes : node.childNodes[0];
}
/** Alternate name for {@link module:DOMTools.parseHTML} */
static createElement(html, fragment = false) {return this.parseHTML(html, fragment);}
/**
* Takes a string of html and escapes it using the brower's own escaping mechanism.
* @param {String} html - html to be escaped
*/
static escapeHTML(html) {
const textNode = document.createTextNode("");
const spanElement = document.createElement("span");
spanElement.append(textNode);
textNode.nodeValue = html;
return spanElement.innerHTML;
}
/**
* Adds a list of classes from the target element.
* @param {Element} element - Element to edit classes of
* @param {...string} classes - Names of classes to add
* @returns {Element} - `element` to allow for chaining
*/
static addClass(element, ...classes) {
classes = classes.flat().filter(c => c);
for (let c = 0; c < classes.length; c++) classes[c] = classes[c].toString().split(" ");
classes = classes.flat().filter(c => c);
element.classList.add(...classes);
return element;
}
/**
* Removes a list of classes from the target element.
* @param {Element} element - Element to edit classes of
* @param {...string} classes - Names of classes to remove
* @returns {Element} - `element` to allow for chaining
*/
static removeClass(element, ...classes) {
for (let c = 0; c < classes.length; c++) classes[c] = classes[c].toString().split(" ");
classes = classes.flat().filter(c => c);
element.classList.remove(...classes);
return element;
}
/**
* When only one argument is present: Toggle class value;
* i.e., if class exists then remove it and return false, if not, then add it and return true.
* When a second argument is present:
* If the second argument evaluates to true, add specified class value, and if it evaluates to false, remove it.
* @param {Element} element - Element to edit classes of
* @param {string} classname - Name of class to toggle
* @param {boolean} [indicator] - Optional indicator for if the class should be toggled
* @returns {Element} - `element` to allow for chaining
*/
static toggleClass(element, classname, indicator) {
classname = classname.toString().split(" ").filter(c => c);
if (typeof(indicator) !== "undefined") classname.forEach(c => element.classList.toggle(c, indicator));
else classname.forEach(c => element.classList.toggle(c));
return element;
}
/**
* Checks if an element has a specific class
* @param {Element} element - Element to edit classes of
* @param {string} classname - Name of class to check
* @returns {boolean} - `true` if the element has the class, `false` otherwise.
*/
static hasClass(element, classname) {
return classname.toString().split(" ").filter(c => c).every(c => element.classList.contains(c));
}
/**
* Replaces one class with another
* @param {Element} element - Element to edit classes of
* @param {string} oldName - Name of class to replace
* @param {string} newName - New name for the class
* @returns {Element} - `element` to allow for chaining
*/
static replaceClass(element, oldName, newName) {
element.classList.replace(oldName, newName);
return element;
}
/**
* Appends `thisNode` to `thatNode`
* @param {Node} thisNode - Node to be appended to another node
* @param {Node} thatNode - Node for `thisNode` to be appended to
* @returns {Node} - `thisNode` to allow for chaining
*/
static appendTo(thisNode, thatNode) {
if (typeof(thatNode) == "string") thatNode = this.query(thatNode);
if (!thatNode) return null;
thatNode.append(thisNode);
return thisNode;
}
/**
* Prepends `thisNode` to `thatNode`
* @param {Node} thisNode - Node to be prepended to another node
* @param {Node} thatNode - Node for `thisNode` to be prepended to
* @returns {Node} - `thisNode` to allow for chaining
*/
static prependTo(thisNode, thatNode) {
if (typeof(thatNode) == "string") thatNode = this.query(thatNode);
if (!thatNode) return null;
thatNode.prepend(thisNode);
return thisNode;
}
/**
* Insert after a specific element, similar to jQuery's `thisElement.insertAfter(otherElement)`.
* @param {Node} thisNode - The node to insert
* @param {Node} targetNode - Node to insert after in the tree
* @returns {Node} - `thisNode` to allow for chaining
*/
static insertAfter(thisNode, targetNode) {
targetNode.parentNode.insertBefore(thisNode, targetNode.nextSibling);
return thisNode;
}
/**
* Insert after a specific element, similar to jQuery's `thisElement.after(newElement)`.
* @param {Node} thisNode - The node to insert
* @param {Node} newNode - Node to insert after in the tree
* @returns {Node} - `thisNode` to allow for chaining
*/
static after(thisNode, newNode) {
thisNode.parentNode.insertBefore(newNode, thisNode.nextSibling);
return thisNode;
}
/**
* Gets the next sibling element that matches the selector.
* @param {Element} element - Element to get the next sibling of
* @param {string} [selector=""] - Optional selector
* @returns {Element} - The sibling element
*/
static next(element, selector = "") {
return selector ? element.querySelector("+ " + selector) : element.nextElementSibling;
}
/**
* Gets all subsequent siblings.
* @param {Element} element - Element to get next siblings of
* @returns {NodeList} - The list of siblings
*/
static nextAll(element) {
return element.querySelectorAll("~ *");
}
/**
* Gets the subsequent siblings until an element matches the selector.
* @param {Element} element - Element to get the following siblings of
* @param {string} selector - Selector to stop at
* @returns {Array<Element>} - The list of siblings
*/
static nextUntil(element, selector) {
const next = [];
while (element.nextElementSibling && !element.nextElementSibling.matches(selector)) next.push(element = element.nextElementSibling);
return next;
}
/**
* Gets the previous sibling element that matches the selector.
* @param {Element} element - Element to get the previous sibling of
* @param {string} [selector=""] - Optional selector
* @returns {Element} - The sibling element
*/
static previous(element, selector = "") {
const previous = element.previousElementSibling;
if (selector) return previous && previous.matches(selector) ? previous : null;
return previous;
}
/**
* Gets all preceeding siblings.
* @param {Element} element - Element to get preceeding siblings of
* @returns {NodeList} - The list of siblings
*/
static previousAll(element) {
const previous = [];
while (element.previousElementSibling) previous.push(element = element.previousElementSibling);
return previous;
}
/**
* Gets the preceeding siblings until an element matches the selector.
* @param {Element} element - Element to get the preceeding siblings of
* @param {string} selector - Selector to stop at
* @returns {Array<Element>} - The list of siblings
*/
static previousUntil(element, selector) {
const previous = [];
while (element.previousElementSibling && !element.previousElementSibling.matches(selector)) previous.push(element = element.previousElementSibling);
return previous;
}
/**
* Find which index in children a certain node is. Similar to jQuery's `$.index()`
* @param {HTMLElement} node - The node to find its index in parent
* @returns {number} Index of the node
*/
static indexInParent(node) {
const children = node.parentNode.childNodes;
let num = 0;
for (let i = 0; i < children.length; i++) {
if (children[i] == node) return num;
if (children[i].nodeType == 1) num++;
}
return -1;
}
/** Shorthand for {@link module:DOMTools.indexInParent} */
static index(node) {return this.indexInParent(node);}
/**
* Gets the parent of the element if it matches the selector,
* otherwise returns null.
* @param {Element} element - Element to get parent of
* @param {string} [selector=""] - Selector to match parent
* @returns {(Element|null)} - The sibling element or null
*/
static parent(element, selector = "") {
return !selector || element.parentElement.matches(selector) ? element.parentElement : null;
}
/**
* Gets all children of Element that match the selector if provided.
* @param {Element} element - Element to get all children of
* @param {string} selector - Selector to match the children to
* @returns {Array<Element>} - The list of children
*/
static findChild(element, selector) {
return element.querySelector(":scope > " + selector);
}
/**
* Gets all children of Element that match the selector if provided.
* @param {Element} element - Element to get all children of
* @param {string} selector - Selector to match the children to
* @returns {Array<Element>} - The list of children
*/
static findChildren(element, selector) {
return element.querySelectorAll(":scope > " + selector);
}
/**
* Gets all ancestors of Element that match the selector if provided.
* @param {Element} element - Element to get all parents of
* @param {string} [selector=""] - Selector to match the parents to
* @returns {Array<Element>} - The list of parents
*/
static parents(element, selector = "") {
const parents = [];
if (selector) while (element.parentElement && element.parentElement.closest(selector)) parents.push(element = element.parentElement.closest(selector));
else while (element.parentElement) parents.push(element = element.parentElement);
return parents;
}
/**
* Gets the ancestors until an element matches the selector.
* @param {Element} element - Element to get the ancestors of
* @param {string} selector - Selector to stop at
* @returns {Array<Element>} - The list of parents
*/
static parentsUntil(element, selector) {
const parents = [];
while (element.parentElement && !element.parentElement.matches(selector)) parents.push(element = element.parentElement);
return parents;
}
/**
* Gets all siblings of the element that match the selector.
* @param {Element} element - Element to get all siblings of
* @param {string} [selector="*"] - Selector to match the siblings to
* @returns {Array<Element>} - The list of siblings
*/
static siblings(element, selector = "*") {
return Array.from(element.parentElement.children).filter(e => e != element && e.matches(selector));
}
/**
* Sets or gets css styles for a specific element. If `value` is provided
* then it sets the style and returns the element to allow for chaining,
* otherwise returns the style.
* @param {Element} element - Element to set the CSS of
* @param {string} attribute - Attribute to get or set
* @param {string} [value] - Value to set for attribute
* @returns {Element|string} - When setting a value, element is returned for chaining, otherwise the value is returned.
*/
static css(element, attribute, value) {
if (typeof(value) == "undefined") return global.getComputedStyle(element)[attribute];
element.style[attribute] = value;
return element;
}
/**
* Sets or gets the width for a specific element. If `value` is provided
* then it sets the width and returns the element to allow for chaining,
* otherwise returns the width.
* @param {Element} element - Element to set the CSS of
* @param {string} [value] - Width to set
* @returns {Element|string} - When setting a value, element is returned for chaining, otherwise the value is returned.
*/
static width(element, value) {
if (typeof(value) == "undefined") return parseInt(getComputedStyle(element).width);
element.style.width = value;
return element;
}
/**
* Sets or gets the height for a specific element. If `value` is provided
* then it sets the height and returns the element to allow for chaining,
* otherwise returns the height.
* @param {Element} element - Element to set the CSS of
* @param {string} [value] - Height to set
* @returns {Element|string} - When setting a value, element is returned for chaining, otherwise the value is returned.
*/
static height(element, value) {
if (typeof(value) == "undefined") return parseInt(getComputedStyle(element).height);
element.style.height = value;
return element;
}
/**
* Sets the inner text of an element if given a value, otherwise returns it.
* @param {Element} element - Element to set the text of
* @param {string} [text] - Content to set
* @returns {string} - Either the string set by this call or the current text content of the node.
*/
static text(element, text) {
if (typeof(text) == "undefined") return element.textContent;
return element.textContent = text;
}
/**
* Returns the innerWidth of the element.
* @param {Element} element - Element to retrieve inner width of
* @return {number} - The inner width of the element.
*/
static innerWidth(element) {
return element.clientWidth;
}
/**
* Returns the innerHeight of the element.
* @param {Element} element - Element to retrieve inner height of
* @return {number} - The inner height of the element.
*/
static innerHeight(element) {
return element.clientHeight;
}
/**
* Returns the outerWidth of the element.
* @param {Element} element - Element to retrieve outer width of
* @return {number} - The outer width of the element.
*/
static outerWidth(element) {
return element.offsetWidth;
}
/**
* Returns the outerHeight of the element.
* @param {Element} element - Element to retrieve outer height of
* @return {number} - The outer height of the element.
*/
static outerHeight(element) {
return element.offsetHeight;
}
/**
* Gets the offset of the element in the page.
* @param {Element} element - Element to get offset of
* @return {Offset} - The offset of the element
*/
static offset(element) {
return element.getBoundingClientRect();
}
static get listeners() { return this._listeners || (this._listeners = {}); }
/**
* This is similar to jQuery's `on` function and can *hopefully* be used in the same way.
*
* Rather than attempt to explain, I'll show some example usages.
*
* The following will add a click listener (in the `myPlugin` namespace) to `element`.
* `DOMTools.on(element, "click.myPlugin", () => {console.log("clicked!");});`
*
* The following will add a click listener (in the `myPlugin` namespace) to `element` that only fires when the target is a `.block` element.
* `DOMTools.on(element, "click.myPlugin", ".block", () => {console.log("clicked!");});`
*
* The following will add a click listener (without namespace) to `element`.
* `DOMTools.on(element, "click", () => {console.log("clicked!");});`
*
* The following will add a click listener (without namespace) to `element` that only fires once.
* `const cancel = DOMTools.on(element, "click", () => {console.log("fired!"); cancel();});`
*
* @param {Element} element - Element to add listener to
* @param {string} event - Event to listen to with option namespace (e.g. "event.namespace")
* @param {(string|callable)} delegate - Selector to run on element to listen to
* @param {callable} [callback] - Function to fire on event
* @returns {module:DOMTools~CancelListener} - A function that will undo the listener
*/
static on(element, event, delegate, callback) {
const [type, namespace] = event.split(".");
const hasDelegate = delegate && callback;
if (!callback) callback = delegate;
const eventFunc = !hasDelegate ? callback : function(event) {
if (event.target.matches(delegate)) {
callback(event);
}
};
element.addEventListener(type, eventFunc);
const cancel = () => {
element.removeEventListener(type, eventFunc);
};
if (namespace) {
if (!this.listeners[namespace]) this.listeners[namespace] = [];
const newCancel = () => {
cancel();
this.listeners[namespace].splice(this.listeners[namespace].findIndex(l => l.event == type && l.element == element), 1);
};
this.listeners[namespace].push({
event: type,
element: element,
cancel: newCancel
});
return newCancel;
}
return cancel;
}
/**
* Functionality for this method matches {@link module:DOMTools.on} but automatically cancels itself
* and removes the listener upon the first firing of the desired event.
*
* @param {Element} element - Element to add listener to
* @param {string} event - Event to listen to with option namespace (e.g. "event.namespace")
* @param {(string|callable)} delegate - Selector to run on element to listen to
* @param {callable} [callback] - Function to fire on event
* @returns {module:DOMTools~CancelListener} - A function that will undo the listener
*/
static once(element, event, delegate, callback) {
const [type, namespace] = event.split(".");
const hasDelegate = delegate && callback;
if (!callback) callback = delegate;
const eventFunc = !hasDelegate ? function(event) {
callback(event);
element.removeEventListener(type, eventFunc);
} : function(event) {
if (!event.target.matches(delegate)) return;
callback(event);
element.removeEventListener(type, eventFunc);
};
element.addEventListener(type, eventFunc);
const cancel = () => {
element.removeEventListener(type, eventFunc);
};
if (namespace) {
if (!this.listeners[namespace]) this.listeners[namespace] = [];
const newCancel = () => {
cancel();
this.listeners[namespace].splice(this.listeners[namespace].findIndex(l => l.event == type && l.element == element), 1);
};
this.listeners[namespace].push({
event: type,
element: element,
cancel: newCancel
});
return newCancel;
}
return cancel;
}
static __offAll(event, element) {
const [type, namespace] = event.split(".");
let matchFilter = listener => listener.event == type, defaultFilter = _ => _;
if (element) matchFilter = l => l.event == type && l.element == element, defaultFilter = l => l.element == element;
const listeners = this.listeners[namespace] || [];
const list = type ? listeners.filter(matchFilter) : listeners.filter(defaultFilter);
for (let c = 0; c < list.length; c++) list[c].cancel();
}
/**
* This is similar to jQuery's `off` function and can *hopefully* be used in the same way.
*
* Rather than attempt to explain, I'll show some example usages.
*
* The following will remove a click listener called `onClick` (in the `myPlugin` namespace) from `element`.
* `DOMTools.off(element, "click.myPlugin", onClick);`
*
* The following will remove a click listener called `onClick` (in the `myPlugin` namespace) from `element` that only fired when the target is a `.block` element.
* `DOMTools.off(element, "click.myPlugin", ".block", onClick);`
*
* The following will remove a click listener (without namespace) from `element`.
* `DOMTools.off(element, "click", onClick);`
*
* The following will remove all listeners in namespace `myPlugin` from `element`.
* `DOMTools.off(element, ".myPlugin");`
*
* The following will remove all click listeners in namespace `myPlugin` from *all elements*.
* `DOMTools.off("click.myPlugin");`
*
* The following will remove all listeners in namespace `myPlugin` from *all elements*.
* `DOMTools.off(".myPlugin");`
*
* @param {(Element|string)} element - Element to remove listener from
* @param {string} [event] - Event to listen to with option namespace (e.g. "event.namespace")
* @param {(string|callable)} [delegate] - Selector to run on element to listen to
* @param {callable} [callback] - Function to fire on event
* @returns {Element} - The original element to allow for chaining
*/
static off(element, event, delegate, callback) {
if (typeof(element) == "string") return this.__offAll(element);
const [type, namespace] = event.split(".");
if (namespace) return this.__offAll(event, element);
const hasDelegate = delegate && callback;
if (!callback) callback = delegate;
const eventFunc = !hasDelegate ? callback : function(event) {
if (event.target.matches(delegate)) {
callback(event);
}
};
element.removeEventListener(type, eventFunc);
return element;
}
/**
* Adds a listener for when the node is added/removed from the document body.
* The listener is automatically removed upon firing.
* @param {HTMLElement} node - node to wait for
* @param {callable} callback - function to be performed on event
* @param {boolean} onMount - determines if it should fire on Mount or on Unmount
*/
static onMountChange(node, callback, onMount = true) {
const wrappedCallback = () => {
this.observer.unsubscribe(wrappedCallback);
callback();
};
this.observer.subscribe(wrappedCallback, mutation => {
const nodes = Array.from(onMount ? mutation.addedNodes : mutation.removedNodes);
const directMatch = nodes.indexOf(node) > -1;
const parentMatch = nodes.some(parent => parent.contains(node));
return directMatch || parentMatch;
});
return node;
}
/** Shorthand for {@link module:DOMTools.onMountChange} with third parameter `true` */
static onMount(node, callback) { return this.onMountChange(node, callback); }
/** Shorthand for {@link module:DOMTools.onMountChange} with third parameter `false` */
static onUnmount(node, callback) { return this.onMountChange(node, callback, false); }
/** Alias for {@link module:DOMTools.onMount} */
static onAdded(node, callback) { return this.onMount(node, callback); }
/** Alias for {@link module:DOMTools.onUnmount} */
static onRemoved(node, callback) { return this.onUnmount(node, callback, false); }
/**
* Helper function which combines multiple elements into one parent element
* @param {Array<HTMLElement>} elements - array of elements to put into a single parent
*/
static wrap(elements) {
const domWrapper = this.parseHTML(`<div class="dom-wrapper"></div>`);
for (let e = 0; e < elements.length; e++) domWrapper.appendChild(elements[e]);
return domWrapper;
}
/**
* Resolves the node to an HTMLElement. This is mainly used by library modules.
* @param {(jQuery|Element)} node - node to resolve
*/
static resolveElement(node) {
if (!(node instanceof jQuery) && !(node instanceof Element)) return undefined;
return node instanceof jQuery ? node[0] : node;
}
}

View File

@ -0,0 +1,230 @@
import {bdpluginErrors, pluginCookie, settingsCookie, bdplugins} from "../0globals";
import ContentManager from "./contentManager";
import DataStore from "./dataStore";
import BDEvents from "./bdEvents";
import Utils from "./utils";
class PluginModule {
get folder() {return ContentManager.pluginsFolder;}
}
PluginModule.prototype.loadPlugins = function () {
this.loadPluginData();
bdpluginErrors.splice(0, 0, ...ContentManager.loadPlugins());
const plugins = Object.keys(bdplugins);
for (let i = 0; i < plugins.length; i++) {
let plugin, name;
try {
plugin = bdplugins[plugins[i]].plugin;
name = plugin.getName();
if (plugin.load && typeof(plugin.load) == "function") plugin.load();
}
catch (err) {
pluginCookie[name] = false;
Utils.err("Plugins", name + " could not be loaded.", err);
bdpluginErrors.push({name: name, file: bdplugins[plugins[i]].filename, message: "load() could not be fired.", error: {message: err.message, stack: err.stack}});
continue;
}
if (!pluginCookie[name]) pluginCookie[name] = false;
if (pluginCookie[name]) {
try {
plugin.start();
if (settingsCookie["fork-ps-2"]) Utils.showToast(`${plugin.getName()} v${plugin.getVersion()} has started.`);
}
catch (err) {
pluginCookie[name] = false;
Utils.err("Plugins", name + " could not be started.", err);
bdpluginErrors.push({name: name, file: bdplugins[plugins[i]].filename, message: "start() could not be fired.", error: {message: err.message, stack: err.stack}});
}
}
}
this.savePluginData();
require("electron").remote.getCurrentWebContents().on("did-navigate-in-page", this.channelSwitch.bind(this));
// if (settingsCookie["fork-ps-5"]) ContentManager.watchContent("plugin");
};
PluginModule.prototype.startPlugin = function(plugin, reload = false) {
try {
bdplugins[plugin].plugin.start();
if (settingsCookie["fork-ps-2"] && !reload) Utils.showToast(`${bdplugins[plugin].plugin.getName()} v${bdplugins[plugin].plugin.getVersion()} has started.`);
}
catch (err) {
if (settingsCookie["fork-ps-2"] && !reload) Utils.showToast(`${bdplugins[plugin].plugin.getName()} v${bdplugins[plugin].plugin.getVersion()} could not be started.`, {type: "error"});
pluginCookie[plugin] = false;
this.savePluginData();
Utils.err("Plugins", plugin + " could not be started.", err);
}
};
PluginModule.prototype.stopPlugin = function(plugin, reload = false) {
try {
bdplugins[plugin].plugin.stop();
if (settingsCookie["fork-ps-2"] && !reload) Utils.showToast(`${bdplugins[plugin].plugin.getName()} v${bdplugins[plugin].plugin.getVersion()} has stopped.`);
}
catch (err) {
if (settingsCookie["fork-ps-2"] && !reload) Utils.showToast(`${bdplugins[plugin].plugin.getName()} v${bdplugins[plugin].plugin.getVersion()} could not be stopped.`, {type: "error"});
Utils.err("Plugins", bdplugins[plugin].plugin.getName() + " could not be stopped.", err);
}
};
PluginModule.prototype.enablePlugin = function (plugin, reload = false) {
if (pluginCookie[plugin]) return;
pluginCookie[plugin] = true;
this.savePluginData();
this.startPlugin(plugin, reload);
};
PluginModule.prototype.enable = function (plugin, reload = false) {
return this.enablePlugin(plugin, reload);
};
PluginModule.prototype.disablePlugin = function (plugin, reload = false) {
if (!pluginCookie[plugin]) return;
pluginCookie[plugin] = false;
this.savePluginData();
this.stopPlugin(plugin, reload);
};
PluginModule.prototype.disable = function (plugin, reload = false) {
return this.disablePlugin(plugin, reload);
};
PluginModule.prototype.togglePlugin = function (plugin) {
if (pluginCookie[plugin]) this.disablePlugin(plugin);
else this.enablePlugin(plugin);
};
PluginModule.prototype.toggle = function (plugin, reload = false) {
return this.togglePlugin(plugin, reload);
};
PluginModule.prototype.loadPlugin = function(filename) {
const error = ContentManager.loadContent(filename, "plugin");
if (error) {
if (settingsCookie["fork-ps-1"]) Utils.showContentErrors({plugins: [error]});
if (settingsCookie["fork-ps-2"]) Utils.showToast(`${filename} could not be loaded.`, {type: "error"});
return Utils.err("ContentManager", `${filename} could not be loaded.`, error);
}
const plugin = Object.values(bdplugins).find(p => p.filename == filename).plugin;
try { if (plugin.load && typeof(plugin.load) == "function") plugin.load();}
catch (err) {if (settingsCookie["fork-ps-1"]) Utils.showContentErrors({plugins: [err]});}
Utils.log("ContentManager", `${plugin.getName()} v${plugin.getVersion()} was loaded.`);
if (settingsCookie["fork-ps-2"]) Utils.showToast(`${plugin.getName()} v${plugin.getVersion()} was loaded.`, {type: "success"});
BDEvents.dispatch("plugin-loaded", plugin.getName());
};
PluginModule.prototype.unloadPlugin = function(filenameOrName) {
const bdplugin = Object.values(bdplugins).find(p => p.filename == filenameOrName) || bdplugins[filenameOrName];
if (!bdplugin) return;
const plugin = bdplugin.plugin.getName();
if (pluginCookie[plugin]) this.disablePlugin(plugin, true);
const error = ContentManager.unloadContent(bdplugins[plugin].filename, "plugin");
delete bdplugins[plugin];
if (error) {
if (settingsCookie["fork-ps-1"]) Utils.showContentErrors({plugins: [error]});
if (settingsCookie["fork-ps-2"]) Utils.showToast(`${plugin} could not be unloaded. It may have not been loaded yet.`, {type: "error"});
return Utils.err("ContentManager", `${plugin} could not be unloaded. It may have not been loaded yet.`, error);
}
Utils.log("ContentManager", `${plugin} was unloaded.`);
if (settingsCookie["fork-ps-2"]) Utils.showToast(`${plugin} was unloaded.`, {type: "success"});
BDEvents.dispatch("plugin-unloaded", plugin);
};
PluginModule.prototype.delete = function(filenameOrName) {
const bdplugin = Object.values(bdplugins).find(p => p.filename == filenameOrName) || bdplugins[filenameOrName];
if (!bdplugin) return;
this.unloadPlugin(bdplugin.filename);
const fullPath = require("path").resolve(ContentManager.pluginsFolder, bdplugin.filename);
require("fs").unlinkSync(fullPath);
};
PluginModule.prototype.reloadPlugin = function(filenameOrName) {
const bdplugin = Object.values(bdplugins).find(p => p.filename == filenameOrName) || bdplugins[filenameOrName];
if (!bdplugin) return this.loadPlugin(filenameOrName);
const plugin = bdplugin.plugin.getName();
const enabled = pluginCookie[plugin];
if (enabled) this.stopPlugin(plugin, true);
const error = ContentManager.reloadContent(bdplugins[plugin].filename, "plugin");
if (error) {
if (settingsCookie["fork-ps-1"]) Utils.showContentErrors({plugins: [error]});
if (settingsCookie["fork-ps-2"]) Utils.showToast(`${plugin} could not be reloaded.`, {type: "error"});
return Utils.err("ContentManager", `${plugin} could not be reloaded.`, error);
}
if (bdplugins[plugin].plugin.load && typeof(bdplugins[plugin].plugin.load) == "function") bdplugins[plugin].plugin.load();
if (enabled) this.startPlugin(plugin, true);
Utils.log("ContentManager", `${plugin} v${bdplugins[plugin].plugin.getVersion()} was reloaded.`);
if (settingsCookie["fork-ps-2"]) Utils.showToast(`${plugin} v${bdplugins[plugin].plugin.getVersion()} was reloaded.`, {type: "success"});
BDEvents.dispatch("plugin-reloaded", plugin);
};
PluginModule.prototype.reload = function(name) {
return this.reloadPlugin(name);
};
PluginModule.prototype.edit = function(filenameOrName) {
console.log("Edit " + filenameOrName);
const bdplugin = Object.values(bdplugins).find(p => p.filename == filenameOrName) || bdplugins[filenameOrName];
if (!bdplugin) return;
const fullPath = require("path").resolve(ContentManager.pluginsFolder, bdplugin.filename);
console.log("Edit " + fullPath);
require("electron").shell.openItem(`${fullPath}`);
};
PluginModule.prototype.updatePluginList = function() {
const results = ContentManager.loadNewContent("plugin");
for (const filename of results.added) this.loadPlugin(filename);
for (const name of results.removed) this.unloadPlugin(name);
};
PluginModule.prototype.loadPluginData = function () {
const saved = DataStore.getSettingGroup("plugins");
if (saved) {
Object.assign(pluginCookie, saved);
}
};
PluginModule.prototype.savePluginData = function () {
DataStore.setSettingGroup("plugins", pluginCookie);
};
PluginModule.prototype.newMessage = function () {
const plugins = Object.keys(bdplugins);
for (let i = 0; i < plugins.length; i++) {
const plugin = bdplugins[plugins[i]].plugin;
if (!pluginCookie[plugin.getName()]) continue;
if (typeof plugin.onMessage === "function") {
try { plugin.onMessage(); }
catch (err) { Utils.err("Plugins", "Unable to fire onMessage for " + plugin.getName() + ".", err); }
}
}
};
PluginModule.prototype.channelSwitch = function () {
const plugins = Object.keys(bdplugins);
for (let i = 0; i < plugins.length; i++) {
const plugin = bdplugins[plugins[i]].plugin;
if (!pluginCookie[plugin.getName()]) continue;
if (typeof plugin.onSwitch === "function") {
try { plugin.onSwitch(); }
catch (err) { Utils.err("Plugins", "Unable to fire onSwitch for " + plugin.getName() + ".", err); }
}
}
};
PluginModule.prototype.rawObserver = function(e) {
const plugins = Object.keys(bdplugins);
for (let i = 0; i < plugins.length; i++) {
const plugin = bdplugins[plugins[i]].plugin;
if (!pluginCookie[plugin.getName()]) continue;
if (typeof plugin.observer === "function") {
try { plugin.observer(e); }
catch (err) { Utils.err("Plugins", "Unable to fire observer for " + plugin.getName() + ".", err); }
}
}
};
export default new PluginModule();

View File

@ -0,0 +1,85 @@
import {settingsCookie} from "../0globals";
import BDV2 from "./v2";
import webpackModules from "./webpackModules";
import Utils from "./utils";
import DOM from "./domtools";
import V2C_PublicServers from "../ui/publicservers/publicServers";
import Layer from "../ui/publicservers/layer";
export default new class V2_PublicServers {
constructor() {
this._appendButton = this._appendButton.bind(this);
}
get component() {
return BDV2.react.createElement(Layer, {rootId: "pubslayerroot", id: "pubslayer"}, BDV2.react.createElement(V2C_PublicServers, {rootId: "pubslayerroot"}));
}
get root() {
const _root = document.getElementById("pubslayerroot");
if (!_root) {
if (!this.injectRoot()) return null;
return this.root;
}
return _root;
}
injectRoot() {
let [
classNameLayers
] = [
BDModules.get(e => e.layers && e.layer)[0].layers.split(" ")[0]
]
const layers = DOM.query(".layers, ."+classNameLayers);
if (!layers) return false;
layers.append(DOM.createElement("<div id='pubslayerroot'>"));
return true;
}
render() {
const root = this.root;
if (!root) {
console.log("FAILED TO LOCATE ROOT: .layers");
return;
}
BDV2.reactDom.render(this.component, root);
}
get button() {
const btn = DOM.createElement(`<div id="bd-pub-li" class="${BDV2.guildClasses.listItem}">`);
if (!settingsCookie["bda-gs-1"]) btn.style.display = "none";
const label = DOM.createElement(`<div id="bd-pub-button" class="${"wrapper-25eVIn " + BDV2.guildClasses.circleButtonMask}">public</div>`);
label.addEventListener("click", () => {this.render();});
btn.append(label);
return btn;
}
_appendButton() {
let [
classNameScroller
] = [
BDModules.get(e => e.scroller && e.scrollbarWidth)[0].scroller
]
if (DOM.query("#bd-pub-li")) return;
const wrapper = BDV2.guildClasses.wrapper.split(" ")[0];
const guilds = DOM.query(`.${wrapper} .${classNameScroller} >:first-child`);
DOM.after(guilds, this.button);
}
addButton() {
if (this.guildPatch) return;
const GuildList = webpackModules.find(m => m.default && m.default.displayName == "NavigableGuilds");
const GuildListOld = webpackModules.findByDisplayName("Guilds");
if (!GuildList && !GuildListOld) Utils.warn("PublicServer", "Can't find GuildList component");
this.guildPatch = Utils.monkeyPatch(GuildList ? GuildList : GuildListOld.prototype, GuildList ? "default" : "render", {after: this._appendButton});
this._appendButton();
}
removeButton() {
this.guildPatch();
delete this.guildPatch;
DOM.query("#bd-pub-li").remove();
}
};

View File

@ -0,0 +1,48 @@
import Utils from "./utils";
import {settings} from "../0globals";
const electron = require("electron");
const fs = require("fs");
const path = require("path");
const BrowserWindow = electron.remote.BrowserWindow;
const webContents = electron.remote.getCurrentWebContents();
export default new class reactDevTools {
constructor() {
let extensionPath = "";
if (process.platform === "win32") extensionPath = path.resolve(process.env.LOCALAPPDATA, "Google/Chrome/User Data");
else if (process.platform === "linux") extensionPath = path.resolve(process.env.HOME, ".config/google-chrome");
else if (process.platform === "darwin") extensionPath = path.resolve(process.env.HOME, "Library/Application Support/Google/Chrome");
else extensionPath = path.resolve(process.env.HOME, ".config/chromium");
extensionPath += "/Default/Extensions/fmkadmapgofadopljbjfkapdkoienihi/";
if (fs.existsSync(extensionPath)) {
const versions = fs.readdirSync(extensionPath);
extensionPath = path.resolve(extensionPath, versions[versions.length - 1]);
}
this.extensionPath = extensionPath;
this.isExtensionInstalled = fs.existsSync(extensionPath);
this.listener = this.listener.bind(this);
settings["React DevTools"].hidden = !this.isExtensionInstalled;
}
listener() {
if (!this.isExtensionInstalled) return;
BrowserWindow.removeDevToolsExtension("React Developer Tools");
const didInstall = BrowserWindow.addDevToolsExtension(this.extensionPath);
if (didInstall) Utils.log("React DevTools", "Successfully installed react devtools.");
else Utils.err("React DevTools", "Couldn't find react devtools in chrome extensions!");
}
start() {
setImmediate(() => webContents.on("devtools-opened", this.listener));
if (webContents.isDevToolsOpened()) this.listener();
}
stop() {
webContents.removeListener("devtools-opened", this.listener);
}
};

View File

@ -0,0 +1,358 @@
import {settings, settingsCookie, bdplugins, bdthemes} from "../0globals";
import DataStore from "./dataStore";
import V2_SettingsPanel_Sidebar from "./settingsPanelSidebar";
import Utils from "./utils";
import BDV2 from "./v2";
import ContentManager from "./contentManager";
import BDEvents from "./bdEvents";
import coloredText from "./coloredText";
import tfHour from "./24hour";
import reactDevTools from "./reactDevTools";
import DOM from "./domtools";
import publicServersModule from "./publicServers";
import voiceMode from "./voiceMode";
import ClassNormalizer from "./classNormalizer";
import dMode from "./devMode";
import Tools from "../ui/tools";
import Scroller from "../ui/scroller";
import SectionedSettingsPanel from "../ui/sectionedSettingsPanel";
import SettingsPanel from "../ui/settingsPanel";
import CssEditor from "../ui/cssEditor";
import CardList from "../ui/addonlist";
import V2C_PresenceSettings from "../ui/presenceSettings";
import CustomRichPresence from "./CustomRichPresence";
export default new class V2_SettingsPanel {
constructor() {
this.sideBarOnClick = this.sideBarOnClick.bind(this);
this.onChange = this.onChange.bind(this);
this.updateSettings = this.updateSettings.bind(this);
this.sidebar = new V2_SettingsPanel_Sidebar(this.sideBarOnClick);
// this.buildPluginProps = this.buildPluginProps.bind(this);
// this.buildThemeProps = this.buildThemeProps.bind(this);
this.showOriginal = this.showOriginal.bind(this);
}
get root() {
const _root = DOM.query("#bd-settingspane-container");
if (!_root) {
if (!this.injectRoot()) return null;
return this.root;
}
return _root;
}
injectRoot() {
let [
classNameLayer,
classSidebar
] = [
BDModules.get(e => e.layer && e.animating)[0].layer.split(" ")[0],
BDModules.get(e => e.standardSidebarView)[0]
]
const sidebar = DOM.query("."+classNameLayer+" ."+classSidebar.standardSidebarView.split(" ")[0]+", ."+classNameLayer+" .ui-standard-sidebar-view");
if (!sidebar) return false;
const root = DOM.createElement(`<div id="bd-settingspane-container" class="${classSidebar.contentRegion} content-region">`);
sidebar.append(root);
Utils.onRemoved(root, () => {
BDV2.reactDom.unmountComponentAtNode(root);
});
return true;
}
get coreSettings() {
const settings = this.getSettings("core");
const categories = [...new Set(settings.map(s => s.category))];
const sections = categories.map(c => {return {title: c, settings: settings.filter(s => s.category == c)};});
return sections;
}
get lightcordSettings() {
const settings = this.getSettings("lightcord");
const categories = [...new Set(settings.map(s => s.category))];
const sections = categories.map(c => {return {title: c, settings: settings.filter(s => s.category == c)};});
return sections;
}
get PresenceSettings() {
return this.getSettings("status")
}
getSettings(category) {
return Object.keys(settings).reduce((arr, key) => {
const setting = settings[key];
if (setting.cat === category && setting.implemented && !setting.hidden) {
setting.text = key;
arr.push(setting);
}
return arr;
}, []);
}
sideBarOnClick(id) {
const contentRegion = DOM.query(".contentRegion-3nDuYy, .content-region");
contentRegion.style.display = "none";
this.root.style.display = "";
switch (id) {
case "core":
this.renderCoreSettings();
break;
case "customcss":
this.renderCustomCssEditor();
break;
case "plugins":
case "themes":
this.renderAddonPane(id);
break;
case "lightcord":
this.renderLightCordSettings()
break
case "status":
this.renderPresenceSettings()
}
}
onClick() {}
onChange(id, checked) {
this.updateSettings(id, checked);
}
updateSettings(id, enabled) {
settingsCookie[id] = enabled;
if (id == "bda-gs-2") {
if (enabled) DOM.addClass(document.body, "bd-minimal");
else DOM.removeClass(document.body, "bd-minimal");
}
if (id == "bda-gs-3") {
if (enabled) DOM.addClass(document.body, "bd-minimal-chan");
else DOM.removeClass(document.body, "bd-minimal-chan");
}
if (id == "bda-gs-1") {
if (enabled) publicServersModule.addButton();
else publicServersModule.removeButton();
}
if (id == "bda-gs-4") {
if (enabled) voiceMode.start();
else voiceMode.stop();
}
if (id == "bda-gs-5") {
if (enabled) DOM.addClass(DOM.query("#app-mount"), "bda-dark");
else DOM.removeClass(DOM.query("#app-mount"), "bda-dark");
}
if (enabled && id == "bda-gs-6") tfHour.inject24Hour();
if (id == "bda-gs-7") {
if (enabled) coloredText.injectColoredText();
else coloredText.removeColoredText();
}
if (id == "fork-ps-4") {
if (enabled) ClassNormalizer.start();
else ClassNormalizer.stop();
}
if (id == "fork-ps-5") {
if (enabled) {
ContentManager.watchContent("plugin");
ContentManager.watchContent("theme");
}
else {
ContentManager.unwatchContent("plugin");
ContentManager.unwatchContent("theme");
}
}
if (id == "fork-wp-1") {
Utils.setWindowPreference("transparent", enabled);
if (enabled) Utils.setWindowPreference("backgroundColor", null);
else Utils.setWindowPreference("backgroundColor", "#2f3136");
}
if (id == "bda-gs-8") {
if (enabled) dMode.startDebugListener();
else dMode.stopDebugListener();
}
if (id == "fork-dm-1") {
if (enabled) dMode.startCopySelector();
else dMode.stopCopySelector();
}
if (id === "reactDevTools") {
if (enabled) reactDevTools.start();
else reactDevTools.stop();
}
if (id === "lightcord-1") {
if (enabled) window.lightcordSettings.devMode = true
else window.lightcordSettings.devMode = false
}
if (id === "lightcord-2") {
if (enabled) window.lightcordSettings.callRingingBeat = true
else window.lightcordSettings.callRingingBeat = false
}
if (id === "lightcord-presence-1") {
if (enabled) CustomRichPresence.enable()
else CustomRichPresence.disable()
}
this.saveSettings();
}
async initializeSettings() {
if (settingsCookie.reactDevTools) reactDevTools.start();
if (settingsCookie["bda-gs-2"]) DOM.addClass(document.body, "bd-minimal");
if (settingsCookie["bda-gs-3"]) DOM.addClass(document.body, "bd-minimal-chan");
if (settingsCookie["bda-gs-1"]) publicServersModule.addButton();
if (settingsCookie["bda-gs-4"]) voiceMode.start();
if (settingsCookie["bda-gs-5"]) DOM.addClass(DOM.query("#app-mount"), "bda-dark");
if (settingsCookie["bda-gs-6"]) tfHour.inject24Hour();
if (settingsCookie["bda-gs-7"]) coloredText.injectColoredText();
if (settingsCookie["fork-ps-4"]) ClassNormalizer.start();
if (settingsCookie["lightcord-1"]) window.lightcordSettings.devMode = true
if (settingsCookie["lightcord-2"]) window.lightcordSettings.callRingingBeat = true
if (settingsCookie["lightcord-presence-1"]) CustomRichPresence.enable()
if (settingsCookie["fork-ps-5"]) {
ContentManager.watchContent("plugin");
ContentManager.watchContent("theme");
}
if (settingsCookie["bda-gs-8"]) dMode.startDebugListener();
if (settingsCookie["fork-dm-1"]) dMode.startCopySelector();
this.saveSettings();
}
saveSettings() {
DataStore.setSettingGroup("settings", settingsCookie);
}
loadSettings() {
Object.assign(settingsCookie, DataStore.getSettingGroup("settings"));
}
showOriginal() {
BDV2.reactDom.unmountComponentAtNode(this.root);
this.root.style.display = "none";
DOM.query("."+BDModules.get(e => e.contentRegion)[0].contentRegion.split(" ")[0]+", .content-region").style.display = "";
}
renderSidebar() {
const tabs = document.querySelectorAll("[class*='side-'] > [class*='item-']");
for (const element of tabs) {
element.removeEventListener("click", this.showOriginal);
element.addEventListener("click", this.showOriginal);
}
this.sidebar.render();
}
get coreComponent() {
return BDV2.react.createElement(Scroller, {contentColumn: true, fade: true, dark: true},
BDV2.react.createElement(SectionedSettingsPanel, {key: "cspanel", onChange: this.onChange, sections: this.coreSettings}),
BDV2.react.createElement(Tools, {key: "tools"})
);
}
get lightcordComponent() {
return BDV2.react.createElement(Scroller, {contentColumn: true, fade: true, dark: true},
BDV2.react.createElement(SectionedSettingsPanel, {key: "lspannel", onChange: this.onChange, sections: this.lightcordSettings}),
BDV2.react.createElement(Tools, {key: "tools"})
);
}
get PresenceComponent() {
return BDV2.react.createElement(Scroller, {contentColumn: true, fade: true, dark: true},
BDV2.react.createElement(V2C_PresenceSettings, {
key: "lspannel",
onChange: this.onChange,
settings: this.PresenceSettings
}),
BDV2.react.createElement(Tools, {key: "tools"})
);
}
get customCssComponent() {
return BDV2.react.createElement(Scroller, {contentColumn: true, fade: true, dark: true},
BDV2.react.createElement(CssEditor, {key: "csseditor"}),
BDV2.react.createElement(Tools, {key: "tools"})
);
}
renderCoreSettings() {
const root = this.root;
if (!root) return Utils.err("SettingsPanel", "FAILED TO LOCATE ROOT: .layer-3QrUeG .standardSidebarView-3F1I7i");
BDV2.reactDom.render(this.coreComponent, root);
}
renderLightCordSettings() {
const root = this.root;
if (!root) return Utils.err("SettingsPanel", "FAILED TO LOCATE ROOT: .layer-3QrUeG .standardSidebarView-3F1I7i");
BDV2.reactDom.render(this.lightcordComponent, root);
}
renderPresenceSettings() {
const root = this.root;
if (!root) return Utils.err("SettingsPanel", "FAILED TO LOCATE ROOT: .layer-3QrUeG .standardSidebarView-3F1I7i");
BDV2.reactDom.render(this.PresenceComponent, root);
}
renderCustomCssEditor() {
const root = this.root;
if (!root) return Utils.err("SettingsPanel", "FAILED TO LOCATE ROOT: .layer-3QrUeG .standardSidebarView-3F1I7i");
BDV2.reactDom.render(this.customCssComponent, root);
}
// renderAddonPane(type) {
// const root = this.root;
// if (!root) return Utils.err("SettingsPanel", "FAILED TO LOCATE ROOT: .layer-3QrUeG .standardSidebarView-3F1I7i");
// BDV2.reactDom.render(this.contentComponent(type), root);
// }
renderAddonPane(type) {
if (!this.root) return Utils.err("SettingsPanel", "FAILED TO LOCATE ROOT: .layer-3QrUeG .standardSidebarView-3F1I7i");
// I know this shouldn't be here, but when it isn't,
// React refuses to change the button when going
// between plugins and themes page... something
// to debug later.
class ContentList extends BDV2.react.Component {
constructor(props) {
super(props);
this.prefix = this.props.type.replace("s", "");
this.onChange = this.onChange.bind(this);
}
componentDidMount() {
BDEvents.on(`${this.prefix}-reloaded`, this.onChange);
BDEvents.on(`${this.prefix}-loaded`, this.onChange);
BDEvents.on(`${this.prefix}-unloaded`, this.onChange);
}
componentWillUnmount() {
BDEvents.off(`${this.prefix}-reloaded`, this.onChange);
BDEvents.off(`${this.prefix}-loaded`, this.onChange);
BDEvents.off(`${this.prefix}-unloaded`, this.onChange);
}
onChange() {
this.props.onChange(this.props.type);
}
render() {return this.props.children;}
}
const list = type === "plugins" ? Object.values(bdplugins) : Object.values(bdthemes);
return BDV2.reactDom.render(BDV2.react.createElement(ContentList, {type, onChange: this.sideBarOnClick}, BDV2.react.createElement(CardList, {type, list})), this.root);
}
};

View File

@ -0,0 +1,77 @@
import {bbdChangelog} from "../0globals";
import Utils from "./utils";
import BDV2 from "./v2";
import DOM from "./domtools";
import SideBar from "../ui/sidebar";
import History from "../ui/icons/history";
import TooltipWrap from "../ui/tooltipWrap";
export default class V2_SettingsPanel_Sidebar {
constructor(onClick) {
this.onClick = onClick;
}
get items() {
return [{
text: "Lightcord",
id: "lightcord"
}, {
text: "Settings",
id: "core"
}, {
text: "Plugins",
id: "plugins"
}, {
text: "Themes",
id: "themes"
}, {
text: "Custom CSS",
id: "customcss"
}, {
text: "Presence",
id: "status"
}];
}
get component() {
//<TooltipWrap color="black" side="top" text={title}>
const changelogButton = BDV2.react.createElement(TooltipWrap, {color: "black", side: "top", text: "Changelog"},
BDV2.react.createElement("div", {className: "bd-changelog-button", onClick: () => {Utils.showChangelogModal(bbdChangelog);}},
BDV2.react.createElement(History, {className: "bd-icon", size: "16px"})
)
);
return BDV2.react.createElement("span", null, BDV2.react.createElement(SideBar, {onClick: this.onClick, headerText: "Bandaged BD", headerButton: changelogButton, items: this.items}));
}
get root() {
const _root = DOM.query("#bd-settings-sidebar");
if (!_root) {
if (!this.injectRoot()) return null;
return this.root;
}
return _root;
}
injectRoot() {
const tabs = DOM.queryAll("[class*='side-'] > [class*='item-']:not([class*=Danger])");
const changeLog = tabs[tabs.length - 1];
if (!changeLog) return false;
changeLog.parentElement.insertBefore(DOM.createElement(`<div id="bd-settings-sidebar">`), changeLog.previousElementSibling);
return true;
}
render() {
const root = this.root;
if (!root) {
console.log("FAILED TO LOCATE ROOT: [class*='side-'] > [class*='item-']:not([class*=Danger])");
return;
}
BDV2.reactDom.render(this.component, root);
Utils.onRemoved(root, () => {
BDV2.reactDom.unmountComponentAtNode(root);
});
}
}

View File

@ -0,0 +1,144 @@
import {bdthemeErrors, themeCookie, settingsCookie, bdthemes} from "../0globals";
import ContentManager from "./contentManager";
import DataStore from "./dataStore";
import BDEvents from "./bdEvents";
import Utils from "./utils";
import DOM from "./domtools";
class ThemeModule {
get folder() {return ContentManager.themesFolder;}
}
ThemeModule.prototype.loadThemes = function () {
this.loadThemeData();
bdthemeErrors.splice(0, 0, ...ContentManager.loadThemes());
const themes = Object.keys(bdthemes);
for (let i = 0; i < themes.length; i++) {
const theme = bdthemes[themes[i]];
if (!themeCookie[theme.name]) themeCookie[theme.name] = false;
if (themeCookie[theme.name]) DOM.addStyle(DOM.escapeID(theme.id), unescape(theme.css));
}
for (const theme in themeCookie) {
if (!bdthemes[theme]) delete themeCookie[theme];
}
this.saveThemeData();
// if (settingsCookie["fork-ps-5"]) ContentManager.watchContent("theme");
};
ThemeModule.prototype.enableTheme = function(name, reload = false) {
themeCookie[name] = true;
this.saveThemeData();
const theme = bdthemes[name];
DOM.addStyle(DOM.escapeID(theme.id), unescape(theme.css));
if (settingsCookie["fork-ps-2"] && !reload) Utils.showToast(`${theme.name} v${theme.version} has been applied.`);
};
ThemeModule.prototype.enable = function (name, reload = false) {
return this.enableTheme(name, reload);
};
ThemeModule.prototype.disableTheme = function(name, reload = false) {
themeCookie[name] = false;
this.saveThemeData();
const theme = bdthemes[name];
DOM.removeStyle(DOM.escapeID(theme.id));
if (settingsCookie["fork-ps-2"] && !reload) Utils.showToast(`${theme.name} v${theme.version} has been disabled.`);
};
ThemeModule.prototype.disable = function (name, reload = false) {
return this.disableTheme(name, reload);
};
ThemeModule.prototype.toggleTheme = function(theme) {
if (themeCookie[theme]) this.disableTheme(theme);
else this.enableTheme(theme);
};
ThemeModule.prototype.toggle = function (name, reload = false) {
return this.toggleTheme(name, reload);
};
ThemeModule.prototype.loadTheme = function(filename) {
const error = ContentManager.loadContent(filename, "theme");
if (error) {
if (settingsCookie["fork-ps-1"]) Utils.showContentErrors({themes: [error]});
if (settingsCookie["fork-ps-2"]) Utils.showToast(`${filename} could not be loaded. It may not have been loaded.`, {type: "error"});
return Utils.err("ContentManager", `${filename} could not be loaded.`, error);
}
const theme = Object.values(bdthemes).find(p => p.filename == filename);
Utils.log("ContentManager", `${theme.name} v${theme.version} was loaded.`);
if (settingsCookie["fork-ps-2"]) Utils.showToast(`${theme.name} v${theme.version} was loaded.`, {type: "success"});
BDEvents.dispatch("theme-loaded", theme.name);
};
ThemeModule.prototype.unloadTheme = function(filenameOrName) {
const bdtheme = Object.values(bdthemes).find(p => p.filename == filenameOrName) || bdthemes[filenameOrName];
if (!bdtheme) return;
const theme = bdtheme.name;
if (themeCookie[theme]) this.disableTheme(theme, true);
const error = ContentManager.unloadContent(bdthemes[theme].filename, "theme");
delete bdthemes[theme];
if (error) {
if (settingsCookie["fork-ps-1"]) Utils.showContentErrors({themes: [error]});
if (settingsCookie["fork-ps-2"]) Utils.showToast(`${theme} could not be unloaded. It may have not been loaded yet.`, {type: "error"});
return Utils.err("ContentManager", `${theme} could not be unloaded. It may have not been loaded yet.`, error);
}
Utils.log("ContentManager", `${theme} was unloaded.`);
if (settingsCookie["fork-ps-2"]) Utils.showToast(`${theme} was unloaded.`, {type: "success"});
BDEvents.dispatch("theme-unloaded", theme);
};
ThemeModule.prototype.delete = function(filenameOrName) {
const bdplugin = Object.values(bdthemes).find(p => p.filename == filenameOrName) || bdthemes[filenameOrName];
if (!bdplugin) return;
this.unloadTheme(bdplugin.filename);
const fullPath = require("path").resolve(ContentManager.pluginsFolder, bdplugin.filename);
require("fs").unlinkSync(fullPath);
};
ThemeModule.prototype.reloadTheme = function(filenameOrName) {
const bdtheme = Object.values(bdthemes).find(p => p.filename == filenameOrName) || bdthemes[filenameOrName];
if (!bdtheme) return this.loadTheme(filenameOrName);
const theme = bdtheme.name;
const error = ContentManager.reloadContent(bdthemes[theme].filename, "theme");
if (themeCookie[theme]) this.disableTheme(theme, true), this.enableTheme(theme, true);
if (error) {
if (settingsCookie["fork-ps-1"]) Utils.showContentErrors({themes: [error]});
if (settingsCookie["fork-ps-2"]) Utils.showToast(`${theme} could not be reloaded.`, {type: "error"});
return Utils.err("ContentManager", `${theme} could not be reloaded.`, error);
}
Utils.log("ContentManager", `${theme} v${bdthemes[theme].version} was reloaded.`);
if (settingsCookie["fork-ps-2"]) Utils.showToast(`${theme} v${bdthemes[theme].version} was reloaded.`, {type: "success"});
BDEvents.dispatch("theme-reloaded", theme);
};
ThemeModule.prototype.reload = function(name) {
return this.reloadTheme(name);
};
ThemeModule.prototype.edit = function(filenameOrName) {
const bdplugin = Object.values(bdthemes).find(p => p.filename == filenameOrName) || bdthemes[filenameOrName];
if (!bdplugin) return;
const fullPath = require("path").resolve(ContentManager.themesFolder, bdplugin.filename);
require("electron").shell.openItem(`${fullPath}`);
};
ThemeModule.prototype.updateThemeList = function() {
const results = ContentManager.loadNewContent("theme");
for (const filename of results.added) this.loadTheme(filename);
for (const name of results.removed) this.unloadTheme(name);
};
ThemeModule.prototype.loadThemeData = function() {
const saved = DataStore.getSettingGroup("themes");
if (saved) {
Object.assign(themeCookie, saved);
}
};
ThemeModule.prototype.saveThemeData = function () {
DataStore.setSettingGroup("themes", themeCookie);
};
export default new ThemeModule();

View File

@ -0,0 +1,418 @@
import {bbdVersion, settingsCookie} from "../0globals";
import WebpackModules from "./webpackModules";
import BDV2 from "./v2";
import DOM from "./domtools";
export default class Utils {
/** Document/window width */
static get screenWidth() { return Math.max(document.documentElement.clientWidth, window.innerWidth || 0); }
/** Document/window height */
static get screenHeight() { return Math.max(document.documentElement.clientHeight, window.innerHeight || 0); }
static get WindowConfigFile() {
return this._windowConfigFile = null;
}
static getAllWindowPreferences() {
return {
transparent: settingsCookie["fork-wp-1"] || settingsCookie.transparency,
frame: settingsCookie.frame
};
}
static getWindowPreference(key) {
if (key === "transparent") return settingsCookie["fork-wp-1"] || settingsCookie.transparency;
if (key === "frame") return settingsCookie.frame;
return null;
}
static setWindowPreference(key, value) {
if (key === "transparent") return settingsCookie["fork-wp-1"] = settingsCookie.transparency = value;
if (key === "frame") return settingsCookie.frame = value;
return null;
}
static stripBOM(content) {
if (content.charCodeAt(0) === 0xFEFF) {
content = content.slice(1);
}
return content;
}
static getTextArea() {
return DOM.query("."+BDModules.get(e => e.channelTextArea && e.titleWrapper)[0].channelTextArea.split(" ")[0]+" textarea");
}
static insertText(textarea, text) {
textarea.focus();
textarea.selectionStart = 0;
textarea.selectionEnd = textarea.value.length;
document.execCommand("insertText", false, text);
}
static escapeID(id) {
return id.replace(/^[^a-z]+|[^\w-]+/gi, "-");
}
static log(moduleName, message) {
console.log(`%c[BandagedBD]%c [${moduleName}]%c ${message}`, "color: #3a71c1; font-weight: 700;", "color: #3a71c1;", "");
}
static warn(moduleName, message) {
console.warn(`%c[BandagedBD]%c [${moduleName}]%c ${message}`, "color: #E8A400; font-weight: 700;", "color: #E8A400;", "");
}
static err(moduleName, message, error) {
console.log(`%c[BandagedBD]%c [${moduleName}]%c ${message}`, "color: red; font-weight: 700;", "color: red;", "");
if (error) {
console.groupCollapsed("%cError: " + error.message, "color: red;");
console.error(error.stack);
console.groupEnd();
}
}
static escape(s) {
return s.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
}
static testJSON(data) {
try {
return JSON.parse(data);
}
catch (err) {
return false;
}
}
static isEmpty(obj) {
if (obj == null || obj == undefined || obj == "") return true;
if (typeof(obj) !== "object") return false;
if (Array.isArray(obj)) return obj.length == 0;
for (const key in obj) {
if (obj.hasOwnProperty(key)) return false;
}
return true;
}
static suppressErrors(method, message) {
return (...params) => {
try { return method(...params); }
catch (e) { this.err("SuppressedError", "Error occurred in " + message, e); }
};
}
static monkeyPatch(what, methodName, options) {
const {before, after, instead, once = false, silent = false, force = false} = options;
const displayName = options.displayName || what.displayName || what[methodName].displayName || what.name || what.constructor.displayName || what.constructor.name;
if (!silent) console.log("patch", methodName, "of", displayName); // eslint-disable-line no-console
if (!what[methodName]) {
if (force) what[methodName] = function() {};
else return console.error(methodName, "does not exist for", displayName); // eslint-disable-line no-console
}
const origMethod = what[methodName];
const cancel = () => {
if (!silent) console.log("unpatch", methodName, "of", displayName); // eslint-disable-line no-console
what[methodName] = origMethod;
};
what[methodName] = function() {
const data = {
thisObject: this,
methodArguments: arguments,
cancelPatch: cancel,
originalMethod: origMethod,
callOriginalMethod: () => data.returnValue = data.originalMethod.apply(data.thisObject, data.methodArguments)
};
if (instead) {
const tempRet = Utils.suppressErrors(instead, "`instead` callback of " + what[methodName].displayName)(data);
if (tempRet !== undefined) data.returnValue = tempRet;
}
else {
if (before) Utils.suppressErrors(before, "`before` callback of " + what[methodName].displayName)(data);
data.callOriginalMethod();
if (after) Utils.suppressErrors(after, "`after` callback of " + what[methodName].displayName)(data);
}
if (once) cancel();
return data.returnValue;
};
Object.assign(what[methodName], origMethod);
what[methodName].__monkeyPatched = true;
what[methodName].displayName = displayName;
if (!what[methodName].__originalMethod) {
what[methodName].__originalMethod = origMethod;
what[methodName].toString = function() {return origMethod.toString();};
}
return cancel;
}
static onRemoved(node, callback) {
const observer = new MutationObserver((mutations) => {
for (let m = 0; m < mutations.length; m++) {
const mutation = mutations[m];
const nodes = Array.from(mutation.removedNodes);
const directMatch = nodes.indexOf(node) > -1;
const parentMatch = nodes.some(parent => parent.contains(node));
if (directMatch || parentMatch) {
observer.disconnect();
callback();
}
}
});
observer.observe(document.body, {subtree: true, childList: true});
}
static getNestedProp(obj, path) {
return path.split(/\s?\.\s?/).reduce(function(obj, prop) {
return obj && obj[prop];
}, obj);
}
/**
* This shows a toast similar to android towards the bottom of the screen.
*
* @param {string} content The string to show in the toast.
* @param {object} options Options object. Optional parameter.
* @param {string} options.type Changes the type of the toast stylistically and semantically. Choices: "", "info", "success", "danger"/"error", "warning"/"warn". Default: ""
* @param {boolean} options.icon Determines whether the icon should show corresponding to the type. A toast without type will always have no icon. Default: true
* @param {number} options.timeout Adjusts the time (in ms) the toast should be shown for before disappearing automatically. Default: 3000
*/
static showToast(content, options = {}) {
if (!document.querySelector(".bd-toasts")) {
const container = document.querySelector("."+BDModules.get(e => e.sidebar && e.hasNotice)[0].sidebar.split(" ")[9]+" + div") || null;
const memberlist = container ? container.querySelector("."+BDModules.get(e => e.membersWrap)[0].membersWrap) : null;
const form = container ? container.querySelector("form") : null;
const left = container ? container.getBoundingClientRect().left : 310;
const right = memberlist ? memberlist.getBoundingClientRect().left : 0;
const width = right ? right - container.getBoundingClientRect().left : Utils.screenWidth - left - 240;
const bottom = form ? form.offsetHeight : 80;
const toastWrapper = document.createElement("div");
toastWrapper.classList.add("bd-toasts");
toastWrapper.style.setProperty("left", left + "px");
toastWrapper.style.setProperty("width", width + "px");
toastWrapper.style.setProperty("bottom", bottom + "px");
document.querySelector("#app-mount").appendChild(toastWrapper);
}
const {type = "", icon = true, timeout = 3000} = options;
const toastElem = document.createElement("div");
toastElem.classList.add("bd-toast");
if (type) toastElem.classList.add("toast-" + type);
if (type && icon) toastElem.classList.add("icon");
toastElem.innerText = content;
document.querySelector(".bd-toasts").appendChild(toastElem);
setTimeout(() => {
toastElem.classList.add("closing");
setTimeout(() => {
toastElem.remove();
if (!document.querySelectorAll(".bd-toasts .bd-toast").length) document.querySelector(".bd-toasts").remove();
}, 300);
}, timeout);
}
static alert(title, content) {
let modalModule = BDModules.get(e => e.modal && e.inner && !e.hideOnFullscreen)[0]
let headerModule = BDModules.get(e => e.header && e.responsiveWidthMobile && e.hideOnFullscreen)[0]
let footer2Module = BDModules.get(e => e.header && e.responsiveWidthMobile && e.focusLock)[0]
const modal = DOM.createElement(`<div class="bd-modal-wrapper theme-dark">
<div class="bd-backdrop ${BDModules.get(e => e.backdrop && e.backdropWithLayer)[0].backdrop}"></div>
<div class="bd-modal ${modalModule.modal}">
<div class="bd-modal-inner ${modalModule.inner}">
<div class="header ${headerModule.header}">
<div class="title">${title}</div>
</div>
<div class="bd-modal-body">
<div class="scroller-wrap fade">
<div class="scroller">
${content}
</div>
</div>
</div>
<div class="footer ${headerModule.footer} ${footer2Module.footer}">
<button type="button">Okay</button>
</div>
</div>
</div>
</div>`);
modal.querySelector(".footer button").addEventListener("click", () => {
DOM.addClass(modal, "closing");
setTimeout(() => { modal.remove(); }, 300);
});
modal.querySelector(".bd-backdrop").addEventListener("click", () => {
DOM.addClass(modal, "closing");
setTimeout(() => { modal.remove(); }, 300);
});
DOM.query("#app-mount").append(modal);
}
static showContentErrors({plugins: pluginErrors = [], themes: themeErrors = []}) {
if (!pluginErrors || !themeErrors) return;
if (!pluginErrors.length && !themeErrors.length) return;
let modalModule = BDModules.get(e => e.modal && e.inner && !e.hideOnFullscreen)[0]
let headerModule = BDModules.get(e => e.header && e.responsiveWidthMobile && e.hideOnFullscreen)[0]
let footer2Module = BDModules.get(e => e.header && e.responsiveWidthMobile && e.focusLock)[0]
const modal = DOM.createElement(`<div class="bd-modal-wrapper theme-dark">
<div class="bd-backdrop ${BDModules.get(e => e.backdrop && e.backdropWithLayer)[0].backdrop}"></div>
<div class="bd-modal bd-content-modal ${modalModule.modal}">
<div class="bd-modal-inner ${modalModule.inner}">
<div class="header ${headerModule.header}"><div class="title">Content Errors</div></div>
<div class="bd-modal-body">
<div class="tab-bar-container">
<div class="tab-bar TOP">
<div class="tab-bar-item">Plugins</div>
<div class="tab-bar-item">Themes</div>
</div>
</div>
<div class="table-header">
<div class="table-column column-name">Name</div>
<div class="table-column column-message">Message</div>
<div class="table-column column-error">Error</div>
</div>
<div class="scroller-wrap fade">
<div class="scroller">
</div>
</div>
</div>
<div class="footer ${headerModule.footer} ${footer2Module.footer}">
<button type="button">Okay</button>
</div>
</div>
</div>
</div>`);
function generateTab(errors) {
const container = DOM.createElement(`<div class="errors">`);
for (const err of errors) {
const error = DOM.createElement(`<div class="error">
<div class="table-column column-name">${err.name ? err.name : err.file}</div>
<div class="table-column column-message">${err.message}</div>
<div class="table-column column-error"><a class="error-link" href="">${err.error ? err.error.message : ""}</a></div>
</div>`);
container.append(error);
if (err.error) {
error.querySelectorAll("a").forEach(el => el.addEventListener("click", (e) => {
e.preventDefault();
Utils.err("ContentManager", `Error details for ${err.name ? err.name : err.file}.`, err.error);
}));
}
}
return container;
}
const tabs = [generateTab(pluginErrors), generateTab(themeErrors)];
modal.querySelectorAll(".tab-bar-item").forEach(el => el.addEventListener("click", (e) => {
e.preventDefault();
const selected = modal.querySelector(".tab-bar-item.selected");
if (selected) DOM.removeClass(selected, "selected");
DOM.addClass(e.target, "selected");
const scroller = modal.querySelector(".scroller");
scroller.innerHTML = "";
scroller.append(tabs[DOM.index(e.target)]);
}));
modal.querySelector(".footer button").addEventListener("click", () => {
DOM.addClass(modal, "closing");
setTimeout(() => { modal.remove(); }, 300);
});
modal.querySelector(".bd-backdrop").addEventListener("click", () => {
DOM.addClass(modal, "closing");
setTimeout(() => { modal.remove(); }, 300);
});
DOM.query("#app-mount").append(modal);
if (pluginErrors.length) modal.querySelector(".tab-bar-item").click();
else modal.querySelectorAll(".tab-bar-item")[1].click();
}
static showChangelogModal(options = {}) {
const ModalStack = WebpackModules.findByProps("push", "update", "pop", "popWithKey");
const ChangelogClasses = WebpackModules.findByProps("fixed", "improved");
const TextElement = WebpackModules.findByDisplayName("Text");
const FlexChild = WebpackModules.findByProps("Child");
const Titles = WebpackModules.findByProps("Tags", "default");
const Changelog = WebpackModules.find(m => m.defaultProps && m.defaultProps.selectable == false);
const MarkdownParser = WebpackModules.findByProps("defaultRules", "parse");
if (!Changelog || !ModalStack || !ChangelogClasses || !TextElement || !FlexChild || !Titles || !MarkdownParser) return;
const {image = "https://repository-images.githubusercontent.com/105473537/957b5480-7c26-11e9-8401-50fa820cbae5", description = "", changes = [], title = "BandagedBD", subtitle = `v${bbdVersion}`, footer} = options;
const ce = BDV2.React.createElement;
const changelogItems = [ce("img", {src: image})];
if (description) changelogItems.push(ce("p", null, MarkdownParser.parse(description)));
for (let c = 0; c < changes.length; c++) {
const entry = changes[c];
const type = ChangelogClasses[entry.type] ? ChangelogClasses[entry.type] : ChangelogClasses.added;
const margin = c == 0 ? ChangelogClasses.marginTop : "";
changelogItems.push(ce("h1", {className: `${type} ${margin}`,}, entry.title));
const list = ce("ul", null, entry.items.map(i => ce("li", null, MarkdownParser.parse(i))));
changelogItems.push(list);
}
const renderHeader = function() {
return ce(FlexChild.Child, {grow: 1, shrink: 1},
ce(Titles.default, {tag: Titles.Tags.H4}, title),
ce(TextElement,{size: TextElement.Sizes.SMALL, color: TextElement.Colors.STANDARD, className: ChangelogClasses.date}, subtitle)
);
};
const renderFooter = () => {
const Anchor = WebpackModules.find(m => m.displayName == "Anchor");
const AnchorClasses = WebpackModules.findByProps("anchorUnderlineOnHover") || {anchor: "anchor-3Z-8Bb", anchorUnderlineOnHover: "anchorUnderlineOnHover-2ESHQB"};
const joinSupportServer = (click) => {
click.preventDefault();
click.stopPropagation();
ModalStack.pop();
BDV2.joinBD2();
};
const supportLink = Anchor ? ce(Anchor, {onClick: joinSupportServer}, "Join our Discord Server.") : ce("a", {className: `${AnchorClasses.anchor} ${AnchorClasses.anchorUnderlineOnHover}`, onClick: joinSupportServer}, "Join our Discord Server.");
const defaultFooter = ce(TextElement,{size: TextElement.Sizes.SMALL, color: TextElement.Colors.STANDARD}, "Need support? ", supportLink);
return ce(FlexChild.Child, {grow: 1, shrink: 1}, footer ? footer : defaultFooter);
};
return ModalStack.push(function(props) {
return ce(Changelog, Object.assign({
className: ChangelogClasses.container,
selectable: true,
onScroll: _ => _,
onClose: _ => _,
renderHeader: renderHeader,
renderFooter: renderFooter,
children: changelogItems
}, props));
});
}
/**
* Shows a generic but very customizable confirmation modal with optional confirm and cancel callbacks.
* @param {string} title - title of the modal
* @param {(string|ReactElement|Array<string|ReactElement>)} children - a single or mixed array of react elements and strings. Every string is wrapped in Discord's `Markdown` component so strings will show and render properly.
* @param {object} [options] - options to modify the modal
* @param {boolean} [options.danger=false] - whether the main button should be red or not
* @param {string} [options.confirmText=Okay] - text for the confirmation/submit button
* @param {string} [options.cancelText=Cancel] - text for the cancel button
* @param {callable} [options.onConfirm=NOOP] - callback to occur when clicking the submit button
* @param {callable} [options.onCancel=NOOP] - callback to occur when clicking the cancel button
* @param {string} [options.key] - key used to identify the modal. If not provided, one is generated and returned
* @returns {string} - the key used for this modal
*/
static showConfirmationModal(title, content, options = {}) {
const ModalStack = WebpackModules.findByProps("push", "update", "pop", "popWithKey");
const Markdown = WebpackModules.findByDisplayName("Markdown");
const ConfirmationModal = WebpackModules.find(m => m.defaultProps && m.key && m.key() == "confirm-modal");
if (!ModalStack || !ConfirmationModal || !Markdown) return Utils.alert(title, content);
const emptyFunction = () => {};
const {onConfirm = emptyFunction, onCancel = emptyFunction, confirmText = "Okay", cancelText = "Cancel", danger = false, key = undefined} = options;
if (!Array.isArray(content)) content = [content];
content = content.map(c => typeof(c) === "string" ? BDV2.React.createElement(Markdown, null, c) : c);
return ModalStack.push(ConfirmationModal, {
header: title,
children: content,
red: danger,
confirmText: confirmText,
cancelText: cancelText,
onConfirm: onConfirm,
onCancel: onCancel
}, key);
}
}
Utils.showToast = Utils.suppressErrors(Utils.showToast, "Could not show toast.");

View File

@ -0,0 +1,141 @@
import {settings} from "../0globals";
export default new class V2 {
constructor() {
this.editorDetached = false;
this.WebpackModules = (() => {
const req = webpackJsonp.push([[], {__extra_id__: (module, exports, req) => module.exports = req}, [["__extra_id__"]]]);
delete req.m.__extra_id__;
delete req.c.__extra_id__;
const shouldProtect = theModule => {
if (theModule.remove && theModule.set && theModule.clear && theModule.get && !theModule.sort) return true;
if (theModule.getToken || theModule.getEmail || theModule.showToken) return true;
return false;
};
const protect = theModule => {
if (theModule.remove && theModule.set && theModule.clear && theModule.get && !theModule.sort) return null;
if (!theModule.getToken && !theModule.getEmail && !theModule.showToken) return theModule;
const proxy = new Proxy(theModule, {
getOwnPropertyDescriptor: function(obj, prop) {
if (prop === "getToken" || prop === "getEmail" || prop === "showToken") return undefined;
return Object.getOwnPropertyDescriptor(obj, prop);
},
get: function(obj, func) {
if (func == "getToken") return () => "mfa.XCnbKzo0CLIqdJzBnL0D8PfDruqkJNHjwHXtr39UU3F8hHx43jojISyi5jdjO52e9_e9MjmafZFFpc-seOMa";
if (func == "getEmail") return () => "puppet11112@gmail.com";
if (func == "showToken") return () => true;
// if (func == "__proto__") return proxy;
return obj[func];
}
});
return proxy;
};
const find = (filter) => {
for (const i in req.c) {
if (req.c.hasOwnProperty(i)) {
const m = req.c[i].exports;
if (m && m.__esModule && m.default && filter(m.default)) return protect(m.default);
if (m && filter(m)) return protect(m);
}
}
// console.warn("Cannot find loaded module in cache");
return null;
};
const findAll = (filter) => {
const modules = [];
for (const i in req.c) {
if (req.c.hasOwnProperty(i)) {
const m = req.c[i].exports;
if (m && m.__esModule && m.default && filter(m.default)) modules.push(protect(m.default));
else if (m && filter(m)) modules.push(protect(m));
}
}
return modules;
};
const findByUniqueProperties = (propNames) => find(module => propNames.every(prop => module[prop] !== undefined));
const findByPrototypes = (protoNames) => find(module => module.prototype && protoNames.every(protoProp => module.prototype[protoProp] !== undefined));
const findByDisplayName = (displayName) => find(module => module.displayName === displayName);
return {find, findAll, findByUniqueProperties, findByPrototypes, findByDisplayName};
})();
this.internal = {
react: this.WebpackModules.findByUniqueProperties(["Component", "PureComponent", "Children", "createElement", "cloneElement"]),
reactDom: this.WebpackModules.findByUniqueProperties(["findDOMNode"])
};
this.getInternalInstance = e => e[Object.keys(e).find(k => k.startsWith("__reactInternalInstance"))];
}
initialize() {
}
joinBD1() {this.InviteActions.acceptInviteAndTransitionToInviteChannel("0Tmfo5ZbORCRqbAd");}
leaveBD1() {this.GuildActions.leaveGuild("86004744966914048");}
joinBD2() {this.InviteActions.acceptInviteAndTransitionToInviteChannel("2HScm8j");}
leaveBD2() {this.GuildActions.leaveGuild("280806472928198656");}
/**
* @type {typeof React}
*/
get react() {return this.internal.react;}
get React() {return this.internal.react;}
get reactDom() {return this.internal.reactDom;}
get ReactDom() {return this.internal.reactDom;}
/**
* @type {typeof React.Component}
*/
get reactComponent() {return this.internal.react.Component;}
get ReactComponent() {return this.internal.react.Component;}
get anchorClasses() {return this.WebpackModules.findByUniqueProperties(["anchorUnderlineOnHover"]) || {anchor: "anchor-3Z-8Bb", anchorUnderlineOnHover: "anchorUnderlineOnHover-2ESHQB"};}
get slateEditorClasses() {return this.WebpackModules.findByUniqueProperties(["slateTextArea"]);}
get messageClasses() {return this.WebpackModules.findByUniqueProperties(["message", "containerCozy"]);}
get guildClasses() {
const guildsWrapper = BDModules.get(e => e.wrapper && e.unreadMentionsBar)[0];
const guilds = BDModules.get(e => e.guildsError && e.selected)[0]
const pill = BDModules.get(e => e.blobContainer)[0]
return Object.assign({}, guildsWrapper, guilds, pill);
}
get MessageContentComponent() {return this.WebpackModules.find(m => m.defaultProps && m.defaultProps.hasOwnProperty("disableButtons"));}
get MessageComponent() {return this.WebpackModules.find(m => m.default && m.default.displayName && m.default.displayName == "Message");}
get TimeFormatter() {return this.WebpackModules.findByUniqueProperties(["dateFormat"]);}
get TooltipWrapper() {return this.WebpackModules.findByDisplayName("Tooltip");}
get NativeModule() {return this.WebpackModules.findByUniqueProperties(["setBadge"]);}
get InviteActions() {return this.WebpackModules.findByUniqueProperties(["acceptInvite"]);}
get GuildActions() {return this.WebpackModules.findByUniqueProperties(["leaveGuild"]);}
get Tooltips() {return this.WebpackModules.find(m => m.hide && m.show && !m.search && !m.submit && !m.search && !m.activateRagingDemon && !m.dismiss);}
get KeyGenerator() {return this.WebpackModules.find(m => m.toString && /"binary"/.test(m.toString()));}
get LayerStack() {return this.WebpackModules.findByUniqueProperties(["popLayer"]);}
get UserStore() {return this.WebpackModules.findByUniqueProperties(["getCurrentUser"]);}
get ChannelStore() {return this.WebpackModules.findByUniqueProperties(["getChannel"]);}
get ChannelActions() {return this.WebpackModules.findByUniqueProperties(["openPrivateChannel"]);}
get PrivateChannelActions() {return this.WebpackModules.findByUniqueProperties(["selectPrivateChannel"]);}
openDM(userId) {
const selfId = this.UserStore.getCurrentUser().id;
if (selfId == userId) return;
const privateChannelId = this.ChannelStore.getDMFromUserId(userId);
if (privateChannelId) return this.PrivateChannelActions.selectPrivateChannel(privateChannelId);
this.ChannelActions.openPrivateChannel(selfId, userId);
}
parseSettings(cat) {
return Object.keys(settings).reduce((arr, key) => {
const setting = settings[key];
if (setting.cat === cat && setting.implemented && !setting.hidden) {
setting.text = key;
arr.push(setting);
} return arr;
}, []);
}
};

View File

@ -0,0 +1,25 @@
import DOM from "./domtools";
const style = `
.container-2Rl01u {
display: none!important;
}
.chat-3bRxxu {
display: none!important;
}
.sidebar-2K8pFh {
flex-grow: 1!important;
}
`;
export default new class VoiceMode {
start() {
DOM.addStyle("VoiceMode", style);
}
stop() {
DOM.removeStyle("VoiceMode");
}
};

View File

@ -0,0 +1,32 @@
const req = webpackJsonp.push([[], {__extra_id__: (module, exports, req) => module.exports = req}, [["__extra_id__"]]]);
delete req.m.__extra_id__;
delete req.c.__extra_id__;
const find = (filter) => {
for (const i in req.c) {
if (req.c.hasOwnProperty(i)) {
const m = req.c[i].exports;
if (m && m.__esModule && m.default && filter(m.default)) return m.default;
if (m && filter(m)) return m;
}
}
// console.warn("Cannot find loaded module in cache");
return null;
};
const findAll = (filter) => {
const modules = [];
for (const i in req.c) {
if (req.c.hasOwnProperty(i)) {
const m = req.c[i].exports;
if (m && m.__esModule && m.default && filter(m.default)) modules.push(m.default);
else if (m && filter(m)) modules.push(m);
}
}
return modules;
};
const findByProps = (...propNames) => find(module => propNames.every(prop => module[prop] !== undefined));
const findByPrototypes = (...protoNames) => find(module => module.prototype && protoNames.every(protoProp => module.prototype[protoProp] !== undefined));
const findByDisplayName = (displayName) => find(module => module.displayName === displayName);
export default {find, findAll, findByProps, findByPrototypes, findByDisplayName};

View File

@ -0,0 +1,201 @@
import {settingsCookie} from "../0globals";
import BDV2 from "../modules/v2";
import Utils from "../modules/utils";
import DOM from "../modules/domtools";
import XSvg from "./xSvg";
import ReloadIcon from "./reloadIcon";
import EditIcon from "./icons/edit";
import DeleteIcon from "./icons/delete";
import Switch from "./components/switch";
import TooltipWrap from "./tooltipWrap";
const React = BDV2.React;
const anchorClasses = BDV2.anchorClasses;
export default class V2C_PluginCard extends BDV2.reactComponent {
constructor(props) {
super(props);
this.onChange = this.onChange.bind(this);
this.showSettings = this.showSettings.bind(this);
this.setInitialState();
this.hasSettings = this.props.addon.plugin && typeof(this.props.addon.plugin.getSettingsPanel) === "function";
this.settingsPanel = "";
this.edit = this.edit.bind(this);
this.delete = this.delete.bind(this);
this.reload = this.reload.bind(this);
}
setInitialState() {
this.state = {
checked: this.props.enabled,
settings: false,
reloads: 0
};
}
showSettings() {
if (!this.hasSettings) return;
this.setState({settings: true});
}
closeSettings() {
this.panelRef.current.innerHTML = "";
this.setState({settingsOpen: false});
}
componentDidUpdate() {
if (!this.state.settings) return;
if (typeof this.settingsPanel === "object") {
this.refs.settingspanel.appendChild(this.settingsPanel);
}
if (!settingsCookie["fork-ps-3"]) return;
setImmediate(() => {
const isHidden = (container, element) => {
const cTop = container.scrollTop;
const cBottom = cTop + container.clientHeight;
const eTop = element.offsetTop;
const eBottom = eTop + element.clientHeight;
return (eTop < cTop || eBottom > cBottom);
};
const thisNode = this.refs.cardNode;
const container = thisNode.closest(".scroller");
if (!isHidden(container, thisNode)) return;
const thisNodeOffset = DOM.offset(thisNode);
const containerOffset = DOM.offset(container);
const original = container.scrollTop;
const endPoint = thisNodeOffset.top - containerOffset.top + container.scrollTop - 30;
DOM.animate({
duration: 300,
update: function(progress) {
if (endPoint > original) container.scrollTop = original + (progress * (endPoint - original));
else container.scrollTop = original - (progress * (original - endPoint));
}
});
});
}
getString(value) {
if (!value) return "???";
return typeof value == "string" ? value : value.toString();
}
get settingsComponent() {
try { this.settingsPanel = this.props.addon.plugin.getSettingsPanel(); }
catch (err) { Utils.err("Plugins", "Unable to get settings panel for " + this.name + ".", err); }
return BDV2.react.createElement("div", {className: "bd-card bd-addon-card settings-open ui-switch-item", ref: "cardNode"},
BDV2.react.createElement("div", {style: {"float": "right", "cursor": "pointer"}, onClick: () => {
this.refs.settingspanel.innerHTML = "";
this.setState({settings: false});
}},
BDV2.react.createElement(XSvg, null)
),
typeof this.settingsPanel === "object" && BDV2.react.createElement("div", {id: `plugin-settings-${this.name}`, className: "plugin-settings", ref: "settingspanel"}),
typeof this.settingsPanel !== "object" && BDV2.react.createElement("div", {id: `plugin-settings-${this.name}`, className: "plugin-settings", ref: "settingspanel", dangerouslySetInnerHTML: {__html: this.settingsPanel}})
);
}
buildTitle(name, version, author) {
const title = "{{name}} v{{version}} by {{author}}".split(/({{[A-Za-z]+}})/);
const nameIndex = title.findIndex(s => s == "{{name}}");
if (nameIndex) title[nameIndex] = React.createElement("span", {className: "name bda-name"}, name);
const versionIndex = title.findIndex(s => s == "{{version}}");
if (nameIndex) title[versionIndex] = React.createElement("span", {className: "version bda-version"}, version);
const authorIndex = title.findIndex(s => s == "{{author}}");
if (nameIndex) {
const props = {className: "author bda-author"};
if (author.link || author.id) {
props.className += ` ${anchorClasses.anchor} ${anchorClasses.anchorUnderlineOnHover}`;
props.target = "_blank";
if (author.link) props.href = author.link;
if (author.id) props.onClick = () => {BDV2.LayerStack.popLayer(); BDV2.openDM(author.id);};
}
title[authorIndex] = React.createElement(author.link || author.id ? "a" : "span", props, author.name);
}
return title.flat();
}
makeLink(title, url) {
const props = {className: "bda-link bda-link-website", target: "_blank"};
if (typeof(url) == "string") props.href = url;
if (typeof(url) == "function") props.onClick = (event) => {event.preventDefault(); event.stopPropagation(); url();};
return BDV2.react.createElement("a", props, title);
}
makeButton(title, children, action) {
return <TooltipWrap color="black" side="top" text={title}>
<div className="bd-addon-button" onClick={action}>{children}</div>
</TooltipWrap>;
}
get links() {
const links = [];
const addon = this.props.addon;
if (addon.website) links.push(this.makeLink("Website", addon.website));
if (addon.source) links.push(this.makeLink("Source", addon.source));
if (addon.invite) {
links.push(this.makeLink("Support Server", () => {
const tester = /\.gg\/(.*)$/;
let code = addon.invite;
if (tester.test(code)) code = code.match(tester)[1];
BDV2.LayerStack.popLayer();
BDV2.InviteActions.acceptInviteAndTransitionToInviteChannel(code);
}));
}
if (addon.donate) links.push(this.makeLink("Donate", addon.donate));
if (addon.patreon) links.push(this.makeLink("Patreon", addon.patreon));
return links;
}
get footer() {
const links = this.links;
return (links.length || this.hasSettings) && BDV2.react.createElement("div", {className: "bd-card-footer bda-footer"},
BDV2.react.createElement("span", {className: "bd-addon-links bda-links"},
...(links.map((element, index) => index < links.length - 1 ? [element, " | "] : element).flat())
),
this.hasSettings && BDV2.react.createElement("button", {onClick: this.showSettings, className: "bd-button bda-settings-button", disabled: !this.state.checked}, "Settings")
);
}
onChange() {
this.props.toggle && this.props.toggle(this.name);
this.setState({checked: !this.state.checked});
}
edit() {this.props.edit(this.name);}
delete() {this.props.remove(this.name);}
reload() {this.props.reload(this.name);}
get name() {return this.getString(this.props.addon.plugin ? this.props.addon.plugin.getName() : this.props.addon.name);}
get author() {return this.getString(this.props.addon.plugin ? this.props.addon.plugin.getAuthor() : this.props.addon.author);}
get description() {return this.getString(this.props.addon.plugin ? this.props.addon.plugin.getDescription() : this.props.addon.description);}
get version() {return this.getString(this.props.addon.plugin ? this.props.addon.plugin.getVersion() : this.props.addon.version);}
render() {
if (this.state.settings) return this.settingsComponent;
const {authorId, authorLink} = this.props.addon;
return BDV2.react.createElement("div", {className: "bd-card bd-addon-card settings-closed ui-switch-item"},
BDV2.react.createElement("div", {className: "bd-addon-header bda-header"},
BDV2.react.createElement("div", {className: "bd-card-title bda-header-title"}, this.buildTitle(this.name, this.version, {name: this.author, id: authorId, link: authorLink})),
BDV2.react.createElement("div", {className: "bd-addon-controls bda-controls"},
this.props.edit && this.makeButton("Edit", <EditIcon className="bd-icon" />, this.edit),
this.props.remove && this.makeButton("Delete", <DeleteIcon className="bd-icon" />, this.delete),
this.props.reload && this.makeButton("Reload", <ReloadIcon className="bd-icon" />, this.reload),
React.createElement(Switch, {onChange: this.onChange, checked: this.state.checked})
)
),
BDV2.react.createElement("div", {className: "bd-scroller-wrap bda-description-wrap scroller-wrap fade"},
BDV2.react.createElement("div", {className: "bd-scroller bd-addon-description bda-description scroller"}, this.description)
),
this.footer
);
}
}

View File

@ -0,0 +1,178 @@
import ErrorBoundary from "./errorBoundary";
import ContentColumn from "./contentColumn";
import Tools from "./tools";
import ReloadIcon from "./reloadIcon";
import AddonCard from "./addoncard";
import Scroller from "./scroller";
import Dropdown from "./components/dropdown";
import Search from "./components/search";
import {settingsCookie, pluginCookie, themeCookie} from "../0globals";
import ContentManager from "../modules/contentManager";
import BDV2 from "../modules/v2";
import pluginModule from "../modules/pluginModule";
import themeModule from "../modules/themeModule";
import WebpackModules from "../modules/webpackModules";
import BdApi from "../modules/bdApi";
const Tooltip = WebpackModules.findByDisplayName("Tooltip");
const React = BDV2.react;
export default class CardList extends BDV2.reactComponent {
constructor(props) {
super(props);
this.state = {sort: "name", ascending: true, query: ""};
this.isPlugins = this.props.type == "plugins";
this.cookie = this.isPlugins ? pluginCookie : themeCookie;
this.manager = this.isPlugins ? pluginModule : themeModule;
this.sort = this.sort.bind(this);
this.reverse = this.reverse.bind(this);
this.search = this.search.bind(this);
}
openFolder() {
require("electron").shell.openItem(this.isPlugins ? ContentManager.pluginsFolder : ContentManager.themesFolder);
}
edit(name) {
console.log(name);
this.manager.edit(name);
}
async delete(name) {
const shouldDelete = await this.confirmDelete(name);
if (!shouldDelete) return;
this.manager.delete(name);
}
confirmDelete(name) {
return new Promise(resolve => {
BdApi.showConfirmationModal("Are You Sure?", `Are you sure you want to delete ${name}?`, {
danger: true,
confirmText: "Delete",
onConfirm: () => {resolve(true);},
onCancel: () => {resolve(false);}
});
});
}
get sortOptions() {
return [
{label: "Name", value: "name"},
{label: "Author", value: "author"},
{label: "Version", value: "version"},
{label: "Recently Added", value: "added"},
{label: "Last Modified", value: "modified"},
{label: "File Size", value: "size"},
];
}
get directions() {
return [
{label: "Ascending", value: true},
{label: "Descending", value: false}
];
}
reverse(value) {
this.setState({ascending: value});
}
sort(value) {
this.setState({sort: value});
}
search(event) {
this.setState({query: event.target.value.toLocaleLowerCase()});
}
getProps(addon) {
return {
key: this.getName(addon),
enabled: this.cookie[this.getName(addon)],
toggle: this.manager.toggle.bind(this.manager),
//edit: this.edit.bind(this),
remove: this.delete.bind(this),
addon: addon
};
}
getString(value) {
if (!value) return "???";
return typeof value == "string" ? value : value.toString();
}
getAddons() {
const sortedAddons = this.props.list.sort((a, b) => {
const cap = this.state.sort.charAt(0).toUpperCase() + this.state.sort.slice(1);
const first = a.plugin && a.plugin[`get${cap}`] ? this.getString(a.plugin[`get${cap}`]()) : a[this.state.sort];
const second = b.plugin && b.plugin[`get${cap}`] ? this.getString(b.plugin[`get${cap}`]()) : b[this.state.sort];
if (typeof(first) == "string") return first.toLocaleLowerCase().localeCompare(second.toLocaleLowerCase());
if (first > second) return 1;
if (second > first) return -1;
return 0;
});
if (!this.state.ascending) sortedAddons.reverse();
const rendered = [];
for (let a = 0; a < sortedAddons.length; a++) {
const addon = sortedAddons[a];
if (this.state.query) {
let matches = null;
const name = this.getName(addon);
const author = this.getAuthor(addon);
const description = this.getDescription(addon);
const version = this.getVersion(addon);
if (name) matches = name.toLocaleLowerCase().includes(this.state.query);
if (author) matches = matches || author.toLocaleLowerCase().includes(this.state.query);
if (description) matches = matches || description.toLocaleLowerCase().includes(this.state.query);
if (version) matches = matches || version.toLocaleLowerCase().includes(this.state.query);
if (!matches) continue;
}
const props = this.getProps(addon);
rendered.push(<ErrorBoundary><AddonCard {...props} reload={!settingsCookie["fork-ps-5"] && this.manager.reload.bind(this.manager)} /></ErrorBoundary>);
}
return rendered;
}
getName(addon) {return this.getString(addon.plugin ? addon.plugin.getName() : addon.name);}
getAuthor(addon) {return this.getString(addon.plugin ? addon.plugin.getAuthor() : addon.author);}
getDescription(addon) {return this.getString(addon.plugin ? addon.plugin.getDescription() : addon.description);}
getVersion(addon) {return this.getString(addon.plugin ? addon.plugin.getVersion() : addon.version);}
render() {
const refreshIcon = <Tooltip color="black" position="top" text="Reload List">
{(props) =>
<ReloadIcon {...props} className="bd-icon bd-reload bd-reload-header" size="18px" onClick={async () => {
if (this.isPlugins) pluginModule.updatePluginList();
else themeModule.updateThemeList();
this.forceUpdate();
}} />
}</Tooltip>;
const addonCards = this.getAddons();
return <Scroller contentColumn={true} fade={true} dark={true}>
<ContentColumn title={`${this.props.type.toUpperCase()}${addonCards.length}`}>
<button key="folder-button" className="bd-button bd-pfbtn" onClick={this.openFolder.bind(this)}>Open {this.isPlugins ? "Plugin" : "Theme"} Folder</button>
{!settingsCookie["fork-ps-5"] && refreshIcon}
<div className="bd-controls bd-addon-controls">
<Search onChange={this.search} placeholder={`Search ${this.props.type}...`} />
<div className="bd-addon-dropdowns">
<div className="bd-select-wrapper">
<label className="bd-label">Sort by:</label>
<Dropdown options={this.sortOptions} onChange={this.sort} style="transparent" />
</div>
<div className="bd-select-wrapper">
<label className="bd-label">Order:</label>
<Dropdown options={this.directions} onChange={this.reverse} style="transparent" />
</div>
</div>
</div>
<div className="bda-slist bd-addon-list">{addonCards}</div>
</ContentColumn>
<Tools key="tools" />
</Scroller>;
}
}

View File

@ -0,0 +1,21 @@
import BDV2 from "../modules/v2";
export default class BDLogo extends BDV2.reactComponent {
render() {
return BDV2.react.createElement(
"svg",
{height: "100%", width: this.props.size || "16px", className: "bd-logo " + this.props.className, style: {fillRule: "evenodd", clipRule: "evenodd", strokeLinecap: "round", strokeLinejoin: "round"}, viewBox: "0 0 2000 2000"},
BDV2.react.createElement("metadata", null),
BDV2.react.createElement("defs", null,
BDV2.react.createElement("filter", {id: "shadow1"}, BDV2.react.createElement("feDropShadow", {"dx": "20", "dy": "0", "stdDeviation": "20", "flood-color": "rgba(0,0,0,0.35)"})),
BDV2.react.createElement("filter", {id: "shadow2"}, BDV2.react.createElement("feDropShadow", {"dx": "15", "dy": "0", "stdDeviation": "20", "flood-color": "rgba(255,255,255,0.15)"})),
BDV2.react.createElement("filter", {id: "shadow3"}, BDV2.react.createElement("feDropShadow", {"dx": "10", "dy": "0", "stdDeviation": "20", "flood-color": "rgba(0,0,0,0.35)"}))
),
BDV2.react.createElement("g", null,
BDV2.react.createElement("path", {style: {filter: "url(#shadow3)"}, d: "M1195.44+135.442L1195.44+135.442L997.6+136.442C1024.2+149.742+1170.34+163.542+1193.64+179.742C1264.34+228.842+1319.74+291.242+1358.24+365.042C1398.14+441.642+1419.74+530.642+1422.54+629.642L1422.54+630.842L1422.54+632.042C1422.54+773.142+1422.54+1228.14+1422.54+1369.14L1422.54+1370.34L1422.54+1371.54C1419.84+1470.54+1398.24+1559.54+1358.24+1636.14C1319.74+1709.94+1264.44+1772.34+1193.64+1821.44C1171.04+1837.14+1025.7+1850.54+1000+1863.54L1193.54+1864.54C1539.74+1866.44+1864.54+1693.34+1864.54+1296.64L1864.54+716.942C1866.44+312.442+1541.64+135.442+1195.44+135.442Z", fill: "#171717", opacity: "1"}),
BDV2.react.createElement("path", {style: {filter: "url(#shadow2)"}, d: "M1695.54+631.442C1685.84+278.042+1409.34+135.442+1052.94+135.442L361.74+136.442L803.74+490.442L1060.74+490.442C1335.24+490.442+1335.24+835.342+1060.74+835.342L1060.74+1164.84C1150.22+1164.84+1210.53+1201.48+1241.68+1250.87C1306.07+1353+1245.76+1509.64+1060.74+1509.64L361.74+1863.54L1052.94+1864.54C1409.24+1864.54+1685.74+1721.94+1695.54+1368.54C1695.54+1205.94+1651.04+1084.44+1572.64+999.942C1651.04+915.542+1695.54+794.042+1695.54+631.442Z", fill: "#3E82E5", opacity: "1"}),
BDV2.react.createElement("path", {style: {filter: "url(#shadow1)"}, d: "M1469.25+631.442C1459.55+278.042+1183.05+135.442+826.65+135.442L135.45+135.442L135.45+1004C135.45+1004+135.427+1255.21+355.626+1255.21C575.825+1255.21+575.848+1004+575.848+1004L577.45+490.442L834.45+490.442C1108.95+490.442+1108.95+835.342+834.45+835.342L664.65+835.342L664.65+1164.84L834.45+1164.84C923.932+1164.84+984.244+1201.48+1015.39+1250.87C1079.78+1353+1019.47+1509.64+834.45+1509.64L135.45+1509.64L135.45+1864.54L826.65+1864.54C1182.95+1864.54+1459.45+1721.94+1469.25+1368.54C1469.25+1205.94+1424.75+1084.44+1346.35+999.942C1424.75+915.542+1469.25+794.042+1469.25+631.442Z", fill: "#FFFFFF", opacity: "1"})
)
);
}
}

View File

@ -0,0 +1,44 @@
import BDV2 from "../modules/v2";
export default class V2C_Checkbox extends BDV2.reactComponent {
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
this.setInitialState();
}
setInitialState() {
this.state = {
checked: this.props.checked || false
};
}
render() {
return BDV2.react.createElement(
"li",
null,
BDV2.react.createElement(
"div",
{className: "checkbox "+BDModules.get(e => e.checkboxElement)[0].checkbox, onClick: this.onClick},
BDV2.react.createElement(
"div",
{className: "checkbox-inner "+BDModules.get(e => e.checkboxInner)[0].checkboxInner},
BDV2.react.createElement("input", {className: BDModules.get(e => e.checkboxElement)[0].checkboxElement, checked: this.state.checked, onChange: () => {}, type: "checkbox"}),
BDV2.react.createElement("span", null)
),
BDV2.react.createElement(
"span",
null,
this.props.text
)
)
);
}
onClick() {
this.props.onChange(this.props.id, !this.state.checked);
this.setState({
checked: !this.state.checked
});
}
}

View File

@ -0,0 +1,66 @@
import BDV2 from "../../modules/v2";
import Arrow from "../icons/downarrow";
const React = BDV2.React;
export default class Select extends React.Component {
constructor(props) {
super(props);
this.state = {open: false, value: this.props.value || this.props.options[0].value};
this.dropdown = React.createRef();
this.onChange = this.onChange.bind(this);
this.showMenu = this.showMenu.bind(this);
this.hideMenu = this.hideMenu.bind(this);
}
showMenu(event) {
event.preventDefault();
this.setState({open: true}, () => {
document.addEventListener("click", this.hideMenu);
});
}
hideMenu() {
this.setState({open: false}, () => {
document.removeEventListener("click", this.hideMenu);
});
}
onChange(value) {
this.setState({value});
if (this.props.onChange) this.props.onChange(value);
}
get selected() {return this.props.options.find(o => o.value == this.state.value);}
get options() {
const selected = this.selected;
return <div className="bd-select-options">
{this.props.options.map(opt =>
<div className={`bd-select-option${selected.value == opt.value ? " selected" : ""}`} onClick={this.onChange.bind(this, opt.value)}>{opt.label}</div>
)}
</div>;
}
render() {
const style = this.props.style == "transparent" ? " bd-select-transparent" : "";
const isOpen = this.state.open ? " menu-open" : "";
return <div className={`bd-select${style}${isOpen}`} onClick={this.showMenu} ref={this.dropdown}>
<div className="bd-select-value">{this.selected.label}</div>
<Arrow className="bd-select-arrow" />
{this.state.open && this.options}
</div>;
}
}
// return <div className="bd-select-wrap">
// <label className="bd-label">{this.props.label}</label>
// <div className={`bd-select${style}${isOpen}`} onClick={this.showMenu} ref={this.dropdown}>
// <div className="bd-select-controls">
// <div className="bd-select-value">{this.selected.label}</div>
// <Arrow className="bd-select-arrow" />
// </div>
// </div>
// {this.state.open && this.options}
// </div>;

View File

@ -0,0 +1,13 @@
import BDV2 from "../../modules/v2";
import SearchIcon from "../icons/search";
const React = BDV2.React;
export default class Search extends React.Component {
render() {
return <div className="bd-search-wrapper">
<input onChange={this.props.onChange} onKeyDown={this.props.onKeyDown} type="text" className="bd-search" placeholder={this.props.placeholder} maxLength="50" />
<SearchIcon />
</div>;
}
}

View File

@ -0,0 +1,25 @@
import BDV2 from "../../modules/v2";
const React = BDV2.React;
export default class Switch extends React.Component {
constructor(props) {
super(props);
this.state = {checked: this.props.checked};
this.onChange = this.onChange.bind(this);
}
onChange() {
if (this.props.disabled) return;
this.props.onChange(!this.state.checked);
this.setState({checked: !this.state.checked});
}
render() {
const enabledClass = this.props.disabled ? " bd-switch-disabled" : "";
const checkedClass = this.state.checked ? " bd-switch-checked" : "";
return <div className={`bd-switch` + enabledClass + checkedClass}>
<input type="checkbox" id={this.props.id} className={`bd-checkbox`} disabled={this.props.disabled} checked={this.state.checked} onChange={this.onChange} />
</div>;
}
}

View File

@ -0,0 +1,23 @@
import BDV2 from "../modules/v2";
export default class V2C_ContentColumn extends BDV2.reactComponent {
constructor(props) {
super(props);
}
static get displayName() {return "ContentColumn";}
render() {
let contentModule = BDModules.get(e => e.contentColumn)[0]
return BDV2.react.createElement(
"div",
{className: contentModule.contentColumn + " "+contentModule.contentColumnDefault+" content-column default"},
BDV2.react.createElement(
"h2",
{className: "ui-form-title h2 margin-reset margin-bottom-20"},
this.props.title
),
this.props.children
);
}
}

View File

@ -0,0 +1,236 @@
import {settingsCookie} from "../0globals";
import Settings from "../modules/settingsPanel";
import BDV2 from "../modules/v2";
import DataStore from "../modules/dataStore";
import DOM from "../modules/domtools";
import SettingsTitle from "./settingsTitle";
import Checkbox from "./checkbox";
import V2C_CssEditorDetached from "./cssEditorDetached";
export default class V2C_CssEditor extends BDV2.reactComponent {
constructor(props) {
super(props);
const self = this;
self.props.lines = 0;
self.setInitialState();
self.attach = self.attach.bind(self);
self.detachedEditor = BDV2.react.createElement(V2C_CssEditorDetached, {attach: self.attach});
self.onClick = self.onClick.bind(self);
self.updateCss = self.updateCss.bind(self);
self.saveCss = self.saveCss.bind(self);
self.detach = self.detach.bind(self);
}
setInitialState() {
this.state = {
detached: this.props.detached || BDV2.editorDetached
};
}
componentDidMount() {
// this.updateLineCount();
this.editor = ace.edit("bd-customcss-editor");
this.editor.setTheme("ace/theme/monokai");
this.editor.session.setMode("ace/mode/css");
this.editor.setShowPrintMargin(false);
this.editor.setFontSize(14);
this.editor.on("change", () => {
if (!settingsCookie["bda-css-0"]) return;
this.saveCss();
this.updateCss();
});
}
componentWillUnmount() {
this.editor.destroy();
}
componentDidUpdate(prevProps, prevState) {
const self = this;
if (prevState.detached && !self.state.detached) {
BDV2.reactDom.unmountComponentAtNode(self.detachedRoot);
}
}
codeMirror() {
}
get options() {
return {
lineNumbers: true,
mode: "css",
indentUnit: 4,
theme: "material",
scrollbarStyle: "simple"
};
}
get css() {
const _ccss = DataStore.getBDData("bdcustomcss");
let ccss = "";
if (_ccss && _ccss !== "") {
ccss = atob(_ccss);
}
return ccss;
}
updateLineCount() {
const lineCount = this.refs.editor.value.split("\n").length;
if (lineCount == this.props.lines) return;
this.refs.lines.textContent = Array.from(new Array(lineCount), (_, i) => i + 1).join(".\n") + ".";
this.props.lines = lineCount;
}
render() {
const self = this;
const {detached} = self.state;
let contentModule = BDModules.get(e => e.contentColumn)[0]
return BDV2.react.createElement(
"div",
{className: contentModule.contentColumn+" "+contentModule.contentColumnDefault+" content-column default", style: {padding: "60px 40px 0px"}},
detached && BDV2.react.createElement(
"div",
{id: "editor-detached"},
BDV2.react.createElement(SettingsTitle, {text: "Custom CSS Editor"}),
BDV2.react.createElement(
"h3",
null,
"Editor Detached"
),
BDV2.react.createElement(
"button",
{className: "btn btn-primary", onClick: () => {
self.attach();
}},
"Attach"
)
),
!detached && BDV2.react.createElement(
"div",
null,
BDV2.react.createElement(SettingsTitle, {text: "Custom CSS Editor"}),
BDV2.react.createElement("div", {className: "editor-wrapper"},
BDV2.react.createElement("div", {id: "bd-customcss-editor", className: "editor", ref: "editor"}, self.css)
),
BDV2.react.createElement(
"div",
{id: "bd-customcss-attach-controls"},
BDV2.react.createElement(
"ul",
{className: "checkbox-group"},
BDV2.react.createElement(Checkbox, {id: "live-update", text: "Live Update", onChange: this.onChange, checked: settingsCookie["bda-css-0"]})
),
BDV2.react.createElement(
"div",
{id: "bd-customcss-detach-controls-button"},
BDV2.react.createElement(
"button",
{style: {borderRadius: "3px 0 0 3px", borderRight: "1px solid #3f4146"}, className: "btn btn-primary", onClick: () => {
self.onClick("update");
}},
"Update"
),
BDV2.react.createElement(
"button",
{style: {borderRadius: "0", borderLeft: "1px solid #2d2d2d", borderRight: "1px solid #2d2d2d"}, className: "btn btn-primary", onClick: () => {
self.onClick("save");
}},
"Save"
),
BDV2.react.createElement(
"button",
{style: {borderRadius: "0 3px 3px 0", borderLeft: "1px solid #3f4146"}, className: "btn btn-primary", onClick: () => {
self.onClick("detach");
}},
"Detach"
),
BDV2.react.createElement(
"span",
{style: {fontSize: "10px", marginLeft: "5px"}},
"Unsaved changes are lost on detach"
),
BDV2.react.createElement("div", {className: "help-text"},
"Press ",
BDV2.react.createElement("code", {className: "inline"}, "ctrl"),
"+",
BDV2.react.createElement("span", {className: "inline"}, ","),
" with the editor focused to access the editor's settings."
)
)
)
)
);
}
onClick(arg) {
const self = this;
switch (arg) {
case "update":
self.updateCss();
break;
case "save":
self.saveCss();
break;
case "detach":
self.detach();
break;
}
}
onChange(id, checked) {
switch (id) {
case "live-update":
settingsCookie["bda-css-0"] = checked;
Settings.saveSettings();
break;
}
}
updateCss() {
DOM.removeStyle("customcss");
DOM.addStyle("customcss", this.editor.session.getValue());
}
saveCss() {
DataStore.setBDData("bdcustomcss", btoa(this.editor.session.getValue()));
}
detach() {
const self = this;
self.setState({
detached: true
});
const droot = self.detachedRoot;
if (!droot) {
console.log("FAILED TO INJECT ROOT: .app");
return;
}
BDV2.reactDom.render(self.detachedEditor, droot);
}
get detachedRoot() {
const _root = DOM.query("#bd-customcss-detach-container");
if (!_root) {
if (!this.injectDetachedRoot()) return null;
return this.detachedRoot;
}
return _root;
}
injectDetachedRoot() {
const app = DOM.query(".app, ."+BDModules.get(e => e.app && e.layers)[0].app.split(" ")[0]);
if (!app) return false;
DOM.insertAfter(DOM.createElement(`<div id="bd-customcss-detach-container">`), app);
return true;
}
attach() {
const self = this;
self.setState({
detached: false
});
}
}

View File

@ -0,0 +1,173 @@
import {settingsCookie} from "../0globals";
import Settings from "../modules/settingsPanel";
import BDV2 from "../modules/v2";
import DataStore from "../modules/dataStore";
import DOM from "../modules/domtools";
import Checkbox from "./checkbox";
export default class V2C_CssEditorDetached extends BDV2.reactComponent {
constructor(props) {
super(props);
const self = this;
self.onClick = self.onClick.bind(self);
self.updateCss = self.updateCss.bind(self);
self.saveCss = self.saveCss.bind(self);
self.onChange = self.onChange.bind(self);
}
componentDidMount() {
DOM.addClass(DOM.query("#app-mount"), "bd-detached-editor");
BDV2.editorDetached = true;
// this.updateLineCount();
this.editor = ace.edit("bd-customcss-editor-detached");
this.editor.setTheme("ace/theme/monokai");
this.editor.session.setMode("ace/mode/css");
this.editor.setShowPrintMargin(false);
this.editor.setFontSize(14);
this.editor.on("change", () => {
if (!settingsCookie["bda-css-0"]) return;
this.saveCss();
this.updateCss();
});
}
componentWillUnmount() {
DOM.removeClass(DOM.query("#app-mount"), "bd-detached-editor");
BDV2.editorDetached = false;
this.editor.destroy();
}
updateLineCount() {
const lineCount = this.refs.editor.value.split("\n").length;
if (lineCount == this.props.lines) return;
this.refs.lines.textContent = Array.from(new Array(lineCount), (_, i) => i + 1).join(".\n") + ".";
this.props.lines = lineCount;
}
get options() {
return {
lineNumbers: true,
mode: "css",
indentUnit: 4,
theme: "material",
scrollbarStyle: "simple"
};
}
get css() {
const _ccss = DataStore.getBDData("bdcustomcss");
let ccss = "";
if (_ccss && _ccss !== "") {
ccss = atob(_ccss);
}
return ccss;
}
get root() {
const _root = DOM.query("#bd-customcss-detach-container");
if (!_root) {
if (!this.injectRoot()) return null;
return this.detachedRoot;
}
return _root;
}
injectRoot() {
const app = DOM.query(".app, ."+BDModules.get(e => e.app && e.layers)[0].app.split(" ")[0]);
if (!app) return false;
DOM.insertAfter(DOM.createElement(`<div id="bd-customcss-detach-container">`), app);
return true;
}
render() {
const self = this;
return BDV2.react.createElement(
"div",
{className: "bd-detached-css-editor", id: "bd-customcss-detach-editor"},
BDV2.react.createElement(
"div",
{id: "bd-customcss-innerpane"},
BDV2.react.createElement("div", {className: "editor-wrapper"},
BDV2.react.createElement("div", {id: "bd-customcss-editor-detached", className: "editor", ref: "editor"}, self.css)
),
BDV2.react.createElement(
"div",
{id: "bd-customcss-attach-controls"},
BDV2.react.createElement(
"ul",
{className: "checkbox-group"},
BDV2.react.createElement(Checkbox, {id: "live-update", text: "Live Update", onChange: self.onChange, checked: settingsCookie["bda-css-0"]})
),
BDV2.react.createElement(
"div",
{id: "bd-customcss-detach-controls-button"},
BDV2.react.createElement(
"button",
{style: {borderRadius: "3px 0 0 3px", borderRight: "1px solid #3f4146"}, className: "btn btn-primary", onClick: () => {
self.onClick("update");
}},
"Update"
),
BDV2.react.createElement(
"button",
{style: {borderRadius: "0", borderLeft: "1px solid #2d2d2d", borderRight: "1px solid #2d2d2d"}, className: "btn btn-primary", onClick: () => {
self.onClick("save");
}},
"Save"
),
BDV2.react.createElement(
"button",
{style: {borderRadius: "0 3px 3px 0", borderLeft: "1px solid #3f4146"}, className: "btn btn-primary", onClick: () => {
self.onClick("attach");
}},
"Attach"
),
BDV2.react.createElement(
"span",
{style: {fontSize: "10px", marginLeft: "5px"}},
"Unsaved changes are lost on attach"
)
)
)
)
);
}
onChange(id, checked) {
switch (id) {
case "live-update":
settingsCookie["bda-css-0"] = checked;
Settings.saveSettings();
break;
}
}
onClick(id) {
const self = this;
switch (id) {
case "attach":
if (DOM.query("#editor-detached")) self.props.attach();
BDV2.reactDom.unmountComponentAtNode(self.root);
self.root.remove();
break;
case "update":
self.updateCss();
break;
case "save":
self.saveCss();
break;
}
}
updateCss() {
DOM.removeStyle("customcss");
DOM.addStyle("customcss", this.editor.session.getValue());
}
saveCss() {
DataStore.setBDData("bdcustomcss", btoa(this.editor.session.getValue()));
}
}

View File

@ -0,0 +1,17 @@
import BDV2 from "../modules/v2";
export default class BDErrorBoundary extends BDV2.reactComponent {
constructor(props) {
super(props);
this.state = {hasError: false};
}
componentDidCatch() {
this.setState({hasError: true});
}
render() {
if (this.state.hasError) return BDV2.react.createElement("div", {className: "react-error"}, "Component Error");
return this.props.children;
}
}

View File

@ -0,0 +1,13 @@
import BDV2 from "../../modules/v2";
const React = BDV2.React;
export default class Delete extends React.Component {
render() {
const size = this.props.size || "24px";
return <svg className={this.props.className || ""} fill="#FFFFFF" viewBox="0 0 24 24" style={{width: size, height: size}} onClick={this.props.onClick}>
<path fill="none" d="M0 0h24v24H0V0z"/><path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zm2.46-7.12l1.41-1.41L12 12.59l2.12-2.12 1.41 1.41L13.41 14l2.12 2.12-1.41 1.41L12 15.41l-2.12 2.12-1.41-1.41L10.59 14l-2.13-2.12zM15.5 4l-1-1h-5l-1 1H5v2h14V4z"/>
<path fill="none" d="M0 0h24v24H0z"/>
</svg>;
}
}

View File

@ -0,0 +1,12 @@
import BDV2 from "../../modules/v2";
const React = BDV2.React;
export default class DownArrow extends React.Component {
render() {
const size = this.props.size || "16px";
return <svg className={this.props.className || ""} fill="#FFFFFF" viewBox="0 0 24 24" style={{width: size, height: size}}>
<path d="M8.12 9.29L12 13.17l3.88-3.88c.39-.39 1.02-.39 1.41 0 .39.39.39 1.02 0 1.41l-4.59 4.59c-.39.39-1.02.39-1.41 0L6.7 10.7c-.39-.39-.39-1.02 0-1.41.39-.38 1.03-.39 1.42 0z"/>
</svg>;
}
}

View File

@ -0,0 +1,13 @@
import BDV2 from "../../modules/v2";
const React = BDV2.React;
export default class Edit extends React.Component {
render() {
const size = this.props.size || "24px";
return <svg className={this.props.className || ""} viewBox="0 0 24 24" fill="#FFFFFF" style={{width: size, height: size}} onClick={this.props.onClick}>
<path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z" />
<path d="M0 0h24v24H0z" fill="none" />
</svg>;
}
}

View File

@ -0,0 +1,18 @@
{/* <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white" width="18px" height="18px">
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M13 3c-4.97 0-9 4.03-9 9H1l3.89 3.89.07.14L9 12H6c0-3.87 3.13-7 7-7s7 3.13 7 7-3.13 7-7 7c-1.93 0-3.68-.79-4.94-2.06l-1.42 1.42C8.27 19.99 10.51 21 13 21c4.97 0 9-4.03 9-9s-4.03-9-9-9zm-1 5v5l4.28 2.54.72-1.21-3.5-2.08V8H12z"/>
</svg> */}
import BDV2 from "../../modules/v2";
const React = BDV2.React;
export default class History extends React.Component {
render() {
const size = this.props.size || "18px";
return <svg viewBox="0 0 24 24" fill="#FFFFFF" className={this.props.className || ""} style={{width: size, height: size}} onClick={this.props.onClick}>
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M13 3c-4.97 0-9 4.03-9 9H1l3.89 3.89.07.14L9 12H6c0-3.87 3.13-7 7-7s7 3.13 7 7-3.13 7-7 7c-1.93 0-3.68-.79-4.94-2.06l-1.42 1.42C8.27 19.99 10.51 21 13 21c4.97 0 9-4.03 9-9s-4.03-9-9-9zm-1 5v5l4.28 2.54.72-1.21-3.5-2.08V8H12z"/>
</svg>;
}
}

View File

@ -0,0 +1,13 @@
import BDV2 from "../../modules/v2";
const React = BDV2.React;
export default class Search extends React.Component {
render() {
const size = this.props.size || "16px";
return <svg className={this.props.className || ""} fill="#FFFFFF" viewBox="0 0 24 24" style={{width: size, height: size}}>
<path fill="none" d="M0 0h24v24H0V0z"/>
<path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/>
</svg>;
}
}

View File

@ -0,0 +1,92 @@
import BDV2 from "../modules/v2";
export default class LightcordLogo extends BDV2.reactComponent {
render() {
return BDV2.react.createElement(
"svg",
{height: "100%", width: this.props.size, className: "bd-logo " + this.props.className, style: {fillRule: "evenodd", clipRule: "evenodd", strokeLinecap: "round", strokeLinejoin: "round"}, viewBox: "0 0 168 190"},
BDV2.react.createElement("metadata", null),
BDV2.react.createElement("rect", {
x: "14.9",
y: "35.3",
className: "st0",
width: "139.2",
height: "97.7",
fill: "#FFFFFF"
}),
BDV2.react.createElement("defs", null, ...[
BDV2.react.createElement("linearGradient", {
id: "SVGID_1_",
gradientUnits: "userSpaceOnUse",
x1: "66.7",
y1: "112.3",
x2: "66.7",
y2: "91.2685",
gradientTransform: "matrix(1 0 0 -1 0 192)"
},
BDV2.react.createElement("stop", {
offset: "0",
style: {
stopColor: "#E20613"
}
}),
BDV2.react.createElement("stop", {
offset: "1",
style: {
stopColor: "#731A14"
}
})
),
BDV2.react.createElement("linearGradient", {
id: "SVGID_2_",
gradientUnits: "userSpaceOnUse",
x1: "101.7",
y1: "112.3",
x2: "101.7",
y2: "91.2685",
gradientTransform: "matrix(1 0 0 -1 0 192)"
},
BDV2.react.createElement("stop", {
offset: "0",
style: {
stopColor: "#E20613"
}
}),
BDV2.react.createElement("stop", {
offset: "1",
style: {
stopColor: "#731A13"
}
})
),
BDV2.react.createElement("linearGradient", {
id: "SVGID_3_",
gradientUnits: "userSpaceOnUse",
x1: "84",
y1: "192",
x2: "84",
y2: "2",
gradientTransform: "matrix(1 0 0 -1 0 192)"
},
BDV2.react.createElement("stop", {
offset: "0",
style: {
stopColor: "#E30613"
}
}),
BDV2.react.createElement("stop", {
offset: "1",
style: {
stopColor: "#731A13"
}
})
),
]),
BDV2.react.createElement("g", null,
BDV2.react.createElement("path", {className: "st1", d: "M66.7,79.7c-5.4,0-9.8,4.7-9.8,10.5s4.4,10.5,9.8,10.5s9.8-4.7,9.8-10.5C76.5,84.4,72.1,79.7,66.7,79.7z", fill: "url(#SVGID_1_)", opacity: "1"}),
BDV2.react.createElement("path", {className: "st2", d: "M101.7,79.7c-5.4,0-9.8,4.7-9.8,10.5s4.4,10.5,9.8,10.5s9.8-4.7,9.8-10.5C111.5,84.4,107.1,79.7,101.7,79.7z", fill: "url(#SVGID_2_)", opacity: "1"}),
BDV2.react.createElement("path", {className: "st3", d: "M148.4,0H19.6C8.8,0,0,8.8,0,19.6V148c0,10.8,8.8,19.6,19.6,19.6h108.9l-5.1-17.5l12.3,11.3l11.6,10.7 L168,190v-41.9v-9.5v-119C168,8.8,159.2,0,148.4,0z M111.3,124.1c0,0-3.4-4.1-6.3-7.7c12.6-3.5,17.4-11.3,17.4-11.3 c-4,2.6-7.7,4.4-11.1,5.6c-4.8,2-9.5,3.3-14,4.1c-9.2,1.7-17.6,1.3-24.9-0.1c-5.5-1-10.2-2.5-14.1-4.1c-2.2-0.8-4.6-1.9-7.1-3.3 c-0.3-0.2-0.6-0.3-0.9-0.5c-0.1-0.1-0.3-0.2-0.4-0.2c-1.7-1-2.6-1.6-2.6-1.6s4.6,7.6,16.8,11.2c-2.9,3.6-6.4,7.9-6.4,7.9 c-21.2-0.6-29.3-14.5-29.3-14.5c0-30.6,13.8-55.4,13.8-55.4c13.8-10.3,26.9-10,26.9-10l1,1.1C52.8,50.3,45,57.9,45,57.9 s2.1-1.2,5.7-2.7c10.3-4.5,18.4-5.7,21.8-6c0.5-0.1,1.1-0.2,1.6-0.2c5.9-0.7,12.5-0.9,19.4-0.2c9.1,1,18.9,3.7,28.9,9.1 c0,0-7.5-7.2-23.9-12.1l1.3-1.5c0,0,13.1-0.3,26.9,10c0,0,13.8,24.8,13.8,55.4C140.6,109.6,132.5,123.5,111.3,124.1z", fill: "url(#SVGID_3_)", opacity: "1"})
)
);
}
}

View File

@ -0,0 +1,15 @@
import BDV2 from "../modules/v2";
export default class V2C_List extends BDV2.reactComponent {
constructor(props) {
super(props);
}
render() {
return BDV2.react.createElement(
"ul",
{className: this.props.className},
this.props.children
);
}
}

View File

@ -0,0 +1,380 @@
import {settingsCookie} from "../0globals";
import BDV2 from "../modules/v2";
import Utils from "../modules/utils";
import DOM from "../modules/domtools";
import XSvg from "./xSvg";
import ReloadIcon from "./reloadIcon";
import EditIcon from "./icons/edit";
import DeleteIcon from "./icons/delete";
import Switch from "./components/switch";
import TooltipWrap from "./tooltipWrap";
import V2C_SettingsTitle from "./settingsTitle";
import V2C_SettingsGroup from "./settingsGroup";
const React = BDV2.React;
const anchorClasses = BDV2.anchorClasses;
export default class V2C_PresenceSettings extends BDV2.reactComponent {
constructor(props) {
super(props);
console.log(props)
}
updatePreview(data){
this.setState({
data
})
}
render() {
let contentModule = BDModules.get(e => e.contentColumn)[0]
return (<div className={contentModule.contentColumn+" "+contentModule.contentColumnDefault+" content-column default"}
style={{padding: "60px 40px 0px"}}>
<V2C_SettingsGroup title="RichPresence Settings" settings={this.props.settings} onChange={this.props.onChange}/>
<V2C_SettingsTitle text="RichPresence"/>
<div>
{/** options */}
{RPCProps.map(e => {
return <PresenceSettingRow setting={e} />
})}
</div>
<div>
{/** preview */}
<RpcPreview settings={this}/>
</div>
</div>)
}
}
class PresenceSettingRow extends BDV2.reactComponent {
render(){
let setting = this.props.setting
let rowModule = BDModules.get(e => e.removeKeybind)[0]
let marginModule = BDModules.get(e => e.marginBottom20)[0]
let marginModule2 = BDModules.get(e => e.defaultMarginh5)[0]
let colorModule = BDModules.get(e => e.colorStandard)[0]
let sizeModule = BDModules.get(e => e.size32)[0]
return (<div className={rowModule.row+" "+marginModule.marginBottom20}>
<div className="item-rJ_Cmt da-item flexChild-faoVW3 da-flexChild">
<h5 className={colorModule.colorStandard+" "+sizeModule.size14+" "+marginModule2.h5+" "+marginModule2.defaultMarginh5}>
{setting.title}
</h5>
<div className="inputWrapper-31_8H8 da-inputWrapper">
<input class="inputDefault-_djjkz input-cIJ7To size16-1__VVI" name="state" type="text" placeholder="" maxlength="999" value={setting.default} />
</div>
</div>
<div class="divider-3573oO da-divider dividerDefault-3rvLe- da-dividerDefault"></div>
</div>)
}
}
class RpcPreview extends BDV2.reactComponent {
constructor(props = {}){
super(props)
this.state = {
active: "profile"
}
this.tabs = []
}
changeTab(tab){
let ancientTab = this.state.active
if(ancientTab === tab.props.id)return
this.tabs.forEach(e => {
e.setActive(false)
})
this.setState({
active: tab.id
})
}
render(){
return (<div className="lc-tabWrapper">
<div className="lc-tabnav" style={{flex: "0 1 auto"}}>
<Tab onClick={this.changeTab.bind(this)} preview={this} title="Full Profile" id="profile"/>
<Tab onClick={this.changeTab.bind(this)} preview={this} title="User Popout" id="popout"/>
</div>
<Profile />
</div>)
}
isActive(tab){
return this.state.active === tab
}
/**
*
* @param {string} tmplate
* @param {any} data
*/
renderTemplate(tmplate, data){
Object.keys(data).forEach(k => {
tmplate.replace(new RegExp("{{"+k+"}}", "g"), data[k])
})
return tmplate
}
}
class Tab extends BDV2.reactComponent {
constructor(props){
super(props)
this.state = {
active: props.preview.isActive(props.id)
}
props.preview.tabs.push(this)
}
setActive(isActive){
this.setState({
active: !!isActive
})
}
render(){
let className = `lc-navItem`
if(this.state.active){
className += ` lc-navItemActive`
}else{
className += ` lc-navItemInactive`
}
return (<div className={className} onClick={()=>{
this.props.onClick(this)
this.setActive(true)
}}>
{this.props.title}
</div>)
}
}
class Status extends BDV2.reactComponent {
render(){
let status = BDModules.get(e => e.default && e.default.getPresence)[0].default.getPresence().status
if(status === "invisible")status = "offline"
let className = "pointerEvents-2zdfdO da-pointerEvents"
return <rect width="16" height="16" x="60" y="60" fill="#ffffff" mask={`url(#svg-mask-status-${status})`} className={className}></rect>
}
}
class Profile extends BDV2.ReactComponent {
render(){
let user = BDModules.get(e => e.default && e.default.getCurrentUser)[0].default.getCurrentUser()
let avatarURL = user.getAvatarURL(user.avatar.startsWith("a_") ? "gif" : "png")
let [
flexModule1,
stylingModule1,
rootModule1,
avatarModule1,
nameTagModule1
] = [
BDModules.get(e => e.flex && e._horizontal)[0],
BDModules.get(e => e.vertical && e.alignStretch)[0],
BDModules.get(e => e.topSectionStreaming)[0],
BDModules.get(e => e.pointerEvents)[0],
BDModules.get(e => e.bot)[0]
]
return [
<div className="lc-tab">
<div class={`${flexModule1.flex} ${stylingModule1.vertical} ${stylingModule1.justifyStart} ${stylingModule1.alignStretch} ${stylingModule1.noWrap} ${rootModule1.root}`} style={{flex: "1 1 auto"}}>
<div class={rootModule1.topSectionPlaying}>
<header class={rootModule1.header}>
<div class={`${rootModule1.avatar} ${avatarModule1.wrapper}`} role="img" aria-hidden="true" style={{width: "80px", height: "80px"}}>
<svg width="92" height="80" viewBox="0 0 92 80" class="mask-1l8v16 da-mask svg-2V3M55 da-svg" aria-hidden="true">
<foreignObject x="0" y="0" width="80" height="80" mask="url(#svg-mask-avatar-status-round-80)">
<img src={avatarURL} alt=" " class={avatarModule1.avatar} aria-hidden="true" />
</foreignObject>
<Status />
</svg>
</div>
<div class={`${rootModule1.headerInfo}`}>
<div class={`${rootModule1.nameTag} ${nameTagModule1.nameTag}`}>
<span class={`${rootModule1.username} ${rootModule1.username}`}>{user.username}</span>
<span class={rootModule1.discriminator}>#{user.discriminator}</span>
</div>
<div class={`${flexModule1.flex} ${flexModule1.horizontal} ${stylingModule1.justifyStart} ${stylingModule1.alignStretch} ${stylingModule1.noWrap} ${rootModule1.profileBadges}`} style={{flex: "1 1 auto"}}>
<Badges />
</div>
</div>
</header>
<div class={rootModule1.headerFill}>
<div class="activityProfile-2bJRaP da-activityProfile activity-1ythUs da-activity">
<h3 class="headerTextNormal-2mGWX3 headerText-1HLrL7 marginBottom8-AtZOdT da-headerTextNormal da-headerText da-marginBottom8 base-1x0h_U da-base size12-3cLvbJ">En train de jouer</h3>
<div class="bodyNormal-2D39hT body-ZAhrcj flex-1O1GKY alignStart-H-X2h- da-bodyNormal da-body da-flex da-alignStart">
<div class="assets-VMAukC da-assets">
<img alt="" src="https://cdn.discordapp.com/app-assets/708687209372581888/711293441526726747.png" class="assetsLargeImageProfile-3YXDex assetsLargeImage-eYwpTX noUserDrag-5Mb43F da-assetsLargeImageProfile da-assetsLargeImage da-noUserDrag assetsLargeMaskProfile-1Qkfen da-assetsLargeMaskProfile"/>
<img alt="" src="https://cdn.discordapp.com/app-assets/708687209372581888/711293441526726747.png" class="assetsSmallImageProfile-3JcsV1 assetsSmallImage-3_3Bzj noUserDrag-5Mb43F da-assetsSmallImageProfile da-assetsSmallImage da-noUserDrag"/>
</div>
<div class="contentImagesProfile-1Mz07W content-3JfFJh da-contentImagesProfile da-content" style={{flex: "1 1 auto"}}>
<h3 class="nameNormal-2lqVQK ellipsis-1XUmPN textRow-19NEd_ da-nameNormal da-ellipsis da-textRow base-1x0h_U da-base size14-e6ZScH" title="Deroku Vanity">
<span class="activityName-1IaRLn da-activityName">Deroku Vanity</span>
</h3>
<div title="Lightcord test" class="details-38sfDr ellipsis-1XUmPN textRow-19NEd_ da-details da-ellipsis da-textRow">
Lightcord test
</div>
<div class="state-Tt0LO3 ellipsis-1XUmPN textRow-19NEd_ da-state da-ellipsis da-textRow">
<span title="gay comme phorcys">gay comme phorcys</span>
</div>
<div class="timestamp-VjAZmo ellipsis-1XUmPN textRow-19NEd_ da-timestamp da-ellipsis da-textRow">
50&nbsp;min&nbsp;24&nbsp;s écoulées
</div>
</div>
</div>
</div>
</div>
</div>
<div class={rootModule1.body}>
<div class="scrollerWrap-2lJEkd da-scrollerWrap scrollerFade-1Ijw5y da-scrollerFade">
<div class="scroller-2FKFPG da-scroller">
<div class="userInfoSection-2acyCx da-userInfoSection">
<div class="userInfoSectionHeader-CBvMDh da-userInfoSectionHeader">Note</div>
<div class="note-3kmerW da-note note-QfFU8y da-note">
<textarea placeholder="Clique pour ajouter une note" maxlength="256" autocorrect="off" class="scrollbarGhostHairline-1mSOM1 scrollbar-3dvm_9 da-scrollbarGhostHairline da-scrollbar" style={{height: "40px"}} disabled></textarea>
</div>
</div>
<div class="userInfoSection-2acyCx da-userInfoSection">
<div class="connectedAccounts-repVzS da-connectedAccounts">
<div class="flex-1xMQg5 flex-1O1GKY da-flex da-flex horizontal-1ae9ci horizontal-2EEEnY flex-1O1GKY directionRow-3v3tfG justifyStart-2NDFzi alignCenter-1dQNNs noWrap-3jynv6 connectedAccount-36nQx7 da-connectedAccount" style={{flex: "0 1 auto"}}>
<img alt="Logo Twitter" class="connectedAccountIcon-3P3V6F da-connectedAccountIcon" src="/assets/4662875160dc4c56954003ebda995414.png" />
<div class="connectedAccountNameInner-1phBvE da-connectedAccountNameInner">
<div class="connectedAccountName-f8AEe2 da-connectedAccountName">jen_wina</div>
<span>
<div class="flowerStarContainer-3zDVtj da-flowerStarContainer connectedAccountVerifiedIcon-3aZz_K da-connectedAccountVerifiedIcon" style={{width: "16px", height: "16px"}}>
<svg class="flowerStar-1GeTsn da-flowerStar" aria-hidden="true" width="16" height="16" viewBox="0 0 16 15.2">
<path fill="#4f545c" fill-rule="evenodd" d="m16 7.6c0 .79-1.28 1.38-1.52 2.09s.44 2 0 2.59-1.84.35-2.46.8-.79 1.84-1.54 2.09-1.67-.8-2.47-.8-1.75 1-2.47.8-.92-1.64-1.54-2.09-2-.18-2.46-.8.23-1.84 0-2.59-1.54-1.3-1.54-2.09 1.28-1.38 1.52-2.09-.44-2 0-2.59 1.85-.35 2.48-.8.78-1.84 1.53-2.12 1.67.83 2.47.83 1.75-1 2.47-.8.91 1.64 1.53 2.09 2 .18 2.46.8-.23 1.84 0 2.59 1.54 1.3 1.54 2.09z"></path>
</svg>
<div class="childContainer-1wxZNh da-childContainer">
<svg aria-hidden="true" width="16" height="16" viewBox="0 0 16 15.2">
<path d="M7.4,11.17,4,8.62,5,7.26l2,1.53L10.64,4l1.36,1Z" fill="#ffffff"></path>
</svg>
</div>
</div>
</span>
</div>
<a class="anchor-3Z-8Bb da-anchor anchorUnderlineOnHover-2ESHQB da-anchorUnderlineOnHover" rel="noreferrer noopener" target="_blank" role="button" tabindex="0">
<svg class="connectedAccountOpenIcon-2cNbq5 da-connectedAccountOpenIcon" aria-hidden="true" width="24" height="24" viewBox="0 0 24 24">
<path fill="currentColor" d="M10 5V3H5.375C4.06519 3 3 4.06519 3 5.375V18.625C3 19.936 4.06519 21 5.375 21H18.625C19.936 21 21 19.936 21 18.625V14H19V19H5V5H10Z"></path>
<path fill="currentColor" d="M21 2.99902H14V4.99902H17.586L9.29297 13.292L10.707 14.706L19 6.41302V9.99902H21V2.99902Z"></path>
</svg>
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>,
<div class="lc-fadeOverlay"></div>
]
}
}
class Badges extends BDV2.reactComponent {
render(){
let user = BDModules.get(e => e.default && e.default.getCurrentUser)[0].default.getCurrentUser()
let rootModule = BDModules.get(e => e.topSectionStreaming)[0]
let UserFlags = BDModules.get(e => e.UserFlags)[0].UserFlags
let badges = []
let serialized = []
for(let flagName in UserFlags){
if(user.hasFlag(UserFlags[flagName]))serialized.push(flagName)
}
for(let flagName of serialized){
let searchable = `profileBadge${flagName.toLowerCase().replace(/_/g, " ").split(" ").map(e => e[0].toUpperCase()+e.slice(1)).join("")}`
searchable = searchable.replace("HypesquadOnline", "HypeSquadOnline")
console.log(searchable, rootModule[searchable])
if(!rootModule[searchable])continue
badges.push(<Badge name={searchable}/>)
}
if(user.hasPremiumSubscription){
badges.push(<Badge name="profileBadgePremium" />)
}
return badges
}
}
class Badge extends BDV2.reactComponent {
render(){
let rootModule1 = BDModules.get(e => e.topSectionStreaming)[0]
return (<div class={rootModule1.profileBadgeWrapper}>
<div>
<div class="" role="button" tabindex="0">
<div class={`profileBadge-2BqF-Z da-profileBadge ${rootModule1[this.props.name]}`}>
</div>
</div>
</div>
</div>)
}
}
const RPCProps = [
{
title: "State",
id: "state",
default: "Browsing Discord",
type: "text"
},
{
title: "Details",
id: "details",
default: "Lightcord",
type: "text"
}
]
const Constants = {
PresenceViewer: {
USER_ACTIVITY_HEADER_WATCHING: "Watching {name}",
USER_ACTIVITY_HEADER_PLAYING: "Playing a game",
USER_ACTIVITY_STATE_SIZE: "({count} of {max})",
USER_ACTIVITY_TIMESTAMP_END: "{hours, plural, =-1 {} other {{hours}:}}{minutes, plural, =-1 {} other {{minutes}:}}{seconds, plural, =-1 {} other {{seconds}}} left",
USER_ACTIVITY_TIMESTAMP_START: "{hours, plural, =-1 {} other {{hours}:}}{minutes, plural, =-1 {} other {{minutes}:}}{seconds, plural, =-1 {} other {{seconds}}} elapsed",
USER_ACTIVITY_ACTION_NOTIFY_ME: "Notify Me",
USER_ACTIVITY_ACTION_ASK_TO_JOIN: "Ask to Join",
USER_ACTIVITY_ACTION_SPECTATE: "Spectate",
SECTION_TITLE: "Rich Presence Visualizer",
SECTION_MORE_INFO: "Rich Presence lets your game surface exciting game data on your players' profiles, and lets them play together with chat invites, Ask to Join, and Spectate. See exactly how your text and art will look on a user's profile.",
PROFILE: "Full Profile",
USER_POPOUT: "User Popout",
ACTIVITY_FEED: "Games Tab",
NONE: "None",
SHOW_CODE: "Show Code",
MOBILE_ALERT: "Rich Presence Visualizer only available on desktop",
PARTY_ID_MUST_BE_UNIQUE: "Party ID can't match Join or Spectate Secrets.",
SECRETS_MUST_BE_UNIQUE: "Join and Spectate Secrets must be unique strings."
},
Tooltips: {
STATE: "[type: char*]\nThe user's current party status",
DETAILS: "[type: char*]\nWhat the player is currently doing",
START_TIMESTAMP: '[type: int64_t]\nEpoch seconds for game start - including will show time as "elapsed"',
END_TIMESTAMP: '[type: int64_t]\nEpoch seconds for game end - including will show time as "remaining"\t',
LARGE_IMAGE_KEY: "[type: char*]\nKey of the uploaded image for the large profile artwork",
LARGE_IMAGE_TEXT: "[type: char*]\nTooltip for the largeImageKey",
SMALL_IMAGE_KEY: "[type: char*]\nKey of the uploaded image for the small profile artwork",
SMALL_IMAGE_TEXT: "[type: char*]\nTooltip for the smallImageKey",
PARTY_ID: "[type: char*]\nId of the player's party, lobby, or group",
PARTY_SIZE: "[type: int]\nCurrent size of the player's party, lobby, or group",
PARTY_MAX: "[type: int]\nMaximum size of the player's party, lobby, or group\t",
SPECTATE_SECRET: "[type: char*]\nUnique hashed string for Spectate button",
JOIN_SECRET: "[type: char*]\nUnique hashed string for chat invitations and Ask to Join"
},
UserProfile: {
USER_INFO: "User Info",
MUTUAL_SERVERS: "Mutual Servers",
MUTUAL_FRIENDS: "Mutual Friends",
NOTE: "Note",
ADD_NOTE: "Click to add note"
}
}

View File

@ -0,0 +1,89 @@
import BDV2 from "../../modules/v2";
import DOM from "../../modules/domtools";
export default class V2C_Layer extends BDV2.reactComponent {
constructor(props) {
super(props);
this.keyupListener = this.keyupListener.bind(this);
}
keyupListener(e) {
if (e.which === 27) {
BDV2.reactDom.unmountComponentAtNode(this.refs.root.parentNode);
}
}
componentDidMount() {
window.addEventListener("keyup", this.keyupListener);
const thisNode = DOM.query(`#${this.props.id}`);
DOM.animate({
duration: 200,
update: function(progress) {
thisNode.style.transform = `scale(${1.1 - 0.1 * progress}) translateZ(0px)`;
thisNode.style.opacity = progress;
if (progress == 1) {
setImmediate(() => {
thisNode.style.transform = "";
thisNode.style.opacity = "";
});
}
}
});
}
componentWillUnmount() {
window.removeEventListener("keyup", this.keyupListener);
const thisNode = DOM.query(`#${this.props.id}`);
DOM.animate({
duration: 200,
update: function(progress) {
thisNode.style.transform = `scale(${1.1 - 0.1 * (1 - progress)}) translateZ(0px)`;
thisNode.style.opacity = 1 - progress;
if (progress == 1) {
setImmediate(() => {
thisNode.remove();
});
}
}
});
const layer = DOM.query(".publicServersOpen");
layer.classList.remove("publicServersOpen");
DOM.animate({
duration: 200,
update: function(progress) {
layer.style.transform = `scale(${0.07 * progress + 0.93}) translateZ(0px)`;
layer.style.opacity = progress;
if (progress == 1) {
setImmediate(() => {
layer.style.transform = "";
layer.style.opacity = "";
});
}
}
});
}
componentWillMount() {
const layer = DOM.query("[class*=\"layer-\"]");
layer.classList.add("publicServersOpen");
DOM.animate({
duration: 200,
update: function(progress) {
layer.style.transform = `scale(${0.07 * (1 - progress) + 0.93}) translateZ(0px)`;
layer.style.opacity = 1 - progress;
}
});
}
render() {
return BDV2.react.createElement(
"div",
{className: "layer bd-layer "+BDModules.get(e => e.layer && e.animating)[0].layer, id: this.props.id, ref: "root", style: {opacity: 0, transform: "scale(1.1) translateZ(0px)"}},
this.props.children
);
}
}

View File

@ -0,0 +1,420 @@
import BDV2 from "../../modules/v2";
import Tools from "../tools";
import SettingsTitle from "../settingsTitle";
import TabBarSeparator from "../tabBarSeparator";
import TabBarHeader from "../tabBarHeader";
import TabBarItem from "../tabBarItem";
import ServerCard from "./serverCard";
import SidebarView from "./sidebarView";
export default class V2C_PublicServers extends BDV2.reactComponent {
constructor(props) {
super(props);
this.setInitialState();
this.close = this.close.bind(this);
this.changeCategory = this.changeCategory.bind(this);
this.search = this.search.bind(this);
this.searchKeyDown = this.searchKeyDown.bind(this);
this.checkConnection = this.checkConnection.bind(this);
this.join = this.join.bind(this);
this.connect = this.connect.bind(this);
this.GuildStore = BDV2.WebpackModules.findByUniqueProperties(["getGuilds"]);
this.AvatarDefaults = BDV2.WebpackModules.findByUniqueProperties(["getUserAvatarURL", "DEFAULT_AVATARS"]);
this.InviteActions = BDV2.WebpackModules.findByUniqueProperties(["acceptInvite"]);
this.SortedGuildStore = BDV2.WebpackModules.findByUniqueProperties(["getSortedGuilds"]);
}
componentDidMount() {
this.checkConnection();
}
setInitialState() {
this.state = {
selectedCategory: -1,
title: "Loading...",
loading: true,
servers: [],
next: null,
connection: {
state: 0,
user: null
}
};
}
close() {
BDV2.reactDom.unmountComponentAtNode(document.getElementById(this.props.rootId));
}
search(query, clear) {
const self = this;
fetch(`${self.endPoint}${query}${query ? "&schema=new" : "?schema=new"}`, {
method: "get"
}).then(async res => {
if(res.status !== 200)throw await res.text()
let data = await res.json()
let servers = data.results.reduce((arr, server) => {
server.joined = false;
arr.push(server);
// arr.push(<ServerCard server={server} join={self.join}/>);
return arr;
}, []);
if (!clear) {
servers = self.state.servers.concat(servers);
}
else {
//servers.unshift(self.bdServer);
}
let end = data.size + data.from;
data.next = `?from=${end}`;
if (self.state.term) data.next += `&term=${self.state.term}`;
if (self.state.selectedCategory) data.next += `&category=${self.categoryButtons[self.state.selectedCategory]}`;
if (end >= data.total) {
end = data.total;
data.next = null;
}
let title = `Showing 1-${end} of ${data.total} results in ${self.categoryButtons[self.state.selectedCategory]}`;
if (self.state.term) title += ` for ${self.state.term}`;
self.setState({
loading: false,
title: title,
servers: servers,
next: data.next
});
if (clear) {
//console.log(self);
self.refs.sbv.refs.contentScroller.scrollTop = 0;
}
}).catch((err) => {
console.error(err)
return self.setState({
loading: false,
title: "Failed to load servers. Check console for details"
});
})
}
async join(serverCard) {
if (serverCard.props.pinned) return this.InviteActions.acceptInvite(serverCard.props.invite_code);
await fetch(`${this.joinEndPoint}/${serverCard.props.server.identifier}`,{
method: "GET",
credentials: "include",
mode: "cors",
headers: {
"Accept": "application/json",
"Content-Type": "application/json"
}
});
serverCard.setState({joined: true});
}
connect() {
const self = this;
const options = self.windowOptions;
options.x = Math.round(window.screenX + window.innerWidth / 2 - options.width / 2);
options.y = Math.round(window.screenY + window.innerHeight / 2 - options.height / 2);
self.joinWindow = new (window.require("electron").remote.BrowserWindow)(options);
const url = "https://auth.discordservers.com/connect?scopes=guilds.join&previousUrl=https://auth.discordservers.com/info";
self.joinWindow.webContents.on("did-navigate", (event, url) => {
if (url != "https://auth.discordservers.com/info") return;
self.joinWindow.close();
self.checkConnection();
});
self.joinWindow.loadURL(url);
}
get windowOptions() {
return {
width: 500,
height: 550,
backgroundColor: "#282b30",
show: true,
resizable: false,
maximizable: false,
minimizable: false,
alwaysOnTop: true,
frame: false,
center: false,
webPreferences: {
nodeIntegration: false
}
};
}
get bdServer() {
const server = {
name: "BetterDiscord",
online: "7500+",
members: "20000+",
categories: ["community", "programming", "support"],
description: "Official BetterDiscord server for support etc",
identifier: "86004744966914048",
iconUrl: "https://cdn.discordapp.com/icons/86004744966914048/292e7f6bfff2b71dfd13e508a859aedd.webp",
nativejoin: true,
invite_code: "0Tmfo5ZbORCRqbAd",
pinned: true
};
const guildList = this.SortedGuildStore.getFlattenedGuildIds();
const defaultList = this.AvatarDefaults.DEFAULT_AVATARS;
return BDV2.react.createElement(ServerCard, {server: server, pinned: true, join: this.join, guildList: guildList, fallback: defaultList[Math.floor(Math.random() * 5)]});
}
get endPoint() {
return "https://search.discordservers.com";
}
get joinEndPoint() {
return "https://j.discordservers.com";
}
get connectEndPoint() {
return "https://join.discordservers.com/connect";
}
async checkConnection() {
const self = this;
try {
const response = await fetch(`https://auth.discordservers.com/info`,{
method: "GET",
credentials: "include",
mode: "cors",
headers: {
"Accept": "application/json",
"Content-Type": "application/json"
}
});
const data = await response.json();
self.setState({
selectedCategory: 0,
connection: {
state: 2,
user: data
}
});
self.search("", true);
}
catch (error) {
console.error(error)
self.setState({
title: "Not connected to discordservers.com!",
loading: true,
selectedCategory: -1,
connection: {
state: 1,
user: null
}
});
}
}
render() {
return BDV2.react.createElement(SidebarView, {ref: "sbv"}, this.component);
}
get component() {
return {
sidebar: {
component: this.sidebar
},
content: {
component: this.content
},
tools: {
component: BDV2.react.createElement(Tools, {key: "pt", ref: "tools", onClick: this.close})
}
};
}
get sidebar() {
return BDV2.react.createElement(
"div",
{className: "sidebar", key: "ps"},
BDV2.react.createElement(
"div",
{className: "ui-tab-bar SIDE"},
BDV2.react.createElement(
"div",
{className: "ui-tab-bar-header", style: {fontSize: "16px"}},
"Public Servers"
),
BDV2.react.createElement(TabBarSeparator, null),
this.searchInput,
BDV2.react.createElement(TabBarSeparator, null),
BDV2.react.createElement(TabBarHeader, {text: "Categories"}),
this.categoryButtons.map((value, index) => {
return BDV2.react.createElement(TabBarItem, {id: index, onClick: this.changeCategory, key: index, text: value, selected: this.state.selectedCategory === index});
}),
BDV2.react.createElement(TabBarSeparator, null),
this.footer,
this.connection
)
);
}
get searchInput() {
return BDV2.react.createElement(
"div",
{className: "ui-form-item"},
BDV2.react.createElement(
"div",
{className: "ui-text-input flex-vertical", style: {width: "172px", marginLeft: "10px"}},
BDV2.react.createElement("input", {ref: "searchinput", onKeyDown: this.searchKeyDown, onChange: () => {}, type: "text", className: "input default", placeholder: "Search...", maxLength: "50"})
)
);
}
searchKeyDown(e) {
const self = this;
if (self.state.loading || e.which !== 13) return;
self.setState({
loading: true,
title: "Loading...",
term: e.target.value
});
let query = `?term=${e.target.value}`;
if (self.state.selectedCategory !== 0) {
query += `&category=${self.categoryButtons[self.state.selectedCategory]}`;
}
self.search(query, true);
}
get categoryButtons() {
return ["All", "FPS Games", "MMO Games", "Strategy Games", "MOBA Games", "RPG Games", "Tabletop Games", "Sandbox Games", "Simulation Games", "Music", "Community", "Language", "Programming", "Other"];
}
changeCategory(id) {
const self = this;
if (self.state.loading) return;
self.refs.searchinput.value = "";
self.setState({
loading: true,
selectedCategory: id,
title: "Loading...",
term: null
});
if (id === 0) {
self.search("", true);
return;
}
self.search(`?category=${self.categoryButtons[id]}`, true);
}
get content() {
const self = this;
const guildList = this.SortedGuildStore.getFlattenedGuildIds();
const defaultList = this.AvatarDefaults.DEFAULT_AVATARS;
if (self.state.connection.state === 1) return self.notConnected;
let columnModule = BDModules.get(e => e.contentColumnDefault)[0]
return [BDV2.react.createElement(
"div",
{ref: "content", key: "pc", className: columnModule.contentColumn+" "+columnModule.contentColumn+" content-column default"},
BDV2.react.createElement(SettingsTitle, {text: self.state.title}),
self.bdServer,
self.state.servers.map((server) => {
return BDV2.react.createElement(ServerCard, {key: server.identifier, server: server, join: self.join, guildList: guildList, fallback: defaultList[Math.floor(Math.random() * 5)]});
}),
self.state.next && BDV2.react.createElement(
"button",
{type: "button", onClick: () => {
if (self.state.loading) return;self.setState({loading: true}); self.search(self.state.next, false);
}, className: "ui-button filled brand small grow", style: {width: "100%", marginTop: "10px", marginBottom: "10px"}},
BDV2.react.createElement(
"div",
{className: "ui-button-contents"},
self.state.loading ? "Loading" : "Load More"
)
),
self.state.servers.length > 0 && BDV2.react.createElement(SettingsTitle, {text: self.state.title})
)];
}
get notConnected() {
const self = this;
//return BDV2.react.createElement(SettingsTitle, { text: self.state.title });
let columnModule = BDModules.get(e => e.contentColumnDefault)[0]
return [BDV2.react.createElement(
"div",
{key: "ncc", ref: "content", className: columnModule.contentColumn+" "+columnModule.contentColumn+" content-column default"},
BDV2.react.createElement(
"h2",
{className: "ui-form-title h2 margin-reset margin-bottom-20"},
"Not connected to discordservers.com!",
BDV2.react.createElement(
"button",
{
onClick: self.connect,
type: "button",
className: "ui-button filled brand small grow",
style: {
display: "inline-block",
minHeight: "18px",
marginLeft: "10px",
lineHeight: "14px"
}
},
BDV2.react.createElement(
"div",
{className: "ui-button-contents"},
"Connect"
)
)
), self.bdServer
)];
}
get footer() {
return BDV2.react.createElement(
"div",
{className: "ui-tab-bar-header"},
BDV2.react.createElement(
"a",
{href: "https://discordservers.com", target: "_blank"},
"Discordservers.com"
)
);
}
get connection() {
const self = this;
const {connection} = self.state;
if (connection.state !== 2) return BDV2.react.createElement("span", null);
return BDV2.react.createElement(
"span",
null,
BDV2.react.createElement(TabBarSeparator, null),
BDV2.react.createElement(
"span",
{style: {color: "#b9bbbe", fontSize: "10px", marginLeft: "10px"}},
"Connected as: ",
`${connection.user.username}#${connection.user.discriminator}`
),
BDV2.react.createElement(
"div",
{style: {padding: "5px 10px 0 10px"}},
BDV2.react.createElement(
"button",
{style: {width: "100%", minHeight: "20px"}, type: "button", className: "ui-button filled brand small grow"},
BDV2.react.createElement(
"div",
{className: "ui-button-contents", onClick: self.connect},
"Reconnect"
)
)
)
);
}
}

View File

@ -0,0 +1,106 @@
import BDV2 from "../../modules/v2";
export default class V2C_ServerCard extends BDV2.reactComponent {
constructor(props) {
super(props);
if (!this.props.server.iconUrl) this.props.server.iconUrl = this.props.fallback;
this.state = {
imageError: false,
joined: this.props.guildList.includes(this.props.server.identifier)
};
}
render() {
const {server} = this.props;
let cardModule = BDModules.get(e => e.card && e.cardPrimary)[0]
let flexModule = BDModules.get(e => e.flexChild && e._horizontalReverse)[0]
let wrapModule = BDModules.get(e => e.noWrap && !e.streamerModeEnabled)[0]
return BDV2.react.createElement(
"div", // cardPrimary-1Hv-to
{className: `${cardModule.card} ${cardModule.cardPrimary} ${BDModules.get(e => e.marginBottom8)[0].marginBottom8} bd-server-card${server.pinned ? " bd-server-card-pinned" : ""}`},
// BDV2.react.createElement(
// "div",
// { className: "flex-1xMQg5 flex-1O1GKY horizontal-1ae9ci horizontal-2EEEnY flex-1O1GKY directionRow-3v3tfG justifyStart-2yIZo0 alignStretch-1hwxMa noWrap-3jynv6" },
BDV2.react.createElement("img", {ref: "img", className: "bd-server-image", src: server.iconUrl, onError: this.handleError.bind(this)}),
BDV2.react.createElement(
"div",
{className: flexModule.flexChild+" bd-server-content"},
BDV2.react.createElement(
"div",
{className: flexModule.horizontal+" "+wrapModule.noWrap+" bd-server-header"},
BDV2.react.createElement(
"h5",
{className: "h5-18_1nd defaultColor-1_ajX0 margin-reset bd-server-name"},
server.name
),
BDV2.react.createElement(
"h5",
{className: "h5-18_1nd defaultColor-1_ajX0 margin-reset bd-server-member-count"},
server.members,
" Members"
)
),
BDV2.react.createElement(
"div",
{className: flexModule.horizontal+" "+wrapModule.noWrap},
BDV2.react.createElement(
"div",
{className: "scrollerWrap-2lJEkd scrollerThemed-2oenus themeGhostHairline-DBD-2d scrollerFade-1Ijw5y bd-server-description-container"},
BDV2.react.createElement(
"div",
{className: "scroller-2FKFPG scroller bd-server-description"},
server.description
)
)
),
BDV2.react.createElement(
"div",
{className: "flex-1xMQg5 flex-1O1GKY horizontal-1ae9ci horizontal-2EEEnY directionRow-3v3tfG noWrap-3jynv6 bd-server-footer"},
BDV2.react.createElement(
"div",
{className: "flexChild-faoVW3 bd-server-tags", style: {flex: "1 1 auto"}},
server.categories.join(", ")
),
this.state.joined && BDV2.react.createElement(
"button",
{type: "button", className: "button-38aScr lookFilled-1Gx00P colorBrand-3pXr91 sizeMin-1mJd1x grow-q77ONN colorGreen-29iAKY", style: {minHeight: "12px", marginTop: "4px", backgroundColor: "#3ac15c"}},
BDV2.react.createElement(
"div",
{className: "ui-button-contents"},
"Joined"
)
),
server.error && BDV2.react.createElement(
"button",
{type: "button", className: "button-38aScr lookFilled-1Gx00P colorBrand-3pXr91 sizeMin-1mJd1x grow-q77ONN disabled-9aF2ug", style: {minHeight: "12px", marginTop: "4px", backgroundColor: "#c13a3a"}},
BDV2.react.createElement(
"div",
{className: "ui-button-contents"},
"Error"
)
),
!server.error && !this.state.joined && BDV2.react.createElement(
"button",
{type: "button", className: "button-38aScr lookFilled-1Gx00P colorBrand-3pXr91 sizeMin-1mJd1x grow-q77ONN", style: {minHeight: "12px", marginTop: "4px"}, onClick: () => {this.join();}},
BDV2.react.createElement(
"div",
{className: "ui-button-contents"},
"Join"
)
)
)
)
// )
);
}
handleError() {
this.props.server.iconUrl = this.props.fallback;
this.setState({imageError: true});
}
join() {
this.props.join(this);
//this.setState({joined: true});
}
}

View File

@ -0,0 +1,33 @@
import BDV2 from "../../modules/v2";
import Scroller from "../scroller";
export default class V2C_SidebarView extends BDV2.reactComponent {
constructor(props) {
super(props);
}
render() {
const {sidebar, content, tools} = this.props.children;
return BDV2.react.createElement(
"div",
{className: "standardSidebarView-3F1I7i ui-standard-sidebar-view"},
BDV2.react.createElement(
"div",
{className: "sidebarRegion-VFTUkN sidebar-region"},
BDV2.react.createElement(Scroller, {key: "sidebarScroller", ref: "sidebarScroller", sidebar: true, fade: sidebar.fade || true, dark: sidebar.dark || true}, sidebar.component)
),
BDV2.react.createElement("div", {className: "contentRegion-3nDuYy content-region"},
BDV2.react.createElement("div", {className: "contentTransitionWrap-3hqOEW content-transition-wrap"},
BDV2.react.createElement("div", {className: "scrollerWrap-2lJEkd firefoxFixScrollFlex-cnI2ix contentRegionScrollerWrap-3YZXdm content-region-scroller-wrap scrollerThemed-2oenus themeGhost-28MSn0 scrollerTrack-1ZIpsv"},
BDV2.react.createElement("div", {className: "scroller-2FKFPG firefoxFixScrollFlex-cnI2ix contentRegionScroller-26nc1e content-region-scroller scroller", ref: "contentScroller"},
BDV2.react.createElement("div", {className: "contentColumn-2hrIYH contentColumnDefault-1VQkGM content-column default"}, content.component),
tools.component
)
)
)
)
);
}
}

View File

@ -0,0 +1,21 @@
import BDV2 from "../modules/v2";
export default class V2C_ReloadIcon extends BDV2.reactComponent {
constructor(props) {
super(props);
}
render() {
return BDV2.react.createElement("svg", {
xmlns: "http://www.w3.org/2000/svg",
viewBox: "0 0 24 24",
fill: "#dcddde",
className: "bd-reload " + this.props.className,
onClick: this.props.onClick,
style: {width: this.props.size || "24px", height: this.props.size || "24px"}
},
BDV2.react.createElement("path", {d: "M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"}),
BDV2.react.createElement("path", {fill: "none", d: "M0 0h24v24H0z"})
);
}
}

View File

@ -0,0 +1,31 @@
import BDV2 from "../modules/v2";
export default class V2C_Scroller extends BDV2.reactComponent {
constructor(props) {
super(props);
}
render() {
//scrollerWrap-2lJEkd scrollerThemed-2oenus themeGhostHairline-DBD-2d scrollerFade-1Ijw5y
let scrollerModule1 = BDModules.get(e => e.scrollerHorizontal)[0]
let scrollerModule2 = BDModules.get(e => e.sidebarRegionScroller)[0]
let wrapperClass = `${scrollerModule1.scrollerWrap} ${scrollerModule1.scrollerThemed} ${scrollerModule1.themeGhostHairline}${this.props.fade ? " "+scrollerModule1.scrollerFade : ""}`;
let scrollerClass = scrollerModule1.scroller+" scroller"; /* fuck */
if (this.props.sidebar) scrollerClass += ` ${scrollerModule2.sidebarRegionScroller} sidebar-region-scroller`
if (this.props.contentColumn) {
scrollerClass += " "+scrollerModule2.contentRegionScroller+" content-region-scroller"; /* fuck */
wrapperClass = `${scrollerModule1.scrollerWrap} ${scrollerModule2.contentRegionScrollerWrap} content-region-scroller-wrap ${scrollerModule1.scrollerThemed} ${scrollerModule1.themeGhost} ${scrollerModule1.scrollerTrack}`;
}
const {children} = this.props;
return BDV2.react.createElement(
"div",
{key: "scrollerwrap", className: wrapperClass},
BDV2.react.createElement(
"div",
{key: "scroller", ref: "scroller", className: scrollerClass},
children
)
);
}
}

View File

@ -0,0 +1,20 @@
import BDV2 from "../modules/v2";
import SettingsGroup from "./settingsGroup";
export default class V2C_SectionedSettingsPanel extends BDV2.reactComponent {
constructor(props) {
super(props);
}
render() {
let columnModule = BDModules.get(e => e.contentColumnDefault)[0]
return BDV2.react.createElement(
"div", {className: columnModule.contentColumn+" "+columnModule.contentColumnDefault+" content-column default"},
this.props.sections.map(section => {
return BDV2.react.createElement(SettingsGroup, Object.assign({}, section, {onChange: this.props.onChange}));
})
);
}
}

View File

@ -0,0 +1,24 @@
import {settingsCookie} from "../0globals";
import BDV2 from "../modules/v2";
import SettingsTitle from "./settingsTitle";
import Switch from "./switch";
export default class V2C_SettingsGroup extends BDV2.reactComponent {
constructor(props) {
super(props);
}
render() {
const {title, settings, button} = this.props;
const buttonComponent = button ? BDV2.react.createElement("button", {key: "title-button", className: "bd-pfbtn", onClick: button.onClick}, button.title) : null;
return [BDV2.react.createElement(SettingsTitle, {text: title}),
buttonComponent,
settings.map(setting => {
return BDV2.react.createElement(Switch, {id: setting.id, key: setting.id, data: setting, checked: settingsCookie[setting.id], onChange: (id, checked) => {
this.props.onChange(id, checked);
}});
})];
}
}

View File

@ -0,0 +1,28 @@
import {settingsCookie} from "../0globals";
import BDV2 from "../modules/v2";
import SettingsTitle from "./settingsTitle";
import Switch from "./switch";
export default class V2C_SettingsPanel extends BDV2.reactComponent {
constructor(props) {
super(props);
}
render() {
const {settings} = this.props;
let columnModule = BDModules.get(e => e.contentColumnDefault)[0]
return BDV2.react.createElement(
"div",
{className: columnModule.contentColumn+" "+columnModule.contentColumnDefault+" content-column default"},
BDV2.react.createElement(SettingsTitle, {text: this.props.title}),
this.props.button && BDV2.react.createElement("button", {key: "title-button", className: "bd-pfbtn", onClick: this.props.button.onClick}, this.props.button.title),
settings.map(setting => {
return BDV2.react.createElement(Switch, {id: setting.id, key: setting.id, data: setting, checked: settingsCookie[setting.id], onChange: (id, checked) => {
this.props.onChange(id, checked);
}});
})
);
}
}

View File

@ -0,0 +1,15 @@
import BDV2 from "../modules/v2";
export default class V2C_SettingsTitle extends BDV2.reactComponent {
constructor(props) {
super(props);
}
//h2-2gWE-o title-3sZWYQ size16-14cGz5 height20-mO2eIN weightSemiBold-NJexzi da-h2 da-title da-size16 da-height20 da-weightSemiBold defaultColor-1_ajX0 da-defaultColor marginTop60-3PGbtK da-marginTop60 marginBottom20-32qID7 da-marginBottom20
render() {
return BDV2.react.createElement(
"h2",
{className: "ui-form-title h2 margin-reset margin-bottom-20 marginTop60-3PGbtK da-marginTop6"},
this.props.text
);
}
}

View File

@ -0,0 +1,77 @@
import BDV2 from "../modules/v2";
import TabBarSeparator from "./tabBarSeparator";
import TabBarHeader from "./tabBarHeader";
import TabBarItem from "./tabBarItem";
export default class V2C_SideBar extends BDV2.reactComponent {
constructor(props) {
super(props);
const si = document.querySelector("[class*=side-] > [class*=selected]");
if (si) this.scn = si.className;
const ns = document.querySelector("[class*=side-] > [class*='item-']:not([class*=selected])");
if (ns) this.nscn = ns.className;
const tabs = document.querySelectorAll("[class*='side-'] > [class*='item-']");
for (const element of tabs) {
element.addEventListener("click", () => {
this.setState({
selected: null
});
});
}
this.setInitialState();
this.onClick = this.onClick.bind(this);
this.setSelected = this.setSelected.bind(this);
}
setInitialState() {
const self = this;
self.state = {
selected: null,
items: self.props.items
};
const initialSelection = self.props.items.find(item => {
return item.selected;
});
if (initialSelection) {
self.state.selected = initialSelection.id;
}
}
render() {
const self = this;
const {headerText} = self.props;
const {items, selected} = self.state;
return BDV2.react.createElement(
"div",
null,
BDV2.react.createElement(TabBarSeparator, null),
BDV2.react.createElement(TabBarHeader, {text: headerText, button: this.props.headerButton}),
items.map(item => {
const {id, text} = item;
return BDV2.react.createElement(TabBarItem, {key: id, selected: selected === id, text: text, id: id, onClick: self.onClick});
})
);
}
setSelected(e) {
e.target.className = this.scn;
}
onClick(id) {
const si = document.querySelector("[class*=side] > [class*=selected]");
if (si) {
si.removeEventListener("click", this.setSelected);
si.addEventListener("click", this.setSelected);
si.className = this.nscn;
}
this.setState({selected: null});
this.setState({selected: id});
if (this.props.onClick) this.props.onClick(id);
}
}

View File

@ -0,0 +1,26 @@
import BDV2 from "../modules/v2";
import Switch from "./components/switch";
export default class SwitchItem extends BDV2.reactComponent {
constructor(props) {
super(props);
this.onChange = this.onChange.bind(this);
}
onChange() {
this.props.checked = !this.props.checked;
this.props.onChange(this.props.id, this.props.checked);
}
render() {
const {text, info} = this.props.data;
return BDV2.react.createElement("div", {className: "ui-flex flex-vertical flex-justify-start flex-align-stretch flex-nowrap ui-switch-item"},
BDV2.react.createElement("div", {className: "ui-flex flex-horizontal flex-justify-start flex-align-stretch flex-nowrap"},
BDV2.react.createElement("h3", {className: "ui-form-title h3 margin-reset margin-reset ui-flex-child"}, text),
BDV2.react.createElement(Switch, {onChange: this.onChange, checked: this.props.checked})
),
BDV2.react.createElement("div", {className: "ui-form-text style-description margin-top-4", style: {flex: "1 1 auto"}}, info)
);
}
}

View File

@ -0,0 +1,11 @@
import BDV2 from "../modules/v2";
export default class V2C_TabBarHeader extends BDV2.reactComponent {
constructor(props) {
super(props);
}
render() {
return BDV2.react.createElement("div",{className: "ui-tab-bar-header"}, this.props.text, this.props.button);
}
}

View File

@ -0,0 +1,30 @@
import BDV2 from "../modules/v2";
export default class V2C_TabBarItem extends BDV2.reactComponent {
constructor(props) {
super(props);
this.setInitialState();
this.onClick = this.onClick.bind(this);
}
setInitialState() {
this.state = {
selected: this.props.selected || false
};
}
render() {
return BDV2.react.createElement(
"div",
{className: `ui-tab-bar-item${this.props.selected ? " selected" : ""}`, onClick: this.onClick},
this.props.text
);
}
onClick() {
if (this.props.onClick) {
this.props.onClick(this.props.id);
}
}
}

View File

@ -0,0 +1,11 @@
import BDV2 from "../modules/v2";
export default class V2C_TabBarSeparator extends BDV2.reactComponent {
constructor(props) {
super(props);
}
render() {
return BDV2.react.createElement("div", {className: "ui-tab-bar-separator margin-top-8 margin-bottom-8"});
}
}

View File

@ -0,0 +1,39 @@
import BDV2 from "../modules/v2";
import XSvg from "./xSvg";
export default class V2C_Tools extends BDV2.reactComponent {
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
}
render() {
let toolsModule = BDModules.get(e => e.toolsContainer)[0]
let containerModule = BDModules.get(e => e.container && e.closeButton && e.closeButtonBold)[0]
return BDV2.react.createElement("div", {className: "tools-container "+toolsModule.toolsContainer},
BDV2.react.createElement("div", {className: "tools "+toolsModule.tools},
BDV2.react.createElement("div", {className: containerModule.container},
BDV2.react.createElement("div",
{className: "btn-close "+containerModule.closeButton, onClick: this.onClick},
BDV2.react.createElement(XSvg, null)
),
BDV2.react.createElement(
"div",
{className: "esc-text "+containerModule.keybind},
"ESC"
)
)
)
);
}
onClick() {
if (this.props.onClick) {
this.props.onClick();
}
const closeButton = document.querySelector("."+BDModules.get(e => e.closeButton && e.keybindBold)[0].closeButton.split(" ")[0]);
if (closeButton) closeButton.click();
}
}

View File

@ -0,0 +1,186 @@
/**
* Tooltip that automatically show and hide themselves on mouseenter and mouseleave events.
* Will also remove themselves if the node to watch is removed from DOM through
* a MutationObserver.
*
* Note this is not using Discord's internals but normal DOM manipulation and emulates
* Discord's own tooltips as closely as possible.
*
* @module EmulatedTooltip
* @version 0.0.1
*/
import Utils from "../modules/utils";
import WebpackModules from "../modules/webpackModules";
const TooltipClasses = WebpackModules.findByProps("tooltip", "tooltipBlack");
const TooltipLayers = WebpackModules.findByProps("layer", "layerContainer");
const getClass = function(sideOrColor) {
const upperCase = sideOrColor[0].toUpperCase() + sideOrColor.slice(1);
const tooltipClass = TooltipClasses[`tooltip${upperCase}`];
if (tooltipClass) return tooltipClass;
return null;
};
const classExists = function(sideOrColor) {
return getClass(sideOrColor) ? true : false;
};
const toPx = function(value) {
return `${value}px`;
};
/* <div class="layer-v9HyYc da-layer" style="left: 234.5px; bottom: 51px;">
<div class="tooltip-2QfLtc da-tooltip tooltipTop-XDDSxx tooltipBlack-PPG47z">
<div class="tooltipPointer-3ZfirK da-tooltipPointer"></div>
User Settings
</div>
</div> */
export default class EmulatedTooltip {
/**
*
* @constructor
* @param {(HTMLElement|jQuery)} node - DOM node to monitor and show the tooltip on
* @param {string} tip - string to show in the tooltip
* @param {object} options - additional options for the tooltip
* @param {string} [options.style=black] - correlates to the discord styling/colors (black, brand, green, grey, red, yellow)
* @param {string} [options.side=top] - can be any of top, right, bottom, left
* @param {boolean} [options.preventFlip=false] - prevents moving the tooltip to the opposite side if it is too big or goes offscreen
* @param {boolean} [options.disabled=false] - whether the tooltip should be disabled from showing on hover
*/
constructor(node, text, options = {}) {
const {style = "black", side = "top", preventFlip = false, disabled = false} = options;
this.node = node instanceof jQuery ? node[0] : node;
this.label = text;
this.style = style.toLowerCase();
this.side = side.toLowerCase();
this.preventFlip = preventFlip;
this.disabled = disabled;
if (!classExists(this.side)) return Utils.err("EmulatedTooltip", `Side ${this.side} does not exist.`);
if (!classExists(this.style)) return Utils.err("EmulatedTooltip", `Style ${this.style} does not exist.`);
this.element = document.createElement("div");
this.element.className = TooltipLayers.layer + " " + TooltipLayers.disabledPointerEvents;
this.tooltipElement = document.createElement("div");
this.tooltipElement.className = `${TooltipClasses.tooltip} ${getClass(this.style)}`;
this.labelElement = document.createElement("div");
this.labelElement.className = TooltipClasses.tooltipContent
const pointerElement = document.createElement("div");
pointerElement.className = TooltipClasses.tooltipPointer;
this.tooltipElement.append(pointerElement);
this.tooltipElement.append(this.labelElement);
this.element.append(this.tooltipElement);
this.node.addEventListener("mouseenter", () => {
if (this.disabled) return;
this.show();
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
const nodes = Array.from(mutation.removedNodes);
const directMatch = nodes.indexOf(this.node) > -1;
const parentMatch = nodes.some(parent => parent.contains(this.node));
if (directMatch || parentMatch) {
this.hide();
observer.disconnect();
}
});
});
observer.observe(document.body, {subtree: true, childList: true});
});
this.node.addEventListener("mouseleave", () => {
this.hide();
});
}
/** Container where the tooltip will be appended. */
get container() { return document.querySelector("."+BDModules.get(e => e.popouts)[0].popouts.split(" ")[0]+" ~ ."+BDModules.get(e => e.layerContainer)[0].layerContainer.split(" ")[0]); }
/** Boolean representing if the tooltip will fit on screen above the element */
get canShowAbove() { return this.node.getBoundingClientRect().top - this.element.offsetHeight >= 0; }
/** Boolean representing if the tooltip will fit on screen below the element */
get canShowBelow() { return this.node.getBoundingClientRect().top + this.node.offsetHeight + this.element.offsetHeight <= Utils.screenHeight; }
/** Boolean representing if the tooltip will fit on screen to the left of the element */
get canShowLeft() { return this.node.getBoundingClientRect().left - this.element.offsetWidth >= 0; }
/** Boolean representing if the tooltip will fit on screen to the right of the element */
get canShowRight() { return this.node.getBoundingClientRect().left + this.node.offsetWidth + this.element.offsetWidth <= Utils.screenWidth; }
/** Hides the tooltip. Automatically called on mouseleave. */
hide() {
this.element.remove();
this.tooltipElement.className = this._className;
}
/** Shows the tooltip. Automatically called on mouseenter. Will attempt to flip if position was wrong. */
show() {
this.tooltipElement.className = `${TooltipClasses.tooltip} ${getClass(this.style)}`;
this.labelElement.textContent = this.label;
this.container.append(this.element);
if (this.side == "top") {
if (this.canShowAbove || (!this.canShowAbove && this.preventFlip)) this.showAbove();
else this.showBelow();
}
if (this.side == "bottom") {
if (this.canShowBelow || (!this.canShowBelow && this.preventFlip)) this.showBelow();
else this.showAbove();
}
if (this.side == "left") {
if (this.canShowLeft || (!this.canShowLeft && this.preventFlip)) this.showLeft();
else this.showRight();
}
if (this.side == "right") {
if (this.canShowRight || (!this.canShowRight && this.preventFlip)) this.showRight();
else this.showLeft();
}
}
/** Force showing the tooltip above the node. */
showAbove() {
this.tooltipElement.classList.add(getClass("top"));
this.element.style.setProperty("top", toPx(this.node.getBoundingClientRect().top - this.element.offsetHeight - 10));
this.centerHorizontally();
}
/** Force showing the tooltip below the node. */
showBelow() {
this.tooltipElement.classList.add(getClass("bottom"));
this.element.style.setProperty("top", toPx(this.node.getBoundingClientRect().top + this.node.offsetHeight + 10));
this.centerHorizontally();
}
/** Force showing the tooltip to the left of the node. */
showLeft() {
this.tooltipElement.classList.add(getClass("left"));
this.element.style.setProperty("left", toPx(this.node.getBoundingClientRect().left - this.element.offsetWidth - 10));
this.centerVertically();
}
/** Force showing the tooltip to the right of the node. */
showRight() {
this.tooltipElement.classList.add(getClass("right"));
this.element.style.setProperty("left", toPx(this.node.getBoundingClientRect().left + this.node.offsetWidth + 10));
this.centerVertically();
}
centerHorizontally() {
const nodecenter = this.node.getBoundingClientRect().left + (this.node.offsetWidth / 2);
this.element.style.setProperty("left", toPx(nodecenter - (this.element.offsetWidth / 2)));
}
centerVertically() {
const nodecenter = this.node.getBoundingClientRect().top + (this.node.offsetHeight / 2);
this.element.style.setProperty("top", toPx(nodecenter - (this.element.offsetHeight / 2)));
}
}

View File

@ -0,0 +1,23 @@
import BDV2 from "../modules/v2";
import Tooltip from "./tooltip";
export default class extends BDV2.reactComponent {
constructor(props) {
super(props);
}
async componentDidMount() {
const {style = "black", side = "top", text = ""} = this.props;
this.node = BDV2.reactDom.findDOMNode(this);
this.tooltip = new Tooltip(this.node, text, {style, side});
}
componentWillUnmount() {
this.tooltip.hide();
delete this.tooltip;
}
render() {
return this.props.children;
}
}

View File

@ -0,0 +1,20 @@
import BDV2 from "../modules/v2";
export default class V2C_XSvg extends BDV2.reactComponent {
constructor(props) {
super(props);
}
render() {
return BDV2.react.createElement(
"svg",
{xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 12 12", style: {width: "18px", height: "18px"}},
BDV2.react.createElement(
"g",
{className: "background", fill: "none", fillRule: "evenodd"},
BDV2.react.createElement("path", {d: "M0 0h12v12H0"}),
BDV2.react.createElement("path", {className: "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"})
)
);
}
}

View File

@ -0,0 +1,70 @@
const path = require("path");
const CircularDependencyPlugin = require("circular-dependency-plugin");
const TerserPlugin = require("terser-webpack-plugin");
module.exports = {
mode: "development",
target: "node",
devtool: "eval-cheap-source-map",
entry: "./src/index.js",
output: {
filename: "main.js",
path: path.resolve(__dirname, "js"),
library: "BetterDiscord",
libraryTarget: "commonjs2"
},
externals: {
electron: `electron`,
fs: `fs`,
path: `path`,
events: `events`,
rimraf: `rimraf`,
yauzl: `yauzl`,
mkdirp: `mkdirp`,
request: `request`
},
resolve: {
extensions: [".js", ".jsx"],
modules: [
path.resolve("src", "builtins"),
path.resolve("src", "modules")
]
},
module: {
rules: [
{
test: /.jsx?$/,
loader: "babel-loader",
exclude: /node_modules/,
query: {
presets: [["@babel/env", {
targets: {
node: "12.8.1",
chrome: "78"
}
}], "@babel/react"]
}
}
]
},
plugins: [
new CircularDependencyPlugin({
// exclude detection of files based on a RegExp
exclude: /a\.js|node_modules/,
// add errors to webpack instead of warnings
// failOnError: true,
// set the current working directory for displaying module paths
cwd: process.cwd(),
})
],
optimization: {
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {drop_debugger:false}
}
})
]
}
};

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 JeanOUINA
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
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.

35
README.md Normal file
View File

@ -0,0 +1,35 @@
# Lightcord
A simple - customizable - Discord Client
## What's this ?
Lightcord is a simple and customizable client for Discord.
It includes [BandagedBD](https://github.com/rauenzi/BetterDiscordApp), [Glasscord](https://github.com/AryToNeX/Glasscord) and a discord.js like api.
## Informations
Lightcord doesn't *patch* Discord with it's content. If it would, Discord will update itself and break the patch. That's why Lightcord is a standalone Discord client. Just grab the latest release version you need, and launch it !
## Installation
You can install it from the release tab.
If you want to `git clone` it and launch it, follow these instructions:
```sh
git clone https://github.com/JeanOUINA/Lightcord
cd Lightcord
npm i
npm run test
```
Discord will launch next. You can see you have done it right by looking at the icon.
![icon](https://i.imgur.com/rHnsPNO.png)
## BetterDiscord
BetterDiscord (BandagedBD) is already installed (modified version).
You can go into your settings manage plugins.
Because it's more a pain than something good, global emotes are not supported on Lightcord. They have been removed.
Only the freeze at the launch because of the downloading is pretty annoying. So I removed them.
## Plugins & Themes
Plugins and themes are not in the standard BetterDiscord folder. They have been moved because betterdiscord supports only stable, ptb and canary release. Using the same directory could cause problems with settings.
They are located in `%AppData%/LightCord_BD`. This is the main folder for BetterDiscord.

BIN
app.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

123
compile.js Normal file
View File

@ -0,0 +1,123 @@
const child_process = require("child_process")
const path = require("path")
const bytenode = require("bytenode")
const terser = require("terser")
const util = require("util")
var rimraf = require("rimraf");
let electron
try{
electron = require("electron")
}catch(e){
electron = null
}
let fs = electron ? require("original-fs") : require("fs")
console.log = (...args) => {
process.stdout.write(Buffer.from(util.formatWithOptions({colors: true}, ...args)+"\n", "binary").toString("utf8"))
}
console.info = (...args) => {
console.log(`\x1b[34m[INFO]\x1b[0m`, ...args)
}
async function main(){
if(electron)await electron.app.whenReady()
console.log(__dirname, process.cwd())
console.info("Reseting existent directory...")
child_process.execSync("node remove.js") // why can't electron remove directory ? it just doesn't work and I am required to use electron for the .jsc compilation.
await fs.promises.mkdir(__dirname+"/distApp/dist", {"recursive": true})
console.info("Executing command `tsc`")
console.log(child_process.execSync("tsc", {encoding: "binary"}))
let startDir = path.join(__dirname, "./dist")
let newDir = path.join(__dirname, "./distApp/dist")
console.info("No error detected. Copying files from "+startDir+".")
await fs.promises.mkdir(startDir, {recursive: true})
async function processNextDir(folder, folders, predicate, compile){
for(let file of fs.readdirSync(folder, {withFileTypes: true})){
if(file.isFile()){
let filepath = path.join(folder, file.name)
if(predicate(filepath)){
await compile(filepath, path.join(filepath.replace(folders.startDir, folders.newDir)), "..")
}else{
await fs.promises.copyFile(filepath, filepath.replace(folders.startDir, folders.newDir))
}
}else if(file.isDirectory()){
await fs.promises.mkdir(path.join(folder, file.name).replace(folders.startDir, folders.newDir), {recursive: true})
await processNextDir(path.join(folder, file.name), ...Array.from(arguments).slice(1))
}
}
}
await processNextDir(startDir, {
startDir,
newDir
}, ((filepath) => filepath.endsWith(".js") && !filepath.endsWith("launcher.js")), (filepath, newpath) => {
console.info(`Compiling ${filepath} to ${newpath}c`)
bytenode.compileFile(filepath, newpath+"c")
}).then(() => {
console.info(`Copied files and minified them from ${startDir}.`)
}).catch(console.error)
await processNextDir(path.join(__dirname, "modules"), {
startDir: path.join(__dirname, "modules"),
newDir: path.join(__dirname, "distApp", "modules")
}, (filepath) => {
if(filepath.includes("node_modules"))return false
if(filepath.endsWith(".node"))return false
if(filepath.endsWith(".json"))return false
if(filepath.endsWith(".js")){
if(filepath.endsWith("mainScreenPreload.js"))return false
for(let file of [
"discord_cloudsync\\index.js",
"discord_desktop_core\\index.js",
"discord_dispatch\\index.js",
"discord_erlpack\\index.js",
"discord_game_utils\\index.js",
"discord_krisp\\index.js",
"discord_media\\index.js",
"discord_modules\\index.js",
"discord_overlay2\\index.js",
"discord_rpc\\index.js",
"discord_spellcheck\\index.js",
"discord_utils\\index.js",
"discord_voice\\index.js",
"discord_desktop_core\\core\\app\\index.js"
]){
if(filepath.endsWith(file))return false
}
return true
}
return false
}, (filepath, newpath) => {
console.info(`Compiling ${filepath} to ${newpath}c`)
bytenode.compileFile(filepath, newpath+"c")
}).then(() => {
console.info(`Copied files and minified them from ${path.join(__dirname, "modules")}.`)
})
await processNextDir(startDir, {
startDir,
newDir
}, ((filepath) => false), ()=>{}).then(() => {
console.info(`Copied files and minified them from ${startDir}.`)
}).catch(console.error)
let packageJSON = require("./package.json")
packageJSON.scripts.build = packageJSON.scripts.build.replace("./distApp", ".")
fs.writeFileSync(path.join(__dirname, "distApp", "package.json"), JSON.stringify(packageJSON), "utf8")
console.info(`Installing ${Object.keys(packageJSON.dependencies).length + Object.keys(packageJSON.devDependencies).length} packages...`)
console.log(child_process.execSync("npm i", {
encoding: "binary",
cwd: path.join(__dirname, "distApp")
}))
}
main()
.then(() => {
if(electron){
electron.app.exit()
}
})

BIN
discord.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

View File

@ -0,0 +1,28 @@
const EventEmitter = require('events');
const {CloudSync: CloudSyncNative} = require('./discord_cloudsync.node');
function makeCallback(resolve, reject) {
return (err, result) => {
if (err != null && err !== '') {
reject(new Error(JSON.parse(err)));
} else {
resolve(result != null && result !== '' ? JSON.parse(result) : null);
}
};
}
class CloudSync extends EventEmitter {
constructor() {
super();
this._cloudSync = new CloudSyncNative(state => this.emit('state', JSON.parse(state)));
}
sync(id, config) {
return new Promise((resolve, reject) =>
this._cloudSync.command(JSON.stringify({type: 'SYNC', id, config}), makeCallback(resolve, reject))
);
}
}
module.exports = CloudSync;

View File

@ -0,0 +1,7 @@
{
"files": [
"discord_cloudsync.node",
"index.js",
"manifest.json"
]
}

View File

@ -0,0 +1,19 @@
const { EventEmitter } = require("events")
class Logger extends EventEmitter{
constructor(name){
super()
this.name = name
this.on("log", data => {
let args = ["%c["+this.name+"]", "\n font-weight: bold;\n color: purple;\n", ...data]
console.log(...args)
})
}
log(...data){
this.emit("log", data)
}
}
module.exports = Logger
module.exports.default = Logger

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,123 @@
const ModuleLoader = require("./loaders/modules")
const { EventEmitter } = require("events")
const Logger = require("./Logger")
const fs = require("fs")
const path = require("path")
const events = exports.events = new EventEmitter()
const logger = exports.logger = new Logger("LightCord")
let hasInit = false
let tries = 0
exports.init = function(){
if(hasInit == true){
console.warn(new Error("LightCord has already inited."))
return
}
hasInit = true
let readyInterval = setInterval(()=>{
events.emit("debug", `[INIT] try ${tries++} loading LightCord`)
try{
if(!global.webpackJsonp)return
if(!ModuleLoader.get(4))return
clearInterval(readyInterval)
privateInit()
.then(() => {
console.log("Finished loading Lightcord.")
})
}catch(e){
console.error(e)
}
}, 100)
}
let hasPrivateInit = false
async function privateInit(){
if(!hasInit)return
if(hasPrivateInit)return
hasPrivateInit = true
//disabling sentry
BDModules.get(e => e.getCurrentHub)[0].getCurrentHub().getClient().getOptions().enabled = false
window.lightcordSettings = {
devMode: false,
callRingingBeat: true
}
let original = BDModules.get((e) => e.createSound)[0].createSound
BDModules.get((e) => e.createSound)[0].createSound = function(sound){
let isCalling = sound === "call_ringing_beat" || sound === "call_ringing"
if(isCalling){
let returned = original(...arguments)
Object.defineProperty(returned, "name", {
get(){
return window.lightcordSettings.callRingingBeat ? "call_ringing_beat" : "call_ringing"
},
set(data){
console.log("Attempting to set call_ringing value. Canceling "+data)
}
})
console.log(returned)
return returned
}else{
return original(...arguments)
}
}
let constants = ModuleLoader.get(m=>m.API_HOST)[0]
let dispatcher = ModuleLoader.get(m=>m.Dispatcher&&m.default&&m.default.dispatch)[0]
require("../../../../../BetterDiscordApp/css/main.css")
require("./lightcord.css")
window.$ = window.jQuery = require("./jquery.min.js")
require("./ace.js")
if(!fs.existsSync(BetterDiscordConfig.dataPath))fs.mkdirSync(BetterDiscordConfig.dataPath, {recursive: true})
let pluginPath = path.join(BetterDiscordConfig.dataPath, "plugins")
let themePath = path.join(BetterDiscordConfig.dataPath, "themes")
console.log(`Plugins: ${pluginPath}\nThemes: ${themePath}`)
if(!fs.existsSync(pluginPath))fs.mkdirSync(pluginPath, {recursive: true})
if(!fs.existsSync(themePath))fs.mkdirSync(themePath, {recursive: true})
// setting Discord Internal Developer Mode for developement and test purposes.
Object.defineProperty(ModuleLoader.get(e => e.default && typeof e.default === "object" && ("isDeveloper" in e.default))[0].default, "isDeveloper", {
get(){return !!window.lightcordSettings.devMode},
set(data){return !!window.lightcordSettings.devMode}
})
const BetterDiscord = window.BetterDiscord = new(require("../../../../../BetterDiscordApp/js/main").default)(BetterDiscordConfig)
BetterDiscord.init()
events.emit("ready")
}
require.extensions[".css"] = (m, filename) => {
let content = fs.readFileSync(filename, "binary")
let style = document.createElement("style")
style.id = btoa(filename)
style.innerText = content
document.head.appendChild(style)
m.exports = {
id: style.id,
remove(){
return style.remove()
}
}
return m.exports
}
const BetterDiscordConfig = window.BetterDiscordConfig = {
"local": true,
"localServer": "//localhost:8080",
"repo": "rauenzi",
"branch": "master",
"injectorBranch": "injector",
"minified": true,
"version": "0.3.2",
dataPath: (process.platform == "win32" ? process.env.APPDATA : process.platform == "darwin" ? process.env.HOME + "/Library/Preferences" : process.env.XDG_CONFIG_HOME ? process.env.XDG_CONFIG_HOME : process.env.HOME + "/.config") + "/LightCord_BD/",
os: process.platform
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,176 @@
/** Lightcord Custom */
.lc-tabWrapper {
position: relative;
overflow: hidden;
height: 100%;
background: #18191c;
border: 1px solid #040405;
border-radius: 5px;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
}
.lc-tabnav {
top: 0;
border-bottom: 1px solid #040405;
padding: 0 4px;
width: 100%;
-ms-flex-item-align: start;
align-self: flex-start;
position: relative;
overflow: hidden;
-webkit-box-sizing: border-box;
box-sizing: border-box;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-ms-flex-wrap: nowrap;
flex-wrap: nowrap;
-webkit-box-pack: start;
-ms-flex-pack: start;
justify-content: flex-start;
-webkit-box-align: stretch;
-ms-flex-align: stretch;
align-items: stretch;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: horizontal;
-webkit-box-direction: normal;
-ms-flex-direction: row;
flex-direction: row;
}
.lc-tab {
border-radius: 8px;
overflow: hidden;
position: relative;
margin: 0 auto;
width: 600px;
max-width: 100%;
-webkit-box-shadow: 0 0 20px 2px rgba(4,4,5,.3);
box-shadow: 0 0 20px 2px rgba(4,4,5,.3);
-webkit-transform: scale(.85);
transform: scale(.85);
-webkit-box-sizing: border-box;
box-sizing: border-box;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
-webkit-box-pack: start;
-ms-flex-pack: start;
justify-content: flex-start;
-webkit-box-align: stretch;
-ms-flex-align: stretch;
align-items: stretch;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
}
.lc-navItem {
padding: 14px 20px;
position: relative;
font-weight: 500;
-webkit-transform: color .125s;
transform: color .125s;
cursor: pointer;
max-height: 100%;
}
.lc-navItem::after {
position: absolute;
bottom: 0;
left: 20px;
right: 20px;
background: #7289da;
-webkit-box-shadow: 0 -4px 12px 0 #7289da;
box-shadow: 0 -4px 12px 0 #7289da;
content: "";
height: 2px;
-webkit-transition: -webkit-transform .125s;
transition: -webkit-transform .125s;
transition: transform .125s;
transition: transform .125s,-webkit-transform .125s;
}
.lc-navItemActive {
color: #fff;
}
.lc-navItemActive::after {
-webkit-transform: none;
transform: none;
}
.lc-navItemInactive {
color: #b9bbbe;
}
.lc-navItemInactive::after {
-webkit-transform: translateY(16px);
transform: translateY(16px);
}
.lc-fadeOverlay {
height: 20%;
left: 0;
bottom: 0;
background-image: -webkit-gradient(linear,left top,left bottom,from(rgba(24,25,28,0)),color-stop(70%,#18191c),to(#18191c));
background-image: linear-gradient(180deg,rgba(24,25,28,0),#18191c 70%,#18191c);
z-index: 900;
position: absolute;
width: 100%;
}
.lc-topSectionPlaying {
background: #7289da;
}
.lc-body {
height: 240px;
overflow: visible;
background: #2f3136;
}
.lc-userInfoSection {
margin: 0 20px;
padding: 20px 0;
}
.lc-userInfoSectionHeader {
font-size: 12px;
font-weight: 700;
color: #72767d;
margin-bottom: 10px;
text-transform: uppercase;
}
.lc-note {
margin: -4px;
color: #b9bbbe;
border-radius: 3px;
font-size: 14px;
line-height: 16px;
padding: 4px;
}
.lc-header {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
padding: 20px;
}
.lc-avatar1 {
position: relative;
border-radius: 50%;
-ms-flex-negative: 0;
flex-shrink: 0;
}
.lc-avatar2 {
margin-right: 20px;
}
.lc-profile {
width: 90px;
height: 90px;
}

View File

@ -0,0 +1,31 @@
class Modules {
/** use this as less as possible */
static get modules(){
let cache = webpackJsonp.push([[],{['']:(_,e,r)=>{e.cache=r.c}},[['']]]).cache
return Object.values(cache)
}
static get(ids, modules){
if(typeof ids === "function"){
return (modules || this.modules).map((mdl) => {
if(mdl && typeof mdl.exports !== "undefined"){
return mdl.exports
}else{
return null
}
}).filter(e => e).filter(ids)
}else if(Array.isArray(ids)){
modules = modules || this.modules
return ids.map(id => this.get(id, modules))
}else{
modules = modules || this.modules
let module = modules.find(e => e.i === ids)
if(!module)return undefined
return module.exports
}
}
}
module.exports = Modules
module.exports.default = Modules
global.BDModules = Modules

View File

@ -0,0 +1,55 @@
'use strict';
// before we can set up (and export) our constants, we first need to grab bootstrap's constants
// so we can merge them in with our constants
function init(bootstrapConstants) {
const APP_NAME = bootstrapConstants.APP_NAME;
const API_ENDPOINT = bootstrapConstants.API_ENDPOINT;
const UPDATE_ENDPOINT = bootstrapConstants.UPDATE_ENDPOINT;
const APP_ID = bootstrapConstants.APP_ID;
const DEFAULT_MAIN_WINDOW_ID = 0;
const MAIN_APP_DIRNAME = __dirname;
const UpdaterEvents = {
UPDATE_NOT_AVAILABLE: 'UPDATE_NOT_AVAILABLE',
CHECKING_FOR_UPDATES: 'CHECKING_FOR_UPDATES',
UPDATE_ERROR: 'UPDATE_ERROR',
UPDATE_MANUALLY: 'UPDATE_MANUALLY',
UPDATE_AVAILABLE: 'UPDATE_AVAILABLE',
MODULE_INSTALL_PROGRESS: 'MODULE_INSTALL_PROGRESS',
UPDATE_DOWNLOADED: 'UPDATE_DOWNLOADED',
MODULE_INSTALLED: 'MODULE_INSTALLED',
CHECK_FOR_UPDATES: 'CHECK_FOR_UPDATES',
QUIT_AND_INSTALL: 'QUIT_AND_INSTALL',
MODULE_INSTALL: 'MODULE_INSTALL',
MODULE_QUERY: 'MODULE_QUERY',
UPDATER_HISTORY_QUERY_AND_TRUNCATE: 'UPDATER_HISTORY_QUERY_AND_TRUNCATE',
UPDATER_HISTORY_RESPONSE: 'UPDATER_HISTORY_RESPONSE'
};
const MenuEvents = {
OPEN_HELP: 'menu:open-help',
OPEN_SETTINGS: 'menu:open-settings',
CHECK_FOR_UPDATES: 'menu:check-for-updates'
};
const exported = {
APP_NAME,
DEFAULT_MAIN_WINDOW_ID,
MAIN_APP_DIRNAME,
APP_ID,
API_ENDPOINT,
UPDATE_ENDPOINT,
UpdaterEvents,
MenuEvents
};
for (const key of Object.keys(exported)) {
module.exports[key] = exported[key];
}
}
module.exports = {
init
};

View File

@ -0,0 +1,26 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.getEnableHardwareAcceleration = getEnableHardwareAcceleration;
exports.setEnableHardwareAcceleration = setEnableHardwareAcceleration;
var _electron = require('electron');
var _appSettings = require('./appSettings');
const settings = _appSettings();
function getEnableHardwareAcceleration() {
// TODO: This should probably a constant
return settings.get('enableHardwareAcceleration', true);
}
function setEnableHardwareAcceleration(enableHardwareAcceleration) {
settings.set('enableHardwareAcceleration', enableHardwareAcceleration);
settings.save();
_electron.app.relaunch();
_electron.app.exit(0);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

View File

@ -0,0 +1,104 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.hasInit = undefined;
exports.init = init;
var _electron = require('electron');
var _utils = require('./utils');
var _mainScreen = require('./mainScreen');
var _appFeatures = require('./appFeatures');
var _ipcMain = require('./ipcMain');
var _ipcMain2 = _interopRequireDefault(_ipcMain);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
const features = (0, _appFeatures.getFeatures)();
let hasInit = exports.hasInit = false;
let lastIndex;
let appIcons;
/**
* Used on Windows to set the taskbar icon
*/
function init() {
// Only init on win32 platforms
if (process.platform !== 'win32') return;
if (hasInit) {
console.warn('appBadge: Has already init! Cancelling init.');
return;
}
exports.hasInit = hasInit = true;
lastIndex = null;
appIcons = [];
const resourcePath = `app/images/badges`;
for (let i = 1; i <= 11; i++) {
appIcons.push(_utils.exposeModuleResource(resourcePath, `badge-${i}.ico`));
}
// TODO: remove on or after April 2018
features.declareSupported('new_app_badge');
_ipcMain2.default.on('APP_BADGE_SET', (_event, count) => setAppBadge(count));
}
function setAppBadge(count) {
const win = _electron.BrowserWindow.fromId((0, _mainScreen.getMainWindowId)());
const { index, description } = getOverlayIconData(count);
// Prevent setting a new icon when the icon is the same
if (lastIndex !== index) {
if (index == null) {
win.setOverlayIcon(null, description);
} else {
win.setOverlayIcon(appIcons[index], description);
}
lastIndex = index;
}
}
/*
* -1 is bullet
* 0 is nothing
* 1-9 is a number badge
* 10+ is `9+`
*/
function getOverlayIconData(count) {
// Unread message badge
if (count === -1) {
return {
index: 10, // this.appIcons.length - 1
description: `Unread messages`
};
}
// Clear overlay icon
if (count === 0) {
return {
index: null, // null is used to clear the overlay icon
description: 'No Notifications'
};
}
// Notification badge
const index = Math.max(1, Math.min(count, 10)) - 1; // arrays are 0 based
return {
index,
description: `${index} notifications`
};
}

View File

@ -0,0 +1,68 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.hasInit = undefined;
exports.init = init;
var _autoStart = require('./autoStart');
var autoStart = _interopRequireWildcard(_autoStart);
var _appSettings = require('./appSettings');
var _appFeatures = require('./appFeatures');
var _ipcMain = require('./ipcMain');
var _ipcMain2 = _interopRequireDefault(_ipcMain);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
const settings = _appSettings();
const features = (0, _appFeatures.getFeatures)();
const NOOP = () => {};
let hasInit = exports.hasInit = false;
function init() {
if (hasInit) {
console.warn('appConfig: Has already init! Cancelling init.');
return;
}
exports.hasInit = hasInit = true;
// TODO remove on or after March 2018
features.declareSupported('app_configs');
_ipcMain2.default.on('TOGGLE_MINIMIZE_TO_TRAY', (_event, value) => setMinimizeOnClose(value));
_ipcMain2.default.on('TOGGLE_OPEN_ON_STARTUP', (_event, value) => toggleRunOnStartup(value));
_ipcMain2.default.on('TOGGLE_START_MINIMIZED', (_event, value) => toggleStartMinimized(value));
}
function setMinimizeOnClose(minimizeToTray) {
settings.set('MINIMIZE_TO_TRAY', minimizeToTray);
}
function toggleRunOnStartup(openOnStartup) {
settings.set('OPEN_ON_STARTUP', openOnStartup);
if (openOnStartup) {
autoStart.install(NOOP);
} else {
autoStart.uninstall(NOOP);
}
}
function toggleStartMinimized(startMinimized) {
settings.set('START_MINIMIZED', startMinimized);
autoStart.isInstalled(installed => {
// Only update the registry for this toggle if the app was already set to autorun
if (installed) {
autoStart.install(NOOP);
}
});
}

View File

@ -0,0 +1,23 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.init = init;
exports.getFeatures = getFeatures;
var _FeatureFlags = require('../common/FeatureFlags');
var _FeatureFlags2 = _interopRequireDefault(_FeatureFlags);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
let features;
function init() {
features = new _FeatureFlags2.default();
}
function getFeatures() {
return features;
}

View File

@ -0,0 +1,3 @@
'use strict';
module.exports = require('./bootstrapModules').appSettings;

View File

@ -0,0 +1,147 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _electron = require('electron');
var _Constants = require('../Constants');
var Constants = _interopRequireWildcard(_Constants);
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
const { MenuEvents } = Constants;
const SEPARATOR = { type: 'separator' };
function getWindow() {
let window = _electron.BrowserWindow.getFocusedWindow();
if (!window) {
const windowList = _electron.BrowserWindow.getAllWindows();
if (windowList && windowList[0]) {
window = windowList[0];
window.show();
window.focus();
}
}
return window;
}
exports.default = [{
label: 'Lightcord',
submenu: [{
label: 'About Lightcord',
selector: 'orderFrontStandardAboutPanel:'
}, {
label: 'Check for Updates...',
click: () => _electron.app.emit(MenuEvents.CHECK_FOR_UPDATES)
}, {
label: 'Acknowledgements',
click: () => _electron.shell.openExternal('https://discord.com/acknowledgements')
}, SEPARATOR, {
label: 'Preferences',
click: () => _electron.app.emit(MenuEvents.OPEN_SETTINGS),
accelerator: 'Command+,'
}, SEPARATOR, {
label: 'Services',
submenu: []
}, SEPARATOR, {
label: 'Hide Lightcord',
selector: 'hide:',
accelerator: 'Command+H'
}, {
label: 'Hide Others',
selector: 'hideOtherApplications:',
accelerator: 'Command+Alt+H'
}, {
label: 'Show All',
selector: 'unhideAllApplications:'
}, SEPARATOR, {
label: 'Quit',
click: () => _electron.app.quit(),
accelerator: 'Command+Q'
}]
}, {
label: 'Edit',
submenu: [{
role: 'undo',
accelerator: 'Command+Z'
}, {
role: 'redo',
accelerator: 'Shift+Command+Z'
}, SEPARATOR, {
role: 'cut',
accelerator: 'Command+X'
}, {
role: 'copy',
accelerator: 'Command+C'
}, {
role: 'paste',
accelerator: 'Command+V'
}, {
role: 'selectAll',
accelerator: 'Command+A'
}]
}, {
label: 'View',
submenu: [{
label: 'Reload',
click: () => {
const window = getWindow();
if (window) {
window.setBackgroundColor(getBackgroundColor())
window.webContents.reloadIgnoringCache()
window.webContents.once("did-finish-load", () => {
window.setBackgroundColor("#00000000")
})
}
},
accelerator: 'Command+R'
}, {
label: 'Toggle Full Screen',
click: () => {
const window = getWindow();
if (window) {
window.setFullScreen(!window.isFullScreen());
}
},
accelerator: 'Command+Control+F'
}, SEPARATOR, {
label: 'Developer',
submenu: [{
label: 'Toggle Developer Tools',
click: () => {
const window = getWindow();
if (window) {
window.toggleDevTools();
}
},
accelerator: 'Alt+Command+I'
}]
}]
}, {
label: 'Window',
submenu: [{
label: 'Minimize',
selector: 'performMiniaturize:',
accelerator: 'Command+M'
}, {
label: 'Zoom',
selector: 'performZoom:'
}, {
label: 'Close',
accelerator: 'Command+W',
selector: 'hide:'
}, SEPARATOR, {
label: 'Bring All to Front',
selector: 'arrangeInFront:'
}]
}, {
label: 'Help',
submenu: [{
label: 'Lightcord Help',
click: () => _electron.app.emit(MenuEvents.OPEN_HELP)
}]
}];
module.exports = exports.default;

View File

@ -0,0 +1,12 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _electron = require('electron');
const menu = require('./' + process.platform);
exports.default = _electron.Menu.buildFromTemplate(menu);
module.exports = exports.default;

View File

@ -0,0 +1,85 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _electron = require('electron');
var _Constants = require('../Constants');
var Constants = _interopRequireWildcard(_Constants);
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
const { MenuEvents } = Constants;
const SEPARATOR = { type: 'separator' };
exports.default = [{
label: '&File',
submenu: [{
label: '&Options',
click: () => _electron.app.emit(MenuEvents.OPEN_SETTINGS),
accelerator: 'Control+,'
}, SEPARATOR, {
label: 'E&xit',
click: () => _electron.app.quit(),
accelerator: 'Control+Q'
}]
}, {
label: '&Edit',
submenu: [{
role: 'undo',
accelerator: 'Control+Z'
}, {
role: 'redo',
accelerator: 'Shift+Control+Z'
}, SEPARATOR, {
role: 'cut',
accelerator: 'Control+X'
}, {
role: 'copy',
accelerator: 'Control+C'
}, {
role: 'paste',
accelerator: 'Control+V'
}, {
role: 'selectAll',
accelerator: 'Control+A'
}]
}, {
label: '&View',
submenu: [{
label: '&Reload',
click: () => {
let window = _electron.BrowserWindow.getFocusedWindow()
window.setBackgroundColor(getBackgroundColor())
window.webContents.reloadIgnoringCache()
window.webContents.once("did-finish-load", () => {
window.setBackgroundColor("#00000000")
})
},
accelerator: 'Control+R'
}, {
label: 'Toggle &Full Screen',
click: () => _electron.BrowserWindow.getFocusedWindow().setFullScreen(!_electron.BrowserWindow.getFocusedWindow().isFullScreen()),
accelerator: 'Control+Shift+F'
}, SEPARATOR, {
label: '&Developer',
submenu: [{
label: 'Toggle Developer &Tools',
click: () => _electron.BrowserWindow.getFocusedWindow().toggleDevTools(),
accelerator: 'Control+Shift+I'
}]
}]
}, {
label: '&Help',
submenu: [{
label: 'Check for Updates',
click: () => _electron.app.emit(MenuEvents.CHECK_FOR_UPDATES)
}, SEPARATOR, {
label: 'Lightcord Help',
click: () => _electron.app.emit(MenuEvents.OPEN_HELP)
}]
}];
module.exports = exports.default;

Some files were not shown because too many files have changed in this diff Show More