This commit is contained in:
Leon Kowarschick 2020-04-18 20:27:12 +02:00
parent 3fac7d37ca
commit 6396f0d370
69 changed files with 5016 additions and 26 deletions

View file

@ -0,0 +1,20 @@
{
"extends": "eslint:recommended",
"env": {
"browser": true,
"node": true,
"es6": true
},
"parserOptions": {
"ecmaVersion": 8
},
"rules": {
"no-console": "off",
"no-unused-vars": "warn",
"no-useless-escape": "warn",
"no-empty": "warn",
"no-var": "error",
"no-mixed-spaces-and-tabs": "warn",
"prefer-const": "warn"
}
}

View file

@ -0,0 +1,12 @@
name: Discord Source detection
on: [issues]
jobs:
autoclose:
runs-on: ubuntu-latest
steps:
- name: Autoclose Discord Source issues
uses: IndyV/IssueChecker@v1.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
issue-close-message: "@${issue.user.login} This issue was automatically closed because we don't accept issue reports from Discord Source.\nThe reason for this is because usually these issues aren't well thought out and are often duplicates.\n\nPlease take a few more minutes to create a well-made, proper issue report."
issue-pattern: "Discord Source - .Issue Report]"

View file

@ -0,0 +1,3 @@
**/.vs
**/bin
**/obj

View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 joe27g
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.

View file

@ -0,0 +1,48 @@
# EnhancedDiscord
A lightweight client mod designed to enhance your Discord experience without slowing down your PC.
Support server:
<a href="https://discord.gg/XAvh9tq"><img src="https://discordapp.com/api/guilds/415246389287583755/embed.png" alt="Discord server" /></a>
(https://discord.gg/XAvh9tq)
#### DISCLAIMER!
> **Using EnhancedDiscord, or any other client mod, is against [Discord's Terms of Service](https://discordapp.com/terms). Use it at your own risk.**
> *It's very unlikely any action will be taken against you, but we take no responsibility if anything happens.*
By default, this loads my theme, with some settings applied. More info [on its repo](https://github.com/joe27g/Discord-Theme). To change this, edit `plugins/style.css` or change the theme location in settings.
You can download more quality plugins from the following repos:
- [joe27g/EnhancedDiscord-Plugins](https://github.com/joe27g/EnhancedDiscord-Plugins)
- [rauenzi/EnhancedDiscordPlugins](https://github.com/rauenzi/EnhancedDiscordPlugins)
- [jakuski/ed_plugins](https://github.com/jakuski/ed_plugins)
If you enable "BD Plugins" in the EnhancedDiscord settings, you can also load BetterDiscord plugins. See the #faq in the support server for more details.
*Note: some BD plugins are notorious for being laggy. In general, the fewer plugins you have, the faster your client will be.*
### Installing the easy way:
*Currently, only Windows and Ubuntu 18.04+ are supported for the new installer, due to lack of devices to compile on*
Go to https://enhanceddiscord.com/ and hit the 'Download' button, or if you're lazy, click [here](https://enhanceddiscord.com/EnhancedDiscord.exe).
If you're having errors (especially related to `InteropServices`), you may need to download [.NET Framework 4.7.1](https://www.microsoft.com/en-us/download/details.aspx?id=56116).
If you're on Linux or MacOS consider using [this install script](https://github.com/Cr3atable/LinuxED) that is specifically made for Unix.
LinuxED is unaffiliated with EnhancedDiscord so please do not request support for this script in an issue.
### Installing the hard way:
If the installer doesn't work for you, and you choose not to use LinuxED, you can also install it manually in just a few minutes.
To do so, see the [advanced installation guide](/advanced_installation.md).
### Explanation of files:
From now on, please refer to https://enhanceddiscord.com for explanations of included plugins.
### Custom plugins
To create your own plugin, check out [the plugin readme](/plugins.md).
### Contributing
Feel free to make pull requests or make your own plugins repository. If you do make your own plugins, request the 'Plugin Developer' role in the support server so you can announce your releases!

View file

@ -0,0 +1,26 @@
# Installing the hard way
#### Recommended for people that have trouble with the installers or really like wasting time.
1. Download/clone this repo to wherever you want your ED files to reside.
2. Find your appdata folder:
- For PC: `%appdata%/discord`
- For Mac: `~/Library/Application Support`
- For Linux: `$XDG_CONFIG_HOME/discord` or `~/.config/discord/`
- *Replace `discord` with `discordcanary` etc. as needed.*
3. In the appdata folder, find `/x.x.xxx/modules/discord_desktop_core/index.js`, where `x.x.xxx` is your current version of the Discord client, and open it.
4. At the top, add these lines:
```js
process.env.injDir = '<path>';
require(`${process.env.injDir}/injection.js`);
```
where `<path>` is the location of the ED folder.
Make sure to escape paths, for example `C:\Users\<Username>\Documents\EnhancedDiscord\EnhancedDiscord` should be `C:\\Users\\<Username>\\Documents\\EnhancedDiscord\\EnhancedDiscord`
5. Create `config.json` in your ED folder and set its contents to `{}`.
6. Restart your Discord client and installation should be complete.

View file

@ -0,0 +1,245 @@
#bd-settingspane-container h2.ui-form-title {
font-size: 16px;
font-weight: 600;
line-height: 20px;
text-transform: uppercase;
display: inline-block;
margin-bottom: 20px;
}
#bd-settingspane-container h2.ui-form-title {
color: #f6f6f7;
}
.theme-light #bd-settingspane-container h2.ui-form-title {
color: #4f545c;
}
#bd-settingspane-container .ui-switch-item {
flex-direction: column;
margin-top: 8px;
}
#bd-settingspane-container .ui-switch-item h3 {
font-size: 16px;
font-weight: 500;
line-height: 24px;
flex: 1;
}
#bd-settingspane-container .ui-switch-item h3 {
color: #f6f6f7;
}
.theme-light #bd-settingspane-container .ui-switch-item h3 {
color: #4f545c;
}
#bd-settingspane-container .ui-switch-item .style-description {
font-size: 14px;
font-weight: 500;
line-height: 20px;
margin-bottom: 10px;
padding-bottom: 10px;
border-bottom: 1px solid hsla(218,5%,47%,.3);
}
#bd-settingspane-container .ui-switch-item .style-description {
color: #72767d;
}
.theme-light #bd-settingspane-container .ui-switch-item .style-description {
color: rgba(114,118,125,.6);
}
#bd-settingspane-container .ui-switch-item .ui-switch-wrapper {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
position: relative;
width: 44px;
height: 24px;
display: block;
flex: 0 0 auto;
}
#bd-settingspane-container .ui-switch-item .ui-switch-wrapper input {
position: absolute;
opacity: 0;
cursor: pointer;
width: 100%;
height: 100%;
z-index: 1;
}
#bd-settingspane-container .ui-switch-item .ui-switch-wrapper .ui-switch {
background: #7289da;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background: #72767d;
border-radius: 14px;
transition: background .15s ease-in-out,box-shadow .15s ease-in-out,border .15s ease-in-out;
}
#bd-settingspane-container .ui-switch-item .ui-switch-wrapper .ui-switch:before {
content: '';
display: block;
width: 18px;
height: 18px;
position: absolute;
top: 3px;
left: 3px;
bottom: 3px;
background: #f6f6f7;
border-radius: 10px;
transition: all .15s ease;
box-shadow: 0 3px 1px 0 rgba(0,0,0,.05),0 2px 2px 0 rgba(0,0,0,.1),0 3px 3px 0 rgba(0,0,0,.05);
}
#bd-settingspane-container .ui-switch-item .ui-switch-wrapper .ui-switch.checked {
background: #7289da;
}
#bd-settingspane-container .ui-switch-item .ui-switch-wrapper .ui-switch.checked:before {
transform: translateX(20px);
}
#bd-settingspane-container .plugin-settings {
padding: 0 12px 12px 20px;
}
@keyframes bd-modal-backdrop {
to { opacity: 0.85; }
}
@keyframes bd-modal-anim {
to { transform: scale(1); opacity: 1; }
}
@keyframes bd-modal-backdrop-closing {
to { opacity: 0; }
}
@keyframes bd-modal-closing {
to { transform: scale(0.7); opacity: 0; }
}
#bd-settingspane-container .backdrop {
animation: bd-modal-backdrop 250ms ease;
animation-fill-mode: forwards;
background-color: rgb(0, 0, 0);
transform: translateZ(0px);
}
#bd-settingspane-container.closing .backdrop {
animation: bd-modal-backdrop-closing 200ms linear;
animation-fill-mode: forwards;
animation-delay: 50ms;
opacity: 0.85;
}
#bd-settingspane-container.closing .modal {
animation: bd-modal-closing 250ms cubic-bezier(0.19, 1, 0.22, 1);
animation-fill-mode: forwards;
opacity: 1;
transform: scale(1);
}
#bd-settingspane-container .modal {
animation: bd-modal-anim 250ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
animation-fill-mode: forwards;
transform: scale(0.7);
transform-origin: 50% 50%;
}
/* Toast CSS */
.toasts {
position: fixed;
display: flex;
top: 0;
flex-direction: column;
align-items: center;
justify-content: flex-end;
pointer-events: none;
z-index: 4000;
}
@keyframes toast-up {
from {
transform: translateY(0);
opacity: 0;
}
}
.toast {
animation: toast-up 300ms ease;
transform: translateY(-10px);
background: #36393F;
padding: 10px;
border-radius: 5px;
box-shadow: 0 0 0 1px rgba(32,34,37,.6), 0 2px 10px 0 rgba(0,0,0,.2);
font-weight: 500;
color: #fff;
user-select: text;
font-size: 14px;
opacity: 1;
margin-top: 10px;
pointer-events: none;
user-select: none;
}
@keyframes toast-down {
to {
transform: translateY(0px);
opacity: 0;
}
}
.toast.closing {
animation: toast-down 200ms ease;
animation-fill-mode: forwards;
opacity: 1;
transform: translateY(-10px);
}
.toast.icon {
padding-left: 30px;
background-size: 20px 20px;
background-repeat: no-repeat;
background-position: 6px 50%;
}
.toast.toast-info {
background-color: #4a90e2;
}
.toast.toast-info.icon {
background-image: url(data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjRkZGRkZGIiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4gICAgPHBhdGggZD0iTTAgMGgyNHYyNEgweiIgZmlsbD0ibm9uZSIvPiAgICA8cGF0aCBkPSJNMTIgMkM2LjQ4IDIgMiA2LjQ4IDIgMTJzNC40OCAxMCAxMCAxMCAxMC00LjQ4IDEwLTEwUzE3LjUyIDIgMTIgMnptMSAxNWgtMnYtNmgydjZ6bTAtOGgtMlY3aDJ2MnoiLz48L3N2Zz4=);
}
.toast.toast-success {
background-color: #43b581;
}
.toast.toast-success.icon {
background-image: url(data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjRkZGRkZGIiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4gICAgPHBhdGggZD0iTTAgMGgyNHYyNEgweiIgZmlsbD0ibm9uZSIvPiAgICA8cGF0aCBkPSJNMTIgMkM2LjQ4IDIgMiA2LjQ4IDIgMTJzNC40OCAxMCAxMCAxMCAxMC00LjQ4IDEwLTEwUzE3LjUyIDIgMTIgMnptLTIgMTVsLTUtNSAxLjQxLTEuNDFMMTAgMTQuMTdsNy41OS03LjU5TDE5IDhsLTkgOXoiLz48L3N2Zz4=);
}
.toast.toast-danger,
.toast.toast-error {
background-color: #f04747;
}
.toast.toast-danger.icon,
.toast.toast-error.icon {
background-image: url(data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjRkZGRkZGIiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4gICAgPHBhdGggZD0iTTEyIDJDNi40NyAyIDIgNi40NyAyIDEyczQuNDcgMTAgMTAgMTAgMTAtNC40NyAxMC0xMFMxNy41MyAyIDEyIDJ6bTUgMTMuNTlMMTUuNTkgMTcgMTIgMTMuNDEgOC40MSAxNyA3IDE1LjU5IDEwLjU5IDEyIDcgOC40MSA4LjQxIDcgMTIgMTAuNTkgMTUuNTkgNyAxNyA4LjQxIDEzLjQxIDEyIDE3IDE1LjU5eiIvPiAgICA8cGF0aCBkPSJNMCAwaDI0djI0SDB6IiBmaWxsPSJub25lIi8+PC9zdmc+);
}
.toast.toast-warning,
.toast.toast-warn {
background-color: #FFA600;
color: white;
}
.toast.toast-warning.icon,
.toast.toast-warn.icon {
background-image: url(data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjRkZGRkZGIiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4gICAgPHBhdGggZD0iTTAgMGgyNHYyNEgweiIgZmlsbD0ibm9uZSIvPiAgICA8cGF0aCBkPSJNMSAyMWgyMkwxMiAyIDEgMjF6bTEyLTNoLTJ2LTJoMnYyem0wLTRoLTJ2LTRoMnY0eiIvPjwvc3ZnPg==);
}

View file

@ -0,0 +1,158 @@
const path = require('path');
const fs = require('fs');
const Module = require('module').Module;
const originalRequire = Module._extensions['.js'];
const EDPlugin = require('./plugin');
module.exports = class BDManager {
static async setup(currentWindow) {
this.currentWindow = currentWindow;
this.defineGlobals();
this.jqueryElement = document.createElement('script');
this.jqueryElement.src = `//ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js`;
await new Promise(resolve => {
this.jqueryElement.onload = resolve;
document.head.appendChild(this.jqueryElement);
});
this.observer = new MutationObserver((mutations) => {
for (let i = 0, mlen = mutations.length; i < mlen; i++) this.fireEvent('observer', mutations[i]);
});
this.observer.observe(document, {childList: true, subtree: true});
this.currentWindow.webContents.on('did-navigate-in-page', BDManager.onSwitch);
fs.readFile(path.join(process.env.injDir, 'bd.css'), (err, text) => {
if (err) return console.error(err);
window.EDApi.injectCSS('BDManager', text);
})
Module._extensions['.js'] = this.pluginRequire();
}
static destroy() {
window.EDApi.clearCSS('BDManager');
this.observer.disconnect();
this.currentWindow.webContents.removeEventListener('did-navigate-in-page', BDManager.onSwitch);
this.jqueryElement.remove();
Module._extensions['.js'] = originalRequire;
}
static onSwitch() {
BDManager.fireEvent('onSwitch');
}
static extractMeta(content) {
const meta = content.split('\n')[0];
const rawMeta = meta.substring(meta.lastIndexOf('//META') + 6, meta.lastIndexOf('*//'));
if (meta.indexOf('META') < 0) throw new Error('META was not found.');
if (!window.EDApi.testJSON(rawMeta)) throw new Error('META could not be parsed.');
const parsed = JSON.parse(rawMeta);
if (!parsed.name) throw new Error('META missing name data.');
return parsed;
}
static pluginRequire() {
return function(moduleWrap, filename) {
if (!filename.endsWith('.plugin.js') || path.dirname(filename) !== path.resolve(process.env.injDir, 'plugins')) return Reflect.apply(originalRequire, this, arguments);
let content = fs.readFileSync(filename, 'utf8');
if (content.charCodeAt(0) === 0xFEFF) content = content.slice(1); // Strip BOM
const meta = BDManager.extractMeta(content);
meta.filename = path.basename(filename);
content += `\nmodule.exports = ${meta.name};`;
moduleWrap._compile(content, filename);
moduleWrap.exports = BDManager.convertPlugin(new moduleWrap.exports());
};
}
static fireEvent(event, ...args) {
const plugins = Object.values(window.ED.plugins);
for (let p = 0; p < plugins.length; p++) {
const plugin = plugins[p];
if (!plugin[event] || typeof plugin[event] !== 'function') continue;
try { plugin[event](...args); }
catch (error) { throw new Error(`Could not fire ${event} for plugin ${plugin.name}.`); }
}
}
static convertPlugin(plugin) {
const newPlugin = new EDPlugin({
name: plugin.getName(),
load: function() {plugin.start();},
unload: function() {plugin.stop();},
bdplugin: plugin
});
Object.defineProperties(newPlugin, {
name: {
enumerable: true, configurable: true,
get() {return plugin.getName();}
},
author: {
enumerable: true, configurable: true,
get() {return plugin.getAuthor();}
},
description: {
enumerable: true, configurable: true,
get() {return plugin.getDescription();}
}
});
if (typeof plugin.getSettingsPanel == 'function') newPlugin.getSettingsPanel = function() {return plugin.getSettingsPanel();};
if (typeof plugin.onSwitch == 'function') newPlugin.onSwitch = function() {return plugin.onSwitch();};
if (typeof plugin.observer == 'function') newPlugin.observer = function(e) {return plugin.observer(e);};
return newPlugin;
}
static defineGlobals() {
window.bdConfig = {dataPath: process.env.injDir};
window.bdplugins = window.bdthemes = window.pluginCookie = window.themeCookie = window.settingsCookie = {};
window.bdpluginErrors = window.bdthemeErrors = [];
window.bdPluginStorage = {get: window.EDApi.getData, set: window.EDApi.setData};
window.Utils = {monkeyPatch: window.EDApi.monkeyPatch, suppressErrors: window.EDApi.suppressErrors, escapeID: window.EDApi.escapeID};
window.BDV2 = class V2 {
static get WebpackModules() {return {find: window.EDApi.findModule, findAll: window.EDApi.findAllModules, findByUniqueProperties: window.EDApi.findModuleByProps, findByDisplayName: window.EDApi.findModuleByDisplayName};}
static getInternalInstance(node) {return window.EDApi.getInternalInstance(node);}
static get react() {return window.EDApi.React;}
static get reactDom() {return window.EDApi.ReactDOM;}
};
}
static showSettingsModal(plugin) {
const baseModalClasses = window.EDApi.findModule(m => m.modal && m.inner && !m.sizeMedium) || {modal: "modal-36zFtW", inner: "inner-2VEzy9"};
const modalClasses = window.EDApi.findModuleByProps("modal", "sizeMedium") || {modal: "backdrop-1wrmKb", sizeMedium: "sizeMedium-ctncE5", content: "content-2KoCOZ", header: "header-2nhbou", footer: "footer-30ewN8", close: "close-hhyjWJ", inner: "inner-2Z5QZX"};
const backdrop = window.EDApi.findModuleByProps("backdrop") || {backdrop: "backdrop-1wrmKb"};
const modalHTML = `<div id="bd-settingspane-container" class="theme-dark">
<div class="backdrop ${backdrop.backdrop}" style="background-color: rgb(0, 0, 0); opacity: 0.85;"></div>
<div class="modal ${baseModalClasses.modal}" style="opacity: 1;">
<div class="${baseModalClasses.inner}">
<div class="${modalClasses.modal} ${modalClasses.sizeMedium}" style="overflow: hidden;">
<div class="flex-1xMQg5 flex-1O1GKY horizontal-1ae9ci horizontal-2EEEnY flex-1O1GKY directionRow-3v3tfG justifyStart-2NDFzi alignCenter-1dQNNs noWrap-3jynv6 ${modalClasses.header}">
<h4 class="title h4-AQvcAz title-3sZWYQ size16-14cGz5 height20-mO2eIN weightSemiBold-NJexzi defaultColor-1_ajX0 defaultMarginh4-2vWMG5 marginReset-236NPn">{{modalTitle}}</h4>
<svg viewBox="0 0 12 12" name="Close" width="18" height="18" class="close-button ${modalClasses.close} flexChild-faoVW3"><g fill="none" fill-rule="evenodd"><path d="M0 0h12v12H0"></path><path class="fill" fill="currentColor" d="M9.5 3.205L8.795 2.5 6 5.295 3.205 2.5l-.705.705L5.295 6 2.5 8.795l.705.705L6 6.705 8.795 9.5l.705-.705L6.705 6"></path></g></svg>
</div>
<div class="scrollerWrap-2lJEkd scrollerThemed-2oenus themeGhostHairline-DBD-2d ${modalClasses.content}">
<div id="{{id}}" class="scroller-2FKFPG ${modalClasses.inner} selectable plugin-settings" data-no-focus-lock="true">
</div>
</div>
<div class="flex-1xMQg5 flex-1O1GKY horizontalReverse-2eTKWD horizontalReverse-3tRjY7 flex-1O1GKY directionRowReverse-m8IjIq justifyStart-2NDFzi alignStretch-DpGPf3 noWrap-3jynv6 ${modalClasses.footer}" style="flex: 0 0 auto;"><button type="button" class="done-button button-38aScr lookFilled-1Gx00P colorBrand-3pXr91 sizeMedium-1AC_Sl grow-q77ONN"><div class="contents-18-Yxp">Done</div></button></div>
</div>
</div>
</div>
</div>`;
const panel = plugin.getSettingsPanel();
if (!panel) return;
const modal = window.$(window.EDApi.formatString(modalHTML, {modalTitle: `${plugin.name} Settings`, id: `plugin-settings-${plugin.name}`}));
if (typeof panel == 'string') modal.find('.plugin-settings').html(panel);
else modal.find('.plugin-settings').append(panel);
modal.find('.backdrop, .close-button, .done-button').on('click', () => {
modal.addClass('closing');
setTimeout(() => { modal.remove(); }, 300);
});
modal.appendTo('#app-mount');
}
};

View file

@ -0,0 +1 @@
{"anti_track":{"enabled":false},"avatar_links":{"enabled":false},"char_count":{"enabled":false},"css_loader":{"path":"glasscord_css.css"},"direct_download":{"enabled":false},"double_click_edit":{"enabled":false},"double_click_mention":{"enabled":false},"ed_settings":{"enabled":true},"friend_count":{"onlineOnly":false,"enabled":false},"guild_count":{"enabled":false},"hidden_channels":{"enabled":false},"quick_save":{"enabled":false},"silence":{"enabled":false},"silent_typing":{"enabled":false},"tag_all":{"enabled":false}}

View file

@ -0,0 +1,456 @@
const path = window.require('path');
const fs = window.require('fs');
const electron = window.require('electron');
const Module = window.require('module').Module;
Module.globalPaths.push(path.resolve(electron.remote.app.getAppPath(), 'node_modules'));
const currentWindow = electron.remote.getCurrentWindow();
if (currentWindow.__preload) require(currentWindow.__preload);
//Get inject directory
if (!process.env.injDir) process.env.injDir = __dirname;
//set up global functions
const c = {
log: function(msg, plugin) {
if (plugin && plugin.name)
console.log(`%c[EnhancedDiscord] %c[${plugin.name}]`, 'color: red;', `color: ${plugin.color}`, msg);
else console.log('%c[EnhancedDiscord]', 'color: red;', msg);
},
info: function(msg, plugin) {
if (plugin && plugin.name)
console.info(`%c[EnhancedDiscord] %c[${plugin.name}]`, 'color: red;', `color: ${plugin.color}`, msg);
else console.info('%c[EnhancedDiscord]', 'color: red;', msg);
},
warn: function(msg, plugin) {
if (plugin && plugin.name)
console.warn(`%c[EnhancedDiscord] %c[${plugin.name}]`, 'color: red;', `color: ${plugin.color}`, msg);
else console.warn('%c[EnhancedDiscord]', 'color: red;', msg);
},
error: function(msg, plugin) {
if (plugin && plugin.name)
console.error(`%c[EnhancedDiscord] %c[${plugin.name}]`, 'color: red;', `color: ${plugin.color}`, msg);
else console.error('%c[EnhancedDiscord]', 'color: red;', msg);
},
sleep: function(ms) {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
}
}
// config util
window.ED = { plugins: {}, version: '2.6.2' };
Object.defineProperty(window.ED, 'config', {
get: function() {
let conf;
try{
conf = require('./config.json');
} catch (err) {
if(err.code !== 'MODULE_NOT_FOUND')
c.error(err);
conf = {};
}
return conf;
},
set: function(newSets = {}) {
let confPath;
let bDelCache;
try{
confPath = require.resolve('./config.json');
bDelCache = true;
} catch (err) {
if(err.code !== 'MODULE_NOT_FOUND')
c.error(err);
confPath = path.join(process.env.injDir, 'config.json');
bDelCache = false;
}
try {
fs.writeFileSync(confPath, JSON.stringify(newSets));
if(bDelCache)
delete require.cache[confPath];
} catch(err) {
c.error(err);
}
return this.config;
}
});
function loadPlugin(plugin) {
try {
if (plugin.preload)
console.log(`%c[EnhancedDiscord] %c[PRELOAD] %cLoading plugin %c${plugin.name}`, 'color: red;', 'color: yellow;', '', `color: ${plugin.color}`, `by ${plugin.author}...`);
else console.log(`%c[EnhancedDiscord] %cLoading plugin %c${plugin.name}`, 'color: red;', '', `color: ${plugin.color}`, `by ${plugin.author}...`);
plugin.load();
} catch(err) {
c.error(`Failed to load:\n${err.stack}`, plugin);
}
}
window.ED.localStorage = window.localStorage;
process.once("loaded", async () => {
c.log(`v${window.ED.version} is running. Validating plugins...`);
const pluginFiles = fs.readdirSync(path.join(process.env.injDir, 'plugins'));
const plugins = {};
for (const i in pluginFiles) {
if (!pluginFiles[i].endsWith('.js') || pluginFiles[i].endsWith(".plugin.js")) continue;
let p;
const pName = pluginFiles[i].replace(/\.js$/, '');
try {
p = require(path.join(process.env.injDir, 'plugins', pName));
if (typeof p.name !== 'string' || typeof p.load !== 'function') {
throw new Error('Plugin must have a name and load() function.');
}
plugins[pName] = Object.assign(p, {id: pName});
}
catch (err) {
c.warn(`Failed to load ${pluginFiles[i]}: ${err}\n${err.stack}`, p);
}
}
for (const id in plugins) {
if (!plugins[id] || !plugins[id].name || typeof plugins[id].load !== 'function') {
c.info(`Skipping invalid plugin: ${id}`); delete plugins[id]; continue;
}
plugins[id].settings; // this will set default settings in config if necessary
}
window.ED.plugins = plugins;
c.log(`Plugins validated.`);
while (!window.webpackJsonp)
await c.sleep(100); // wait until this is loaded in order to use it for modules
window.ED.webSocket = window._ws;
/* Add helper functions that make plugins easy to create */
window.req = window.webpackJsonp.push([[], {
'__extra_id__': (module, exports, req) => module.exports = req
}, [['__extra_id__']]]);
delete window.req.m['__extra_id__'];
delete window.req.c['__extra_id__'];
window.findModule = window.EDApi.findModule;
window.findModules = window.EDApi.findAllModules;
window.findRawModule = window.EDApi.findRawModule;
window.monkeyPatch = window.EDApi.monkeyPatch;
while (!window.EDApi.findModule('dispatch'))
await c.sleep(100);
c.log(`Loading preload plugins...`);
for (const id in plugins) {
if (window.ED.config[id] && window.ED.config[id].enabled == false) continue;
if (!plugins[id].preload) continue;
loadPlugin(plugins[id]);
}
const d = {resolve: () => {}};
window.monkeyPatch(window.findModule('dispatch'), 'dispatch', {before: b => {
// modules seem to all be loaded when RPC server loads
if (b.methodArguments[0].type === 'RPC_SERVER_READY') {
window.findModule('dispatch').dispatch.unpatch();
d.resolve();
}
}});
await new Promise(resolve => {
d.resolve = resolve;
})
c.log(`Modules done loading (${Object.keys(window.req.c).length})`);
if (window.ED.config.bdPlugins) {
await require('./bd_shit').setup(currentWindow);
c.log(`Preparing BD plugins...`);
for (const i in pluginFiles) {
if (!pluginFiles[i].endsWith('.js') || !pluginFiles[i].endsWith(".plugin.js")) continue;
let p;
const pName = pluginFiles[i].replace(/\.js$/, '');
try {
p = require(path.join(process.env.injDir, 'plugins', pName));
if (typeof p.name !== 'string' || typeof p.load !== 'function') {
throw new Error('Plugin must have a name and load() function.');
}
plugins[pName] = Object.assign(p, {id: pName});
}
catch (err) {
c.warn(`Failed to load ${pluginFiles[i]}: ${err}\n${err.stack}`, p);
}
}
for (const id in plugins) {
if (!plugins[id] || !plugins[id].name || typeof plugins[id].load !== 'function') {
c.info(`Skipping invalid plugin: ${id}`); delete plugins[id]; continue;
}
}
}
c.log(`Loading plugins...`);
for (const id in plugins) {
if (window.ED.config[id] && window.ED.config[id].enabled == false) continue;
if (plugins[id].preload) continue;
if (window.ED.config[id].enabled !== true && plugins[id].disabledByDefault) {
plugins[id].settings.enabled = false; continue;
}
loadPlugin(plugins[id]);
}
const ht = window.EDApi.findModule('hideToken')
// prevent client from removing token from localstorage when dev tools is opened, or reverting your token if you change it
window.EDApi.monkeyPatch(ht, 'hideToken', () => {});
window.fixedShowToken = () => {
// Only allow this to add a token, not replace it. This allows for changing of the token in dev tools.
if (!window.ED.localStorage || window.ED.localStorage.getItem("token")) return;
return window.ED.localStorage.setItem("token", '"'+ht.getToken()+'"');
};
window.EDApi.monkeyPatch(ht, 'showToken', window.fixedShowToken);
if (!window.ED.localStorage.getItem("token") && ht.getToken())
window.fixedShowToken(); // prevent you from being logged out for no reason
// change the console warning to be more fun
const wc = require('electron').remote.getCurrentWebContents();
wc.removeAllListeners("devtools-opened");
wc.on("devtools-opened", () => {
console.log("%cHold Up!", "color: #FF5200; -webkit-text-stroke: 2px black; font-size: 72px; font-weight: bold;");
console.log("%cIf you're reading this, you're probably smarter than most Discord developers.", "font-size: 16px;");
console.log("%cPasting anything in here could actually improve the Discord client.", "font-size: 18px; font-weight: bold; color: red;");
console.log("%cUnless you understand exactly what you're doing, keep this window open to browse our bad code.", "font-size: 16px;");
console.log("%cIf you don't understand exactly what you're doing, you should come work with us: https://discordapp.com/jobs", "font-size: 16px;");
});
})
/* BD/ED joint api */
window.EDApi = window.BdApi = class EDApi {
static get React() { return this.findModuleByProps('createElement'); }
static get ReactDOM() { return this.findModuleByProps('findDOMNode'); }
static escapeID(id) {
return id.replace(/^[^a-z]+|[^\w-]+/gi, "");
}
static injectCSS(id, css) {
const style = document.createElement("style");
style.id = this.escapeID(id);
style.innerHTML = css;
document.head.append(style);
}
static clearCSS(id) {
const element = document.getElementById(this.escapeID(id));
if (element) element.remove();
}
static linkJS(id, url) {
return new Promise(resolve => {
const script = document.createElement("script");
script.id = this.escapeID(id);
script.src = url;
script.type = "text/javascript";
script.onload = resolve;
document.head.append(script);
});
}
static unlinkJS(id) {
const element = document.getElementById(this.escapeID(id));
if (element) element.remove();
}
static getPlugin(name) {
const plugin = Object.values(window.ED.plugins).find(p => p.name == name);
if (!plugin) return null;
return plugin.bdplugin ? plugin.bdplugin : plugin;
}
static alert(title, body) {
const ModalStack = this.findModuleByProps("push", "update", "pop", "popWithKey");
const AlertModal = this.findModule(m => m.prototype && m.prototype.handleCancel && m.prototype.handleSubmit && m.prototype.handleMinorConfirm);
if (!ModalStack || !AlertModal) return window.alert(body);
ModalStack.push(function(props) {
return EDApi.React.createElement(AlertModal, Object.assign({title, body}, props));
});
}
static loadData(pluginName, key) {
if (!window.ED.config[pluginName]) window.ED.config[pluginName] = {};
return window.ED.config[pluginName][key];
}
static saveData(pluginName, key, data) {
if (!window.ED.config[pluginName]) window.ED.config[pluginName] = {};
window.ED.config[pluginName][key] = data;
window.ED.config = window.ED.config;
}
static getData(pluginName, key) {
return this.loadData(pluginName, key);
}
static setData(pluginName, key, data) {
this.saveData(pluginName, key, data);
}
static getInternalInstance(node) {
if (!(node instanceof window.jQuery) && !(node instanceof Element)) return undefined;
if (node instanceof window.jQuery) node = node[0];
return node[Object.keys(node).find(k => k.startsWith("__reactInternalInstance"))];
}
static showToast(content, options = {}) {
if (!document.querySelector(".toasts")) {
const toastWrapper = document.createElement("div");
toastWrapper.classList.add("toasts");
const boundingElement = document.querySelector(".chat-3bRxxu form, #friends, .noChannel-Z1DQK7, .activityFeed-28jde9");
toastWrapper.style.setProperty("left", boundingElement ? boundingElement.getBoundingClientRect().left + "px" : "0px");
toastWrapper.style.setProperty("width", boundingElement ? boundingElement.offsetWidth + "px" : "100%");
toastWrapper.style.setProperty("bottom", (document.querySelector(".chat-3bRxxu form") ? document.querySelector(".chat-3bRxxu form").offsetHeight : 80) + "px");
document.querySelector("." + this.findModule('app').app).appendChild(toastWrapper);
}
const {type = "", icon = true, timeout = 3000} = options;
const toastElem = document.createElement("div");
toastElem.classList.add("toast");
if (type) toastElem.classList.add("toast-" + type);
if (type && icon) toastElem.classList.add("icon");
toastElem.innerText = content;
document.querySelector(".toasts").appendChild(toastElem);
setTimeout(() => {
toastElem.classList.add("closing");
setTimeout(() => {
toastElem.remove();
if (!document.querySelectorAll(".toasts .toast").length) document.querySelector(".toasts").remove();
}, 300);
}, timeout);
}
static findModule(filter, silent = true) {
const moduleName = typeof filter === 'string' ? filter : null;
for (const i in window.req.c) {
if (window.req.c.hasOwnProperty(i)) {
const m = window.req.c[i].exports;
if (m && m.__esModule && m.default && (moduleName ? m.default[moduleName] : filter(m.default))) return m.default;
if (m && (moduleName ? m[moduleName] : filter(m))) return m;
}
}
if (!silent) c.warn(`Could not find module ${module}.`, {name: 'Modules', color: 'black'})
return null;
}
static findRawModule(filter, silent = true) {
const moduleName = typeof filter === 'string' ? filter : null;
for (const i in window.req.c) {
if (window.req.c.hasOwnProperty(i)) {
const m = window.req.c[i].exports;
if (m && m.__esModule && m.default && (moduleName ? m.default[moduleName] : filter(m.default)))
return window.req.c[i];
if (m && (moduleName ? m[moduleName] : filter(m)))
return window.req.c[i];
}
}
if (!silent) c.warn(`Could not find module ${module}.`, {name: 'Modules', color: 'black'})
return null;
}
static findAllModules(filter) {
const moduleName = typeof filter === 'string' ? filter : null;
const modules = [];
for (const i in window.req.c) {
if (window.req.c.hasOwnProperty(i)) {
const m = window.req.c[i].exports;
if (m && m.__esModule && m.default && (moduleName ? m.default[moduleName] : filter(m.default))) modules.push(m.default);
else if (m && (moduleName ? m[moduleName] : filter(m))) modules.push(m);
}
}
return modules;
}
static findModuleByProps(...props) {
return this.findModule(module => props.every(prop => module[prop] !== undefined));
}
static findModuleByDisplayName(name) {
return this.findModule(module => module.displayName === name);
}
static monkeyPatch(what, methodName, options) {
if (typeof options === 'function') {
const newOptions = {instead: options, silent: true};
options = newOptions;
}
const {before, after, instead, once = false, silent = false, force = false} = options;
const displayName = options.displayName || what.displayName || what.name || what.constructor ? (what.constructor.displayName || what.constructor.name) : null;
if (!silent) console.log(`%c[EnhancedDiscord] %c[Modules]`, 'color: red;', `color: black;`, `Patched ${methodName} in module ${displayName || '<unknown>'}:`, what); // eslint-disable-line no-console
if (!what[methodName]) {
if (force) what[methodName] = function() {};
else return console.warn(`%c[EnhancedDiscord] %c[Modules]`, 'color: red;', `color: black;`, `Method ${methodName} doesn't exist in module ${displayName || '<unknown>'}`, what); // eslint-disable-line no-console
}
const origMethod = what[methodName];
const cancel = () => {
if (!silent) console.log(`%c[EnhancedDiscord] %c[Modules]`, 'color: red;', `color: black;`, `Unpatched ${methodName} in module ${displayName || '<unknown>'}:`, what); // 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 = EDApi.suppressErrors(instead, "`instead` callback of " + what[methodName].displayName)(data);
if (tempRet !== undefined) data.returnValue = tempRet;
}
else {
if (before) EDApi.suppressErrors(before, "`before` callback of " + what[methodName].displayName)(data);
data.callOriginalMethod();
if (after) EDApi.suppressErrors(after, "`after` callback of " + what[methodName].displayName)(data);
}
if (once) cancel();
return data.returnValue;
};
what[methodName].__monkeyPatched = true;
what[methodName].displayName = "patched " + (what[methodName].displayName || methodName);
what[methodName].unpatch = cancel;
return cancel;
}
static testJSON(data) {
try {
JSON.parse(data);
return true;
}
catch (err) {
return false;
}
}
static suppressErrors(method, description) {
return (...params) => {
try { return method(...params); }
catch (e) { console.error("Error occurred in " + description, e); }
};
}
static formatString(string, values) {
for (const val in values) {
string = string.replace(new RegExp(`\\{\\{${val}\\}\\}`, 'g'), values[val]);
}
return string;
}
static isPluginEnabled(name) {
const plugins = Object.values(window.ED.plugins);
const plugin = plugins.find(p => p.id == name || p.name == name);
if (!plugin) return false;
return !(plugin.settings.enabled === false);
}
static isThemeEnabled() {
return false;
}
static isSettingEnabled(id) {
return window.ED.config[id];
}
};

View file

@ -0,0 +1,346 @@
/*
Copyright 2020 AryToNeX
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
:root{
/* Here you can define properties for use with Glasscord. */
/* Global properties */
/* Main switch: disabling this will disable Glasscord. */
--glasscord-enable: true;
/* Tint: it's basically a global background color. */
--glasscord-tint: rgba(0, 0, 0, 0);
/* Windows-specific properties */
/* Blur mode. Available types: acrylic (strong blur), blurbehind (light blur), transparent */
--glasscord-win-blur: acrylic;
/* Acrylic mode makes some computers pretty unresponsive when resizing/moving the window.
Should we work around it? */
--glasscord-win-performance-mode: true;
/* macOS-specific properties */
/* Vibrancy mode */
--glasscord-macos-vibrancy: hud;
/* Linux-specific properties */
/* Should Glasscord send a request to your window compositor to blur behind Discord's window? */
/* It currently supports KWin only */
--glasscord-linux-blur: false;
}
.theme-dark{
--background-primary: transparent;
--background-primary-alt: #36393ff0;
--background-secondary: #2f313610;
--background-secondary-chat: #2f3136a0;
--background-secondary-alt: #292b2f00;
--background-tertiary: #20222570;
--background-logo: #36393fa0;
--deprecated-card-bg: #36393f4c;
--deprecated-store-bg: #36393f00;
--background-attachments: #2f313640;
--background-search-bar: #202225a0;
--background-switcher: #2f3136ff;
--background-chatbox: #20222550;
--background-lights-out: #000;
--background-pinned: #2f3136ff;
--background-profile-expand: #2f3136ff;
--text-lights-out: #f0f0f0;
--background-titlebar: #2f313650;
}
.theme-dark .scrollerThemed-2oenus.themedWithTrack-q8E3vB .scroller-2FKFPG::-webkit-scrollbar-track-piece {
background-color: transparent;
border: 4px solid transparent;
}
.theme-dark .scrollerThemed-2oenus.themedWithTrack-q8E3vB .scroller-2FKFPG::-webkit-scrollbar-thumb {
background-color: #20222550;
}
.theme-dark .container-1D34oG {
background-color: transparent;
}
.theme-dark .card-FDVird:before {
background-color: #33363c90;
}
.theme-dark .codeRedemptionRedirect-1wVR4b {
background-color: #2f313650;
border-color: #20222550;
}
.theme-dark .pageWrapper-1PgVDX {
background-color: transparent;
}
.theme-light {
--background-primary: transparent;
--background-primary-alt: #ffffffff;
--background-secondary: #f2f3f550;
--background-secondary-chat: #f2f3f5a0;
--background-secondary-alt: #ebedef00;
--background-tertiary: #e3e5e860;
--background-logo: #f2f3f5a0;
--deprecated-card-bg: #f8f9f94c;
--deprecated-store-bg: #f8f9f900;
--background-attachments: #f6f6f640;
--background-search-bar: #e3e5e8a0;
--background-switcher: #f2f3f5ff;
--background-chatbox: #e3e5e850;
--background-lights-out: #fff;
--background-pinned: #f2f3f5ff;
--background-profile-expand: #f2f3f5ff;
--text-lights-out: #202020;
--background-titlebar: #f2f3f550;
--interactive-muted: #95999d;
}
.theme-light .scrollerThemed-2oenus.themedWithTrack-q8E3vB .scroller-2FKFPG::-webkit-scrollbar-track-piece {
background-color: transparent;
border: 4px solid transparent;
}
.theme-light .scrollerThemed-2oenus.themedWithTrack-q8E3vB .scroller-2FKFPG::-webkit-scrollbar-thumb {
border-color: transparent;
background-color: #e3e5e850;
}
.theme-light .container-1D34oG {
background-color: transparent;
}
.theme-light .card-FDVird:before {
background-color: #f6f6f790;
}
.theme-light .codeRedemptionRedirect-1wVR4b {
background-color: #f6f6f750;
border-color: #dcddde50;
}
.theme-light .pageWrapper-1PgVDX {
background-color: transparent;
}
.withFrame-haYltI {
height: 18px;
margin-top: 0;
padding-top: 4px;
}
.unread-3zKkbm {
height: 4px;
width: 4px;
margin-top: -2px;
border-radius: 2px 2px 2px 2px;
}
.jumpToPresentBar-9P20AM {
border-radius: 8px;
padding-bottom: 0;
}
.platform-win .sidebar-2K8pFh {
border-radius: 0;
}
.messagesPopoutWrap-1MQ1bW {
background-color: var(--background-pinned);
}
.notice-2FJMB4 {
background-color: var(--background-secondary) !important;
border-radius: 0;
-webkit-box-shadow: none;
box-shadow: none;
}
.appMount-3lHmkl {
background-color: transparent;
}
.app-2rEoOp {
background-color: transparent;
}
.attachment-33OFj0 {
background-color: var(--background-attachments) !important;
}
.header-2o-2hj {
-webkit-box-shadow: none;
box-shadow: none;
}
.privateChannels-1nO12o {
background-color: transparent;
}
.root-SR8cQa {
background-color: var(--background-profile-expand);
}
.container-PNkimc {
background-color: transparent;
}
.inset-3sAvek {
background-color: var(--background-secondary) !important;
}
.container-1r6BKw.themed-ANHk51 {
background-color: var(--background-secondary);
}
.wrapper-1Rf91z {
background-color: var(--background-secondary);
}
.childWrapper-anI2G9 {
background-color: var(--background-logo);
}
.circleIconButton-jET_ig {
background-color: var(--background-logo);
}
.searchBar-3dMhjb {
background-color: var(--background-search-bar);
}
.searchBar-6Kv8R2 {
-webkit-box-shadow: none;
box-shadow: none;
}
.searchHeader-1l-wpR {
-webkit-box-shadow: none;
box-shadow: none;
}
.channelName-1QajIf {
background-color: transparent;
}
.searchResult-3pzFAB:before {
background-image: none;
}
.searchResult-3pzFAB:after {
background-image: none;
}
.perksModal-fSYqOq {
background-color: transparent !important;
}
.ctaBar-2UsjF2 {
background-color: var(--background-attachments) !important;
}
.tierBody-16Chc9 {
background-color: var(--background-secondary-chat) !important;
}
.perk-2WeBWW {
background-color: var(--background-attachments) !important;
}
.content-yTz4x3:before {
-webkit-box-shadow: none;
box-shadow: none;
}
.tabBody-3YRQ8W:before {
-webkit-box-shadow: none;
box-shadow: none;
}
.container-19hC9u:before {
-webkit-box-shadow: none;
box-shadow: none;
}
.container-xm7Ad0:before {
-webkit-box-shadow: none;
box-shadow: none;
}
.searchBar-6Kv8R2 .searchBarComponent-32dTOx {
background-color: var(--background-search-bar);
}
.scroller-1JbKMe {
background-color: transparent;
}
.scroller-2TZvBN {
background-color: transparent;
}
.quickswitcher-3JagVE {
background-color: var(--background-switcher);
}
.wrapper-2aW0bm {
background-color: var(--background-primary-alt);
}
.searchResultMessage-2VxO12.hit-NLlWXA {
background-color: var(--background-primary-alt);
}
.scroller-zPkAnE {
background-color: transparent;
}
.embedFull-2tM8-- {
background-color: var(--background-attachments);
}
.messagesWrapper-3lZDfY {
margin-bottom: 8px;
}
.panels-j1Uci_ {
background-color: transparent;
}
.titleBar-AC4pGV {
background-color: var(--background-titlebar);
}
.uploadArea-3QgLtW {
background-color: var(--background-lights-out);
}
.backdropWithLayer-3_uhz4 {
background-color: var(--background-lights-out) !important;
}
.backdrop-1wrmKB {
background-color: var(--background-lights-out) !important;
}
.downloadLink-1ywL9o {
color: var(--text-lights-out) !important;
}
.bg-AYqtMd {
-webkit-mask-image: linear-gradient(#000f, #0000) !important;
}
.scrollableContainer-2NUZem {
background-color: var(--background-chatbox);
}

View file

@ -0,0 +1,41 @@
const electron = require('electron');
const path = require('path');
electron.session.defaultSession.webRequest.onHeadersReceived(function(details, callback) {
if (!details.responseHeaders["content-security-policy-report-only"] && !details.responseHeaders["content-security-policy"]) return callback({cancel: false});
delete details.responseHeaders["content-security-policy-report-only"];
delete details.responseHeaders["content-security-policy"];
callback({cancel: false, responseHeaders: details.responseHeaders});
});
class BrowserWindow extends electron.BrowserWindow {
constructor(originalOptions) {
if (!originalOptions || !originalOptions.webPreferences || !originalOptions.title) return super(originalOptions);
const originalPreloadScript = originalOptions.webPreferences.preload;
// Make sure Node integration is enabled
originalOptions.webPreferences.nodeIntegration = true;
originalOptions.webPreferences.preload = path.join(process.env.injDir, 'dom_shit.js');
originalOptions.webPreferences.transparency = true;
super(originalOptions);
this.__preload = originalPreloadScript;
}
}
const electron_path = require.resolve('electron');
Object.assign(BrowserWindow, electron.BrowserWindow); // Assigns the new chrome-specific ones
if (electron.deprecate && electron.deprecate.promisify) {
const originalDeprecate = electron.deprecate.promisify; // Grab original deprecate promisify
electron.deprecate.promisify = (originalFunction) => originalFunction ? originalDeprecate(originalFunction) : () => void 0; // Override with falsey check
}
const newElectron = Object.assign({}, electron, {BrowserWindow});
// Tempfix for Injection breakage due to new version of Electron on Canary (Electron 7.x)
// Found by Zerebos (Zack Rauen)
delete require.cache[electron_path].exports;
// /TempFix
require.cache[electron_path].exports = newElectron;
//const browser_window_path = require.resolve(path.resolve(electron_path, '..', '..', 'browser-window.js'));
//require.cache[browser_window_path].exports = BrowserWindow;

View file

@ -0,0 +1,22 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnhancedDiscordUI", "EnhancedDiscordUI\EnhancedDiscordUI.csproj", "{3639AE05-14E6-43B2-9DDB-1A3F4F52657C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{3639AE05-14E6-43B2-9DDB-1A3F4F52657C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3639AE05-14E6-43B2-9DDB-1A3F4F52657C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3639AE05-14E6-43B2-9DDB-1A3F4F52657C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3639AE05-14E6-43B2-9DDB-1A3F4F52657C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.1"/>
</startup>
</configuration>

View file

@ -0,0 +1,138 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{3639AE05-14E6-43B2-9DDB-1A3F4F52657C}</ProjectGuid>
<OutputType>WinExe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>EnhancedDiscordUI</RootNamespace>
<AssemblyName>EnhancedDiscordUI</AssemblyName>
<TargetFrameworkVersion>v4.7.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup>
<ApplicationIcon>ed.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.IO.Compression.FileSystem" />
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Deployment" />
<Reference Include="System.Drawing" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Form1.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Form1.Designer.cs">
<DependentUpon>Form1.cs</DependentUpon>
</Compile>
<Compile Include="LogWriter.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<EmbeddedResource Include="Form1.resx">
<DependentUpon>Form1.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<SubType>Designer</SubType>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<None Include="Resources\discord_canary_16.png" />
</ItemGroup>
<ItemGroup>
<None Include="Resources\discord_stable_16.png" />
</ItemGroup>
<ItemGroup>
<None Include="Resources\discord_stable_32.png" />
</ItemGroup>
<ItemGroup>
<None Include="Resources\discord_stable_64.png" />
</ItemGroup>
<ItemGroup>
<None Include="Resources\discord_canary_32.png" />
</ItemGroup>
<ItemGroup>
<None Include="Resources\discord_canary_64.png" />
</ItemGroup>
<ItemGroup>
<None Include="Resources\discord_dev_16.png" />
</ItemGroup>
<ItemGroup>
<None Include="Resources\discord_dev_32.png" />
</ItemGroup>
<ItemGroup>
<None Include="Resources\discord_dev_64.png" />
</ItemGroup>
<ItemGroup>
<None Include="Resources\ed_og.png" />
</ItemGroup>
<ItemGroup>
<Content Include="ed.ico" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
</Target>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View file

@ -0,0 +1,381 @@
namespace EnhancedDiscordUI
{
partial class EDInstaller
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(EDInstaller));
this.InstallButton = new System.Windows.Forms.Button();
this.Title = new System.Windows.Forms.Label();
this.UninstallButton = new System.Windows.Forms.Button();
this.UpdateButton = new System.Windows.Forms.Button();
this.InstallProgress = new System.Windows.Forms.ProgressBar();
this.StatusLabel2 = new System.Windows.Forms.Label();
this.StatusLabel = new System.Windows.Forms.Label();
this.StatusCloseButton = new System.Windows.Forms.Button();
this.StatusText = new System.Windows.Forms.TextBox();
this.toolTip1 = new System.Windows.Forms.ToolTip(this.components);
this.DevButton = new System.Windows.Forms.Button();
this.CanaryButton = new System.Windows.Forms.Button();
this.PTBButton = new System.Windows.Forms.Button();
this.StableButton = new System.Windows.Forms.Button();
this.ReinjectButton = new System.Windows.Forms.Button();
this.pictureBox1 = new System.Windows.Forms.PictureBox();
this.OpenFolderButton = new System.Windows.Forms.Button();
this.BetaRadio = new System.Windows.Forms.RadioButton();
((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit();
this.SuspendLayout();
//
// InstallButton
//
this.InstallButton.Anchor = System.Windows.Forms.AnchorStyles.Top;
this.InstallButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.InstallButton.Font = new System.Drawing.Font("Segoe UI", 7.8F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.InstallButton.ForeColor = System.Drawing.Color.WhiteSmoke;
this.InstallButton.Location = new System.Drawing.Point(155, 127);
this.InstallButton.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.InstallButton.Name = "InstallButton";
this.InstallButton.Size = new System.Drawing.Size(131, 30);
this.InstallButton.TabIndex = 0;
this.InstallButton.Text = "Install";
this.toolTip1.SetToolTip(this.InstallButton, "Downloads and injects ED into your Discord client.");
this.InstallButton.UseVisualStyleBackColor = true;
this.InstallButton.Click += new System.EventHandler(this.InstallButton_Click);
//
// Title
//
this.Title.AutoSize = true;
this.Title.Font = new System.Drawing.Font("Segoe UI Semibold", 18F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.Title.ForeColor = System.Drawing.Color.WhiteSmoke;
this.Title.Location = new System.Drawing.Point(131, 11);
this.Title.Name = "Title";
this.Title.Size = new System.Drawing.Size(255, 41);
this.Title.TabIndex = 1;
this.Title.Text = "EnhancedDiscord";
//
// UninstallButton
//
this.UninstallButton.Anchor = System.Windows.Forms.AnchorStyles.Top;
this.UninstallButton.Enabled = false;
this.UninstallButton.FlatAppearance.BorderColor = System.Drawing.Color.WhiteSmoke;
this.UninstallButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.UninstallButton.Font = new System.Drawing.Font("Segoe UI", 7.8F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.UninstallButton.ForeColor = System.Drawing.Color.WhiteSmoke;
this.UninstallButton.Location = new System.Drawing.Point(155, 162);
this.UninstallButton.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.UninstallButton.Name = "UninstallButton";
this.UninstallButton.Size = new System.Drawing.Size(131, 30);
this.UninstallButton.TabIndex = 2;
this.UninstallButton.Text = "Uninstall";
this.toolTip1.SetToolTip(this.UninstallButton, "Uninjects ED and prompts you to delete ED\'s files.");
this.UninstallButton.UseVisualStyleBackColor = true;
this.UninstallButton.Click += new System.EventHandler(this.UninstallButton_Click);
//
// UpdateButton
//
this.UpdateButton.Anchor = System.Windows.Forms.AnchorStyles.Top;
this.UpdateButton.Enabled = false;
this.UpdateButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.UpdateButton.Font = new System.Drawing.Font("Segoe UI", 7.8F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.UpdateButton.ForeColor = System.Drawing.Color.WhiteSmoke;
this.UpdateButton.Location = new System.Drawing.Point(87, 197);
this.UpdateButton.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.UpdateButton.Name = "UpdateButton";
this.UpdateButton.Size = new System.Drawing.Size(131, 30);
this.UpdateButton.TabIndex = 3;
this.UpdateButton.Text = "Update";
this.toolTip1.SetToolTip(this.UpdateButton, "Replaces the ED files with the most recent ones.");
this.UpdateButton.UseVisualStyleBackColor = true;
this.UpdateButton.Click += new System.EventHandler(this.UpdateButton_Click);
//
// InstallProgress
//
this.InstallProgress.Location = new System.Drawing.Point(12, 218);
this.InstallProgress.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.InstallProgress.Name = "InstallProgress";
this.InstallProgress.Size = new System.Drawing.Size(415, 23);
this.InstallProgress.Style = System.Windows.Forms.ProgressBarStyle.Continuous;
this.InstallProgress.TabIndex = 5;
this.InstallProgress.Visible = false;
//
// StatusLabel2
//
this.StatusLabel2.AutoSize = true;
this.StatusLabel2.Font = new System.Drawing.Font("Segoe UI", 7.8F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.StatusLabel2.ForeColor = System.Drawing.Color.WhiteSmoke;
this.StatusLabel2.Location = new System.Drawing.Point(9, 190);
this.StatusLabel2.Name = "StatusLabel2";
this.StatusLabel2.Size = new System.Drawing.Size(89, 19);
this.StatusLabel2.TabIndex = 8;
this.StatusLabel2.Text = "Lorem ipsum";
this.StatusLabel2.Visible = false;
//
// StatusLabel
//
this.StatusLabel.AutoSize = true;
this.StatusLabel.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.StatusLabel.ForeColor = System.Drawing.Color.WhiteSmoke;
this.StatusLabel.Location = new System.Drawing.Point(8, 167);
this.StatusLabel.Name = "StatusLabel";
this.StatusLabel.Size = new System.Drawing.Size(165, 28);
this.StatusLabel.TabIndex = 9;
this.StatusLabel.Text = "Installation failed.";
this.StatusLabel.Visible = false;
//
// StatusCloseButton
//
this.StatusCloseButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.StatusCloseButton.Font = new System.Drawing.Font("Segoe UI", 7.8F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.StatusCloseButton.ForeColor = System.Drawing.Color.WhiteSmoke;
this.StatusCloseButton.Location = new System.Drawing.Point(251, 128);
this.StatusCloseButton.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.StatusCloseButton.Name = "StatusCloseButton";
this.StatusCloseButton.Size = new System.Drawing.Size(64, 30);
this.StatusCloseButton.TabIndex = 10;
this.StatusCloseButton.Text = "Close";
this.StatusCloseButton.UseVisualStyleBackColor = true;
this.StatusCloseButton.Visible = false;
this.StatusCloseButton.Click += new System.EventHandler(this.StatusCloseButton_Click);
//
// StatusText
//
this.StatusText.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(31)))), ((int)(((byte)(36)))), ((int)(((byte)(36)))));
this.StatusText.BorderStyle = System.Windows.Forms.BorderStyle.None;
this.StatusText.Font = new System.Drawing.Font("Segoe UI", 10.2F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.StatusText.ForeColor = System.Drawing.Color.WhiteSmoke;
this.StatusText.Location = new System.Drawing.Point(11, 68);
this.StatusText.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.StatusText.Multiline = true;
this.StatusText.Name = "StatusText";
this.StatusText.ReadOnly = true;
this.StatusText.Size = new System.Drawing.Size(416, 26);
this.StatusText.TabIndex = 11;
this.StatusText.Text = "Make sure to launch Discord before installing!";
this.StatusText.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
//
// DevButton
//
this.DevButton.Anchor = System.Windows.Forms.AnchorStyles.Top;
this.DevButton.FlatAppearance.BorderSize = 2;
this.DevButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.DevButton.Font = new System.Drawing.Font("Segoe UI", 7.8F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.DevButton.ForeColor = System.Drawing.Color.White;
this.DevButton.Image = global::EnhancedDiscordUI.Properties.Resources.discord_dev_64;
this.DevButton.ImageAlign = System.Drawing.ContentAlignment.TopCenter;
this.DevButton.Location = new System.Drawing.Point(320, 98);
this.DevButton.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.DevButton.Name = "DevButton";
this.DevButton.RightToLeft = System.Windows.Forms.RightToLeft.Yes;
this.DevButton.Size = new System.Drawing.Size(91, 110);
this.DevButton.TabIndex = 15;
this.DevButton.Text = "Dev";
this.DevButton.TextAlign = System.Drawing.ContentAlignment.BottomCenter;
this.toolTip1.SetToolTip(this.DevButton, "Discord Development (aka Local.)");
this.DevButton.UseVisualStyleBackColor = true;
this.DevButton.Visible = false;
this.DevButton.Click += new System.EventHandler(this.DevButton_Click);
//
// CanaryButton
//
this.CanaryButton.Anchor = System.Windows.Forms.AnchorStyles.Top;
this.CanaryButton.FlatAppearance.BorderSize = 2;
this.CanaryButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.CanaryButton.Font = new System.Drawing.Font("Segoe UI", 7.8F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.CanaryButton.ForeColor = System.Drawing.Color.Gold;
this.CanaryButton.Image = global::EnhancedDiscordUI.Properties.Resources.discord_canary_64;
this.CanaryButton.ImageAlign = System.Drawing.ContentAlignment.TopCenter;
this.CanaryButton.Location = new System.Drawing.Point(224, 98);
this.CanaryButton.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.CanaryButton.Name = "CanaryButton";
this.CanaryButton.RightToLeft = System.Windows.Forms.RightToLeft.Yes;
this.CanaryButton.Size = new System.Drawing.Size(91, 110);
this.CanaryButton.TabIndex = 14;
this.CanaryButton.Text = "Canary";
this.CanaryButton.TextAlign = System.Drawing.ContentAlignment.BottomCenter;
this.toolTip1.SetToolTip(this.CanaryButton, "Discord Canary");
this.CanaryButton.UseVisualStyleBackColor = true;
this.CanaryButton.Visible = false;
this.CanaryButton.Click += new System.EventHandler(this.CanaryButton_Click);
//
// PTBButton
//
this.PTBButton.Anchor = System.Windows.Forms.AnchorStyles.Top;
this.PTBButton.FlatAppearance.BorderSize = 2;
this.PTBButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.PTBButton.Font = new System.Drawing.Font("Segoe UI", 7.8F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.PTBButton.ForeColor = System.Drawing.Color.SteelBlue;
this.PTBButton.Image = global::EnhancedDiscordUI.Properties.Resources.discord_stable_64;
this.PTBButton.ImageAlign = System.Drawing.ContentAlignment.TopCenter;
this.PTBButton.Location = new System.Drawing.Point(128, 98);
this.PTBButton.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.PTBButton.Name = "PTBButton";
this.PTBButton.RightToLeft = System.Windows.Forms.RightToLeft.Yes;
this.PTBButton.Size = new System.Drawing.Size(91, 110);
this.PTBButton.TabIndex = 13;
this.PTBButton.Text = "PTB";
this.PTBButton.TextAlign = System.Drawing.ContentAlignment.BottomCenter;
this.toolTip1.SetToolTip(this.PTBButton, "Discord PTB (Public Test Build)");
this.PTBButton.UseVisualStyleBackColor = true;
this.PTBButton.Visible = false;
this.PTBButton.Click += new System.EventHandler(this.PTBButton_Click);
//
// StableButton
//
this.StableButton.Anchor = System.Windows.Forms.AnchorStyles.Top;
this.StableButton.FlatAppearance.BorderSize = 2;
this.StableButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.StableButton.Font = new System.Drawing.Font("Segoe UI", 7.8F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.StableButton.ForeColor = System.Drawing.Color.SteelBlue;
this.StableButton.Image = global::EnhancedDiscordUI.Properties.Resources.discord_stable_64;
this.StableButton.ImageAlign = System.Drawing.ContentAlignment.TopCenter;
this.StableButton.Location = new System.Drawing.Point(32, 98);
this.StableButton.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.StableButton.Name = "StableButton";
this.StableButton.RightToLeft = System.Windows.Forms.RightToLeft.Yes;
this.StableButton.Size = new System.Drawing.Size(91, 110);
this.StableButton.TabIndex = 12;
this.StableButton.Text = "Stable";
this.StableButton.TextAlign = System.Drawing.ContentAlignment.BottomCenter;
this.toolTip1.SetToolTip(this.StableButton, "Normal version of Discord.");
this.StableButton.UseVisualStyleBackColor = true;
this.StableButton.Visible = false;
this.StableButton.Click += new System.EventHandler(this.StableButton_Click);
//
// ReinjectButton
//
this.ReinjectButton.Anchor = System.Windows.Forms.AnchorStyles.Top;
this.ReinjectButton.Enabled = false;
this.ReinjectButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.ReinjectButton.Font = new System.Drawing.Font("Segoe UI", 7.8F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.ReinjectButton.ForeColor = System.Drawing.Color.WhiteSmoke;
this.ReinjectButton.Location = new System.Drawing.Point(225, 197);
this.ReinjectButton.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.ReinjectButton.Name = "ReinjectButton";
this.ReinjectButton.Size = new System.Drawing.Size(131, 30);
this.ReinjectButton.TabIndex = 17;
this.ReinjectButton.Text = "Reinject";
this.toolTip1.SetToolTip(this.ReinjectButton, "Reinjects without changing your ED folder; useful after Discord updates.");
this.ReinjectButton.UseVisualStyleBackColor = true;
this.ReinjectButton.Click += new System.EventHandler(this.ReinjectButton_Click);
//
// pictureBox1
//
this.pictureBox1.Image = global::EnhancedDiscordUI.Properties.Resources.ed_og;
this.pictureBox1.Location = new System.Drawing.Point(41, 12);
this.pictureBox1.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.pictureBox1.Name = "pictureBox1";
this.pictureBox1.Size = new System.Drawing.Size(87, 50);
this.pictureBox1.SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage;
this.pictureBox1.TabIndex = 4;
this.pictureBox1.TabStop = false;
//
// OpenFolderButton
//
this.OpenFolderButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.OpenFolderButton.Font = new System.Drawing.Font("Segoe UI", 7.8F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.OpenFolderButton.ForeColor = System.Drawing.Color.WhiteSmoke;
this.OpenFolderButton.Location = new System.Drawing.Point(113, 128);
this.OpenFolderButton.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.OpenFolderButton.Name = "OpenFolderButton";
this.OpenFolderButton.Size = new System.Drawing.Size(125, 30);
this.OpenFolderButton.TabIndex = 16;
this.OpenFolderButton.Text = "Open Folder";
this.OpenFolderButton.UseVisualStyleBackColor = true;
this.OpenFolderButton.Visible = false;
this.OpenFolderButton.Click += new System.EventHandler(this.OpenFolderButton_Click);
//
// BetaRadio
//
this.BetaRadio.AutoSize = true;
this.BetaRadio.Font = new System.Drawing.Font("Segoe UI", 10.2F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.BetaRadio.ForeColor = System.Drawing.SystemColors.ControlLightLight;
this.BetaRadio.Location = new System.Drawing.Point(115, 95);
this.BetaRadio.Name = "BetaRadio";
this.BetaRadio.Size = new System.Drawing.Size(200, 27);
this.BetaRadio.TabIndex = 18;
this.BetaRadio.TabStop = true;
this.BetaRadio.Text = "Opt-in to beta version";
this.BetaRadio.UseVisualStyleBackColor = true;
//
// EDInstaller
//
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(31)))), ((int)(((byte)(36)))), ((int)(((byte)(36)))));
this.ClientSize = new System.Drawing.Size(439, 254);
this.Controls.Add(this.BetaRadio);
this.Controls.Add(this.ReinjectButton);
this.Controls.Add(this.OpenFolderButton);
this.Controls.Add(this.UninstallButton);
this.Controls.Add(this.StatusCloseButton);
this.Controls.Add(this.StatusLabel);
this.Controls.Add(this.StatusLabel2);
this.Controls.Add(this.InstallProgress);
this.Controls.Add(this.pictureBox1);
this.Controls.Add(this.UpdateButton);
this.Controls.Add(this.Title);
this.Controls.Add(this.InstallButton);
this.Controls.Add(this.StatusText);
this.Controls.Add(this.StableButton);
this.Controls.Add(this.DevButton);
this.Controls.Add(this.CanaryButton);
this.Controls.Add(this.PTBButton);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.Name = "EDInstaller";
this.Text = "EnhancedDiscord Installer";
((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Button InstallButton;
private System.Windows.Forms.Label Title;
private System.Windows.Forms.Button UninstallButton;
private System.Windows.Forms.Button UpdateButton;
private System.Windows.Forms.PictureBox pictureBox1;
private System.Windows.Forms.ProgressBar InstallProgress;
private System.Windows.Forms.Label StatusLabel2;
private System.Windows.Forms.Label StatusLabel;
private System.Windows.Forms.Button StatusCloseButton;
private System.Windows.Forms.TextBox StatusText;
private System.Windows.Forms.ToolTip toolTip1;
private System.Windows.Forms.Button StableButton;
private System.Windows.Forms.Button PTBButton;
private System.Windows.Forms.Button CanaryButton;
private System.Windows.Forms.Button DevButton;
private System.Windows.Forms.Button OpenFolderButton;
private System.Windows.Forms.Button ReinjectButton;
private System.Windows.Forms.RadioButton BetaRadio;
}
}

View file

@ -0,0 +1,750 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.IO.Compression;
using System.Runtime.InteropServices;
namespace EnhancedDiscordUI
{
public partial class EDInstaller : Form
{
private Process stableProcess;
private Process ptbProcess;
private Process canaryProcess;
private Process devProcess;
private string operation = "INSTALL";
private string platform;
private string branch = "master";
public EDInstaller()
{
Logger.MakeDivider();
Logger.Log("Starting...");
InitializeComponent();
if (Directory.Exists("./EnhancedDiscord"))
{
UninstallButton.Enabled = true;
UpdateButton.Enabled = true;
ReinjectButton.Enabled = true;
}
}
private void endInstallation(string reason, bool failed)
{
InstallProgress.Value = 100;
BetaRadio.Hide();
InstallButton.Hide();
UninstallButton.Hide();
UpdateButton.Hide();
ReinjectButton.Hide();
StatusText.Hide();
StatusLabel.Show();
StatusLabel.Text = operation == "UPDATE" ? "Update " + (failed ? "failed" : "complete") : (operation == "UNINSTALL" ? "Unin" : "In") + "stallation " + (failed ? " failed." : "completed!");
StatusLabel.ForeColor = failed ? Color.Red : Color.Lime;
StatusLabel2.Show();
StatusLabel2.Text = reason;
StatusCloseButton.Show();
if (platform != "Linux")
{
OpenFolderButton.Show();
}
}
private void InstallButton_Click(object sender, EventArgs e)
{
if (BetaRadio.Checked)
{
branch = "beta";
}
BetaRadio.Hide();
InstallButton.Hide();
UninstallButton.Hide();
UpdateButton.Hide();
ReinjectButton.Hide();
StatusText.Show();
InstallProgress.Show();
StatusText.Text = "Finding Discord processes...";
Process[] stable = Process.GetProcessesByName("Discord");
Process[] canary = Process.GetProcessesByName("DiscordCanary");
Process[] ptb = Process.GetProcessesByName("DiscordPtb");
Process[] dev = Process.GetProcessesByName("DiscordDevelopment");
List<Process> discordProcesses = new List<Process>();
discordProcesses.AddRange(stable);
discordProcesses.AddRange(canary);
discordProcesses.AddRange(ptb);
discordProcesses.AddRange(dev);
if (discordProcesses.Count == 0)
{
endInstallation("No Discord processes found. Please open Discord and try again.", true); return;
}
List<Process> uniqueProcesses = new List<Process>();
// First look for processes with unique filenames that have a title
for (int i = 0; i < discordProcesses.Count; i++)
{
bool isUnique = true;
for (int j = 0; j < uniqueProcesses.Count; j++)
{
if (uniqueProcesses[j].MainModule.FileName.Equals(discordProcesses[i].MainModule.FileName))
{
isUnique = false; break;
}
}
if (!isUnique || discordProcesses[i].MainWindowTitle == "" || discordProcesses[i].MainWindowTitle.StartsWith("Developer Tools")) continue;
uniqueProcesses.Add(discordProcesses[i]);
}
// Then look for all processes with unique filenames
for (int i = 0; i < discordProcesses.Count; i++)
{
bool isUnique = true;
for (int j = 0; j < uniqueProcesses.Count; j++)
{
if (uniqueProcesses[j].MainModule.FileName.Equals(discordProcesses[i].MainModule.FileName))
{
isUnique = false; break;
}
}
if (!isUnique) continue;
uniqueProcesses.Add(discordProcesses[i]);
}
StatusText.Text = "Found " + uniqueProcesses.Count + " Discord process" + (uniqueProcesses.Count == 1 ? "" : "es") + ".";
InstallProgress.Value = 10;
Process finalProcess = uniqueProcesses[0];
if (uniqueProcesses.Count > 1)
{
// Enable selection buttons
List<Button> clients = new List<Button>();
for (int i = 0; i < uniqueProcesses.Count; i++)
{
if (canary.Contains(uniqueProcesses[i]))
{
CanaryButton.Show();
clients.Add(CanaryButton);
canaryProcess = uniqueProcesses[i];
}
else if (ptb.Contains(uniqueProcesses[i]))
{
PTBButton.Show();
clients.Add(PTBButton);
ptbProcess = uniqueProcesses[i];
}
else if (dev.Contains(uniqueProcesses[i]))
{
DevButton.Show();
clients.Add(DevButton);
devProcess = uniqueProcesses[i];
}
else if (stable.Contains(uniqueProcesses[i]))
{
StableButton.Show();
clients.Add(StableButton);
stableProcess = uniqueProcesses[i];
}
}
// position buttons correctly
if (clients.Count == 3)
{
clients[0].Left = 55;
clients[1].Left = 131;
clients[2].Left = 207;
}
else if (clients.Count == 2)
{
clients[0].Left = 88;
clients[1].Left = 164;
}
return; // stuff continues w/ button events
}
if (operation == "UPDATE")
{
continueUpdate(finalProcess);
}
else
{
continueInstall(finalProcess);
}
}
private void StableButton_Click(object sender, EventArgs e)
{
continueInstall(stableProcess);
}
private void PTBButton_Click(object sender, EventArgs e)
{
continueInstall(ptbProcess);
}
private void CanaryButton_Click(object sender, EventArgs e)
{
continueInstall(canaryProcess);
}
private void DevButton_Click(object sender, EventArgs e)
{
continueInstall(devProcess);
}
private async void continueInstall(Process proc)
{
string path = proc.MainModule.FileName;
string release = "discord";
if (path.Contains("DiscordPTB"))
{
release = "discordptb";
}
else if (path.Contains("DiscordCanary"))
{
release = "discordcanary";
}
else if (path.Contains("DiscordDevelopment"))
{
release = "discorddevelopment";
}
Logger.Log("Using release " + release);
StableButton.Hide();
PTBButton.Hide();
CanaryButton.Hide();
DevButton.Hide();
StatusText.Text = "Injecting...";
InstallProgress.Value = 20;
Logger.Log(StatusText.Text);
string dLocation = Path.GetDirectoryName(path);
platform = "";
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
platform = "Windows";
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
platform = "Linux";
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
platform = "Mac";
}
StatusText.Text = "Detected platform: " + platform + " | Discord release: " + release;
Logger.Log(StatusText.Text);
string basePath;
string appVersion = dLocation.Substring(dLocation.IndexOf("app-") + 4);
if (platform == "Windows")
{
basePath = Path.GetDirectoryName(Path.GetDirectoryName(Path.GetDirectoryName(dLocation)));
basePath = Path.Combine(basePath, "Roaming");
}
else if (platform == "Mac")
{
basePath = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
basePath = Path.Combine(basePath, "Library", "Application Support");
}
else
{
basePath = Environment.GetEnvironmentVariable("XDG_CONFIG_HOME");
if (basePath == null)
{
basePath = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
basePath = Path.Combine(basePath, ".config");
}
}
string[] pathPieces = { basePath, release, appVersion, "modules", "discord_desktop_core", "index.js" };
// the base "appdata" folder ^ ^ ^
// i.e. "discord" or "discordcanary" -' |
// i.e. "0.0.300" or "0.0.204" ---------------'
string targetPath = Path.Combine(pathPieces);
if (targetPath == "" || !File.Exists(targetPath))
{
Logger.Error("Could not fine injection file with basepath " + basePath);
endInstallation("Could not find injection file.", true); return;
}
if (operation == "UNINSTALL")
{
continueUninstall(proc, targetPath, platform); return;
}
string currentContents = File.ReadAllText(targetPath);
if (currentContents != "module.exports = require('./core.asar');")
{
StatusText.Text = "EnhancedDiscord was already injected. Reinjecting...";
Logger.Log(StatusText.Text);
}
InstallProgress.Value = 30;
string stuffToInject = Properties.Resources.injection;
string cd = Directory.GetCurrentDirectory() + "/EnhancedDiscord";
cd = cd.Replace("\\", "/").Replace("'", "\\'").Replace("/", "\\\\");
string newContents = "process.env.injDir = '" + cd + "';\n";
newContents += stuffToInject + "\nmodule.exports = require('./core.asar');";
try
{
File.WriteAllText(targetPath, newContents);
}
catch (Exception e)
{
Logger.Error("Failed to write to injection file. " + e.Message);
endInstallation("Failed to write to injection file.", true); return;
}
if (operation == "REINJECT")
{
try
{
proc.Kill();
startDetached(path, null);
endInstallation("Successfully reinjected.", false);
}
catch (Exception e)
{
Logger.Error("Failed to restart Discord; do this manually. " + e.Message);
endInstallation("Failed to restart Discord; do this manually.", false);
}
return;
}
InstallProgress.Value = 40;
StatusText.Text = "Successfully injected. Downloading ED...";
Logger.Log(StatusText.Text);
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 | SecurityProtocolType.Ssl3;
string zipLink = Properties.Resources.zipLink + branch;
WebClient wc = new WebClient();
try
{
await wc.DownloadFileTaskAsync(new Uri(zipLink), "./ED_master.zip");
}
catch (Exception e)
{
Logger.Error("Failed to download ED files. " + e.Message);
endInstallation("Failed to download ED files.", true); return;
}
InstallProgress.Value = 60;
StatusText.Text = "Successfully downloaded. Extracting...";
Logger.Log(StatusText.Text);
if (Directory.Exists("./EnhancedDiscord") || Directory.Exists("./EnhancedDiscord-" + branch))
{
DialogResult confirmResult = MessageBox.Show("ED folder already exists. Overwrite it?", "EnhancedDiscord - Confirm Overwrite", MessageBoxButtons.YesNo);
if (confirmResult == DialogResult.No)
{
Logger.Error("Not replacing old ED files; restart Discord manually.");
endInstallation("Not replacing old ED files; restart Discord manually.", false); return;
}
try
{
if (Directory.Exists("./EnhancedDiscord"))
{
Directory.Delete("./EnhancedDiscord", true);
}
if (Directory.Exists("./EnhancedDiscord-" + branch))
{
Directory.Delete("./EnhancedDiscord-" + branch, true);
}
}
catch (Exception e)
{
StatusText.Text = "Error deleting old folders.";
Logger.Error(StatusText.Text + " " + e.Message);
}
}
try
{
ZipFile.ExtractToDirectory("./ED_master.zip", "./");
}
catch (Exception e)
{
Logger.Error("Failed to extract zip file. " + e.Message);
endInstallation("Failed to extract zip file.", true); return;
}
InstallProgress.Value = 70;
StatusText.Text = "Finished extracting zip. Cleaning up...";
Logger.Log(StatusText.Text);
try
{
Directory.Move("./EnhancedDiscord-" + branch, "./EnhancedDiscord");
}
catch (Exception e)
{
Logger.Error("Failed to rename extracted folder. " + e.Message);
endInstallation("Failed to rename extracted folder.", true); return;
}
string[] garbage = new string[] { "./EnhancedDiscord/README.md", "./EnhancedDiscord/plugins.md", "./EnhancedDiscord/advanced_installation.md", "./EnhancedDiscord/.gitignore", "./ED_master.zip", "./EnhancedDiscord/installer", "./EnhancedDiscord/installer_cmdline" };
foreach (string filePath in garbage)
{
try
{
if (File.Exists(filePath))
{
File.Delete(filePath);
}
if (Directory.Exists(filePath))
{
Directory.Delete(filePath, true);
}
}
catch (Exception e)
{
Logger.Error("Error during cleanup. " + e.Message);
}
}
InstallProgress.Value = 80;
StatusText.Text = "Finished cleaning up. Creating config.json...";
Logger.Log(StatusText.Text);
bool configSuccess = true;
if (!File.Exists("./EnhancedDiscord/config.json"))
{
try
{
File.WriteAllText("./EnhancedDiscord/config.json", "{}");
}
catch (Exception e)
{
Logger.Error("Failed to write config.json. " + e.Message);
configSuccess = false;
//StatusText.Text = "Failed to create config.json.";
}
}
InstallProgress.Value = 90;
StatusText.Text = (configSuccess ? File.Exists("./EnhancedDiscord/config.json") ? "Found" : "Created" : "Failed to create") + " config.json. Relaunching Discord...";
if (configSuccess) Logger.Log(StatusText.Text);
else Logger.Error(StatusText.Text);
try
{
proc.Kill();
startDetached(path, null);
}
catch (Exception e)
{
StatusText.Text = "Failed to restart Discord; do this manually.";
Logger.Error(StatusText.Text + " " + e.Message);
}
InstallProgress.Value = 100;
endInstallation("Finished cleaning up.", false);
return;
}
private void continueUninstall(Process proc, string targetPath, string platform)
{
StatusText.Text = "Uninstalling...";
InstallProgress.Value = 30;
Logger.Log(StatusText.Text);
string path = proc.MainModule.FileName;
try
{
File.WriteAllText(targetPath, "module.exports = require('./core.asar');");
}
catch (Exception e)
{
Logger.Error("Failed to write to injection file. " + e.Message);
endInstallation("Failed to write to injection file.", true); return;
}
StatusText.Text = "Successfully uninjected. Deleting old files...";
InstallProgress.Value = 60;
Logger.Log(StatusText.Text);
if (Directory.Exists("./EnhancedDiscord"))
{
DialogResult confirmResult = MessageBox.Show("Would you like to keep your EnhancedDiscord folder?", "EnhancedDiscord - Confirm Delete", MessageBoxButtons.YesNo);
if (confirmResult == DialogResult.No)
{
bool success = true;
StatusText.Text = "Killing Discord process...";
Logger.Log(StatusText.Text);
try
{
proc.Kill();
}
catch (Exception e)
{
success = false;
StatusText.Text = "Failed to kill Discord process. Aborted deletion of ED directory.";
Logger.Error(StatusText.Text + " " + e.Message);
}
if (success)
{
try
{
Directory.Delete("./EnhancedDiscord", true);
Directory.Delete("./EnhancedDiscord", false);
}
catch (Exception e)
{
StatusText.Text = "Failed to delete EnhancedDiscord directory.";
Logger.Error(StatusText.Text + " " + e.Message);
}
try
{
startDetached(path, null);
}
catch (Exception e)
{
Logger.Error("Uninjected successfully. Failed to restart Discord; do this manually. " + e.Message);
endInstallation("Uninjected successfully. Failed to restart Discord; do this manually.", false); return;
}
}
}
else
{
try
{
proc.Kill();
startDetached(path, null);
}
catch (Exception e)
{
Logger.Error("Uninjected successfully. Failed to restart Discord; do this manually. " + e.Message);
endInstallation("Uninjected successfully. Failed to restart Discord; do this manually.", false); return;
}
}
}
else
{
try
{
proc.Kill();
startDetached(path, null);
}
catch (Exception e)
{
Logger.Error("Uninjected successfully. Failed to restart Discord; do this manually. " + e.Message);
endInstallation("Uninjected successfully. Failed to restart Discord; do this manually.", false); return;
}
}
Logger.Log("Uninjected and cleaned up successfully.");
endInstallation("Uninjected and cleaned up successfully.", false); return;
}
private Process startDetached(string executablePath, string args)
{
if (platform == "Windows")
{
return Process.Start("cmd.exe", "/c start " + executablePath + (args == null ? "" : " " + args));
}
return Process.Start(executablePath, (args == null ? "" : args + " ") + "&"); // should work on Mac and Linux
}
private void StatusCloseButton_Click(object sender, EventArgs e)
{
Close();
}
private void UninstallButton_Click(object sender, EventArgs e)
{
operation = "UNINSTALL";
InstallButton_Click(sender, e);
}
private void OpenFolderButton_Click(object sender, EventArgs e)
{
if (platform == "Windows")
{
startDetached("", ".\\EnhancedDiscord");
}
else if (platform == "Mac")
{
startDetached("open", "./EnhancedDiscord");
}
}
private void UpdateButton_Click(object sender, EventArgs e)
{
operation = "UPDATE";
InstallButton_Click(sender, e);
}
private void ReinjectButton_Click(object sender, EventArgs e)
{
operation = "REINJECT";
InstallButton_Click(sender, e);
}
async private void continueUpdate(Process proc)
{
string path = proc.MainModule.FileName;
operation = "UPDATE";
BetaRadio.Hide();
InstallButton.Hide();
UninstallButton.Hide();
UpdateButton.Hide();
ReinjectButton.Hide();
StatusText.Show();
InstallProgress.Show();
InstallProgress.Value = 0;
string tempPath = Path.Combine(Path.GetTempPath(), "EnhancedDiscord");
if (Directory.Exists(tempPath))
{
try
{
Directory.Delete(tempPath, true);
}
catch (Exception e)
{
StatusText.Text = "Error deleting temp folders.";
Logger.Log(StatusText.Text + " " + e.Message);
}
}
Directory.CreateDirectory(tempPath);
StatusText.Text = "Downloading package...";
Logger.Log(StatusText.Text);
string zipPath = Path.Combine(tempPath, "EnhancedDiscord.zip");
string zipLink = Properties.Resources.zipLink + branch;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 | SecurityProtocolType.Ssl3;
WebClient wc = new WebClient();
try
{
await wc.DownloadFileTaskAsync(new Uri(zipLink), zipPath);
}
catch (Exception e)
{
Logger.Error("Failed to download ED files. " + e.Message);
endInstallation("Failed to download ED files.", true); return;
}
InstallProgress.Value = 40;
StatusText.Text = "Successfully downloaded. Extracting...";
Logger.Log(StatusText.Text);
try
{
ZipFile.ExtractToDirectory(zipPath, tempPath);
}
catch (Exception e)
{
Logger.Error("Failed to extract zip file. " + e.Message);
endInstallation("Failed to extract zip file.", true); return;
}
InstallProgress.Value = 50;
StatusText.Text = "Finished extracting zip. Checking core...";
Logger.Log(StatusText.Text);
string extractedPath = Path.Combine(tempPath, "EnhancedDiscord-" + branch);
string enhancedPath = "./EnhancedDiscord";
if (!File.Exists(Path.Combine(enhancedPath, "config.json")))
{
try
{
File.WriteAllText(Path.Combine(enhancedPath, "config.json"), "{}");
}
catch (Exception e)
{
Logger.Error("Failed to write config.json. " + e.Message);
}
}
string[] garbage = new string[] { "README.md", "plugins.md", ".gitignore", "advanced_installation.md" };
foreach (string file in Directory.GetFiles(extractedPath))
{
string filename = Path.GetFileName(file);
if (Array.Exists(garbage, f => f == filename)) continue;
string equiv = Path.Combine(enhancedPath, filename);
bool filesEqual = false;
bool fileExists = File.Exists(equiv);
if (fileExists) filesEqual = FilesEqual(file, equiv);
try
{
if (fileExists && !filesEqual) File.Delete(equiv);
if (!fileExists || !filesEqual) File.Copy(file, equiv);
}
catch (Exception e)
{
StatusText.Text = "Could not update plugin: " + filename;
Logger.Log(StatusText.Text + " " + e.Message);
}
}
InstallProgress.Value = 70;
StatusText.Text = "Core finished. Checking plugins...";
Logger.Log(StatusText.Text);
string pluginPath = Path.Combine(enhancedPath, "plugins");
if (!Directory.Exists(pluginPath)) Directory.CreateDirectory(pluginPath);
foreach (string file in Directory.GetFiles(Path.Combine(extractedPath, "plugins")))
{
string filename = Path.GetFileName(file);
if (filename == "style.css") continue;
string equiv = Path.Combine(pluginPath, filename);
bool filesEqual = false;
bool fileExists = File.Exists(equiv);
if (fileExists) filesEqual = FilesEqual(file, equiv);
try
{
if (fileExists && !filesEqual) File.Delete(equiv);
if (!fileExists || !filesEqual) File.Copy(file, equiv);
}
catch (Exception e)
{
StatusText.Text = "Could not update plugin: " + filename;
Logger.Log(StatusText.Text + " " + e.Message);
}
}
StatusText.Text = "Cleaning up...";
Logger.Log(StatusText.Text);
if (Directory.Exists(tempPath))
{
try
{
Directory.Delete(tempPath, true);
}
catch (Exception e)
{
StatusText.Text = "Error deleting temp folders.";
Logger.Log(StatusText.Text + " " + e.Message);
}
}
InstallProgress.Value = 90;
endInstallation("ED files updated.", false); return;
}
// Adapted from https://stackoverflow.com/questions/7931304/comparing-two-files-in-c-sharp
private bool FilesEqual(string filename1, string filename2)
{
if (filename1 == filename2) return true;
FileStream fileStream1 = new FileStream(filename1, FileMode.Open, FileAccess.Read);
FileStream fileStream2 = new FileStream(filename2, FileMode.Open, FileAccess.Read);
if (fileStream1.Length != fileStream2.Length)
{
fileStream1.Close();
fileStream2.Close();
return false;
}
int fileByte1;
int fileByte2;
do
{
fileByte1 = fileStream1.ReadByte();
fileByte2 = fileStream2.ReadByte();
}
while ((fileByte1 == fileByte2) && (fileByte1 != -1));
fileStream1.Close();
fileStream2.Close();
return ((fileByte1 - fileByte2) == 0);
}
}
}

View file

@ -0,0 +1,148 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="toolTip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>19, 20</value>
</metadata>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAABMLAAATCwAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAIAAAACAAAAAgAAAAIAAAACAAAAAAAAAAEAAAACAAAAAgAA
AAIAAAACAAAAAQAAAAAAAAAAAAAAAAAON1cADTaWAA02lwANNpcADTaYAA0zkgAHHzgADTZUAA02mAAN
NpcADTaXAA01lwAMMYQACCJFAAEDCQACCgAAFVOdABVW/wAVU/gAFVPwABVT8AAVU/EAEUWrABNNrAAV
Vv8AFVT5ABVU8wAVVPUAFVX9ABRR7AAOO3gAAAAJABxvnQAccv8AF1y/ABRSgwAVVYQAE051ABFHOAAb
baEAHHH/ABZXjQAWWTIAF1w2ABllbwAcce0AGmjmAA01QQAiip0AI47/ACOM+gAji/UAI4z2ACGE5wAV
U1sAIomZACOM/wAaaGwAM80AABZaAAAXXwgAIoq5ACKK/AAWWmcAKaWdACqp/wAkkM0AIoeeACKJnwAi
iJ4AHXNaACGFVAAhg5IAGGFQABFFIwATSiUAIYNWACqn4wApo+4AGmhJADDBnQAyxv8AL7vvAC643wAu
ud8ALbTbAB97bAAml0UALrfQAC654gAuuOIALrnkADDA9gAxw/kAKqqOAA87CwA11WwANtiyADbZsgA2
2bMANtmzADfZtQAvu5IAHHAeADbWeQA32bUANtmzADbYswA10p4AMMBZACKIDAApowAAMsUDAC61BQAt
tAUALbQFAC20BQAttAUANM4FACqoAQAxwgIALrYFAC20BQAqqQUAHXQCACqmAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAA//8AAP//AAD//wAA//8AAAIHAAAAAQAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAQAAAAcAAP//
AAD//wAA//8AAA==
</value>
</data>
</root>

View file

@ -0,0 +1,81 @@
using System;
using System.IO;
using System.Reflection;
namespace EnhancedDiscordUI
{
static public class Logger
{
static public void Log(string logMessage)
{
string m_exePath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
try
{
using (StreamWriter w = File.AppendText(m_exePath + "\\" + "log.txt"))
{
_Log("INFO", logMessage, w);
}
}
catch (Exception ex)
{
}
}
static public void Warn(string logMessage)
{
string m_exePath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
try
{
using (StreamWriter w = File.AppendText(m_exePath + "\\" + "log.txt"))
{
_Log("WARN", logMessage, w);
}
}
catch (Exception ex)
{
}
}
static public void Error(string logMessage)
{
string m_exePath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
try
{
using (StreamWriter w = File.AppendText(m_exePath + "\\" + "log.txt"))
{
_Log("ERROR", logMessage, w);
}
}
catch (Exception ex)
{
}
}
static public void _Log(string type, string logMessage, TextWriter txtWriter)
{
try
{
txtWriter.WriteLine("[{0}][{1} {2}]: {3}", type, DateTime.Now.ToLongTimeString(), DateTime.Now.ToLongDateString(), logMessage);
}
catch (Exception ex)
{
}
}
static public void MakeDivider()
{
string m_exePath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
try
{
using (StreamWriter w = File.AppendText(m_exePath + "\\" + "log.txt"))
{
w.WriteLine("---------------------------------------------------------------------");
}
}
catch (Exception ex)
{
}
}
}
}

View file

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace EnhancedDiscordUI
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new EDInstaller());
}
}
}

View file

@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("EnhancedDiscordUI")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("EnhancedDiscordUI")]
[assembly: AssemblyCopyright("Copyright © 2018")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("3639ae05-14e6-43b2-9ddb-1a3f4f52657c")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View file

@ -0,0 +1,181 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace EnhancedDiscordUI.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("EnhancedDiscordUI.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap discord_canary_16 {
get {
object obj = ResourceManager.GetObject("discord_canary_16", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap discord_canary_32 {
get {
object obj = ResourceManager.GetObject("discord_canary_32", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap discord_canary_64 {
get {
object obj = ResourceManager.GetObject("discord_canary_64", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap discord_dev_16 {
get {
object obj = ResourceManager.GetObject("discord_dev_16", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap discord_dev_32 {
get {
object obj = ResourceManager.GetObject("discord_dev_32", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap discord_dev_64 {
get {
object obj = ResourceManager.GetObject("discord_dev_64", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap discord_stable_16 {
get {
object obj = ResourceManager.GetObject("discord_stable_16", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap discord_stable_32 {
get {
object obj = ResourceManager.GetObject("discord_stable_32", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap discord_stable_64 {
get {
object obj = ResourceManager.GetObject("discord_stable_64", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap ed_og {
get {
object obj = ResourceManager.GetObject("ed_og", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized string similar to require(`${process.env.injDir}/injection.js`);.
/// </summary>
internal static string injection {
get {
return ResourceManager.GetString("injection", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to https://codeload.github.com/joe27g/EnhancedDiscord/zip/.
/// </summary>
internal static string zipLink {
get {
return ResourceManager.GetString("zipLink", resourceCulture);
}
}
}
}

View file

@ -0,0 +1,157 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="discord_canary_64" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\discord_canary_64.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="discord_dev_32" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\discord_dev_32.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="discord_canary_16" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\discord_canary_16.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="discord_stable_64" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\discord_stable_64.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="discord_canary_32" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\discord_canary_32.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="discord_dev_64" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\discord_dev_64.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="discord_stable_32" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\discord_stable_32.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="discord_dev_16" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\discord_dev_16.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="discord_stable_16" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\discord_stable_16.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="ed_og" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\ed_og.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="injection" xml:space="preserve">
<value>require(`${process.env.injDir}/injection.js`);</value>
</data>
<data name="zipLink" xml:space="preserve">
<value>https://codeload.github.com/joe27g/EnhancedDiscord/zip/</value>
</data>
</root>

View file

@ -0,0 +1,26 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace EnhancedDiscordUI.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default {
get {
return defaultInstance;
}
}
}
}

View file

@ -0,0 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
<Settings />
</SettingsFile>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View file

@ -0,0 +1,98 @@
/**
* Plugin Class
*/
class Plugin {
/**
* Create your plugin, must have a name and load() function
* @constructor
* @param {object} options - Plugin options
*/
constructor (opts = {}) {
if (!opts.name || typeof opts.load !== 'function')
return 'Invalid plugin. Needs a name and a load() function.';
Object.assign(this, opts);
if (!this.color)
this.color = 'orange';
if (!this.author)
this.author = '<unknown>';
}
load () {}
unload () {}
reload () {
this.unload();
delete require.cache[require.resolve(`./plugins/${this.id}`)];
const newPlugin = require(`./plugins/${this.id}`);
window.ED.plugins[this.id] = newPlugin;
newPlugin.id = this.id;
newPlugin.load();
}
/**
* Send a decorated console.log prefixed with ED and your plugin name
* @param {...string} msg - Message to be logged
*/
log (...msg) {
console.log(`%c[EnhancedDiscord] %c[${this.name}]`, 'color: red;', `color: ${this.color}`, ...msg);
}
/**
* Send a decorated console.info prefixed with ED and your plugin name
* @param {...string} msg - Message to be logged
*/
info (...msg) {
console.info(`%c[EnhancedDiscord] %c[${this.name}]`, 'color: red;', `color: ${this.color}`, ...msg);
}
/**
* Send a decorated console.warn prefixed with ED and your plugin name
* @param {...string} msg - Message to be logged
*/
warn (...msg) {
console.warn(`%c[EnhancedDiscord] %c[${this.name}]`, 'color: red;', `color: ${this.color}`, ...msg);
}
/**
* Send a decorated console.error prefixed with ED and your plugin name
* @param {...string} msg - Message to be logged
*/
error (...msg) {
console.error(`%c[EnhancedDiscord] %c[${this.name}]`, 'color: red;', `color: ${this.color}`, ...msg);
}
/**
* Returns a Promise that resolves after ms milliseconds.
* @param {number} ms - How long to wait before resolving the promise
*/
sleep (ms) {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
}
get settings() {
//this.log('getting settings');
if (window.ED.config && window.ED.config[this.id])
return window.ED.config[this.id];
const final = {};
if (this.config)
for (const key in this.config)
final[key] = this.config[key].default;
return this.settings = final;
//return final;
}
set settings(newSets = {}) {
//this.log(__dirname, process.env.injDir);
//console.log(`setting settings for ${this.id} to`, newSets);
try {
const gay = window.ED.config;
gay[this.id] = newSets;
window.ED.config = gay;
//console.log(`set settings for ${this.id} to`, this.settings);
} catch(err) {
this.error(err);
}
return this.settings;
}
}
module.exports = Plugin;

View file

@ -0,0 +1,94 @@
# How to make plugins
Let's start by showing the basic format:
```js
const Plugin = require('../plugin');
module.exports = new Plugin({
name: 'A Plugin', /* Human-readable plugin name. */
author: 'Joe', /* [Optional] Put your name here to give yourself credit for making it :) */
description: 'Does absolutely nothing, lol', /* Description of what this plugin does. */
preload: false, /* [Optional] If true, load this before Discord has finished starting up */
color: '#666', /* [Optional] The color that this plugin shows in logs and in the plugin settings tab. Any valid CSS color will work here. */
load: function() {
/* What your plugin does when Discord is loaded, or when the plugin is reloaded. */
},
unload: function() {
/* What your plugin does when it is disabled or being reloaded. */
}
});
```
### Helper functions reference
The Plugin class adds the following functions to your plugin:
* **`log`, `info`, `warn`, `error`**: same as `console.log`, `console.info`, etc., except it adds the name and color of your plugin to the log statement. Example: `this.log('Successfully loaded!')`
* **`sleep(ms)`**: Returns a Promise that resolves after `ms` milliseconds. Usage: `await this.sleep(5000);` __[Make sure you make `load()` an async function first!]__
### Storing settings
The Plugin class and settings plugin offer the following helpers for plugin settings:
* **`settings`**: Stores your plugin's settings in the config file. It is done using getters and setters, so loading and saving your plugin's settings are as easy as reading and writing `this.settings`.
* **`config`**: Integrates with `settings`. This should essentially store all your default settings, and these will be saved to the config file the first time your plugin is loaded, or when the user resets their settings. Use this when creating your plugin, in the object that is used in `new Plugin({})`.
* This should be located in the object you used to create the plugin (i.e. between the `id` property and `load` function.)
* The format is as follows:
```js
config: {
'settingName': {
default: 'default value for this setting'
/* I highly recommend you add extra properties here to help you create the settings section, if you wish to do so. */
}
}
```
### Global helper functions
The following functions/variables are available from the `window` object to help you make plugins:
* `findModule(name, silent)`: Looks for a module with a function called `name`. Using this can be trial-and-error sometimes. Example: `findModule('sendMessage').sendMessage('415248292767727616', {content: 'random shit'})` would send the message 'random shit' in #shitposting in the EnhancedDiscord server. The `silent` argument tells whether to send a warning in console if the module is not found.
* `findModules(name)`: Same as above, but returns an Array of modules.
* `monkeyPatch(module, functionName, newFunction)`: Replaces a function by the name of `functionName` inside a `module` with a new one (`newFunction`). **This has special properties regarding the original method - Details below.**
* To access the original arguments, use `arguments[0].methodArguments`. For example, in sendMessage, `arguments[0].methodArguments[0]` is the channel ID.
* To access the "this" object used internally, use `arguments[0].thisObject`.
* To access the original function, use `arguments[0].originalMethod`.
* To run the original function, use `arguments[0].callOriginalMethod(arguments[0].methodArguments)`.
* To undo your patch, use the `unpatch()` function; for example, `findModule('sendMessage').sendMessage.unpatch();`. The `__monkeyPatched` property, located in the same place, can be used to determine if a function is already patched.
Original versions of these two functions are from [samogot](https://github.com/samogot)'s [Lib Discord Internals](https://github.com/samogot/betterdiscord-plugins/blob/master/v2/1Lib%20Discord%20Internals/plugin.js).
### Advanced plugin functionality
You can also add your own section to EnhancedDiscord's settings in your plugin, but you'll have to do all the grunt work.
* **You must have a `config` for the following to work.** Set it to an empty object if you do not need it.
* `generateSettings`: a function that is called when EnhancedDiscord settings are opened. This should return HTML to be placed in your setttings section.
* `settingListeners`: an Object mapping selectors to eventlisteners.
* For example, if your `generateSettings` function has an element `<input type="checkbox" id="memez">`, you might use the following format:
```js
settingListeners: [{
el: '#memez',
type: 'click'
eHandler: function() {
alert('some dumb text');
}
}]
```
#### For more examples, just browse the included plugins, namely `emoji_packs`.

View file

@ -0,0 +1,17 @@
const Plugin = require('../plugin');
module.exports = new Plugin({
name: 'Anti-Track',
author: 'Joe 🎸#7070',
description: `Prevent Discord from sending "tracking" data that they may be selling to advertisers or otherwise sharing.`,
color: 'white',
load: async function() {
window.EDApi.monkeyPatch(window.EDApi.findModule('track'), 'track', () => {});
window.EDApi.monkeyPatch(window.EDApi.findModule('submitLiveCrashReport'), 'submitLiveCrashReport', () => {});
},
unload: async function() {
window.EDApi.findModule('track').track.unpatch();
window.EDApi.findModule('submitLiveCrashReport').submitLiveCrashReport.unpatch();
}
});

View file

@ -0,0 +1,107 @@
const Plugin = require("../plugin");
const Clipboard = require("electron").clipboard;
let cm = {}, Dispatcher, ImageResolver, ContextMenuActions, ree;
module.exports = new Plugin({
name: "Avatar Links",
author: "Joe 🎸#7070",
description: "Lets you copy a user or guild's avatar URL by right-clicking on it.",
color: "#ffe000",
load: async function() {
ree = this;
cm = window.EDApi.findModule('contextMenu');
Dispatcher = window.EDApi.findModule("dispatch");
ImageResolver = window.EDApi.findModule("getUserAvatarURL");
ContextMenuActions = window.EDApi.findModule("closeContextMenu");
Dispatcher.subscribe("CONTEXT_MENU_OPEN", this.checkMenu);
},
unload: function() {
Dispatcher.unsubscribe("CONTEXT_MENU_OPEN", this.checkMenu);
},
checkMenu: async function() {
// Make sure it's already in the DOM
await new Promise(r => {setTimeout(r, 5)});
const theMenu = document.querySelector('.'+cm.contextMenu);
const reactData = theMenu.__reactInternalInstance$;
let label = "";
let url = "";
let props = {onHeightUpdate: () => {}};
// For users
if (
reactData.return &&
reactData.return.return &&
reactData.return.return.return &&
reactData.return.return.return.return &&
reactData.return.return.return.return.memoizedProps &&
reactData.return.return.return.return.memoizedProps.user &&
reactData.return.return.return.return.memoizedProps.type &&
reactData.return.return.return.return.memoizedProps.type.startsWith("USER_")
) {
props = reactData.return.return.return.return.memoizedProps;
label = "Copy Avatar URL";
const user = props.user;
const imageType = ImageResolver.hasAnimatedAvatar(user) ? "gif" : "png";
// Internal module maxes at 1024 hardcoded, so do that and change to 2048.
url = ImageResolver.getUserAvatarURL(user, imageType, 1024).replace("size=1024", "size=2048");
// For default avatars
if (!url.startsWith("http") && url.startsWith("/assets"))
url = `https://discordapp.com${url}`;
}
// For guilds
if (
reactData.return &&
reactData.return.memoizedProps &&
reactData.return.memoizedProps.guild &&
reactData.return.memoizedProps.type == "GUILD_ICON_BAR"
) {
props = reactData.return.memoizedProps;
label = "Copy Icon URL";
const guild = props.guild;
// Internal module maxes at 1024 hardcoded, so do that and change to 2048.
url = ImageResolver.getGuildIconURL({id: guild.id, icon: guild.icon, size: 1024}).replace("size=1024", "size=2048");
// No way to make it return the animated version, do it manually
if (ImageResolver.hasAnimatedGuildIcon(guild))
url = url.replace(".webp?", ".gif?");
else
url = url.replace(".webp?", ".png?");
}
// Assume it is already in the DOM and add item ASAP
if (label && url)
ree.addMenuItem(url, label, props);
},
addMenuItem: function(imageURL, text, menu) {
const cmGroups = document.getElementsByClassName(cm.itemGroup);
if (!cmGroups || cmGroups.length == 0) return;
const newCmItem = document.createElement("div");
newCmItem.className = cm.item+' '+cm.clickable;
const newCmItemContent = document.createElement("div");
newCmItemContent.className = cm.label;
newCmItemContent.innerHTML = text;
newCmItem.appendChild(newCmItemContent);
const lastGroup = cmGroups[cmGroups.length-1];
lastGroup.appendChild(newCmItem);
menu.onHeightUpdate();
newCmItem.onclick = () => {
Clipboard.write({text: imageURL});
ContextMenuActions.closeContextMenu();
}
}
});

View file

@ -0,0 +1,80 @@
const Plugin = require('../plugin');
let ml = {}, cta = {}, ta, gs, em, gc, gci, gcu, cm;
module.exports = new Plugin({
name: 'Character Count',
author: 'Joe 🎸#7070',
description: `Shows the number of characters next to the message you're typing. Takes into account the extra length of resolved emojis, mentions, etc.`,
color: 'violet',
load: async function() {
ml = window.EDApi.findModule('maxLength');
cta = window.EDApi.findModule('channelTextArea');
ta = window.EDApi.findModuleByDisplayName('ChannelEditorContainer').prototype;
gs = window.EDApi.findModule('getAllSettings');
em = window.EDApi.findModule(m => m.checkbox && m.errorMessage);
gc = window.EDApi.findModule('getChannel');
gci = window.EDApi.findModule('getChannelId');
gcu = window.EDApi.findModule('getCurrentUser');
cm = window.EDApi.findModule('createBotMessage');
window.EDApi.monkeyPatch(ta, 'render', {after: this.onRender, silent: true});
window.EDApi.findModule('dispatch').subscribe("MESSAGE_CREATE", this.msgListener);
},
onRender: function(b) {
const ctaElem = document.querySelector('.' + cta.channelTextArea);
if (!ctaElem) return;
if (!gs.useRichChatTextBox) return module.exports.inputListener(ctaElem); // legacy support
const txt = b.thisObject.props.textValue;
const parent = ctaElem.parentElement;
let charCountElem = parent.querySelector('.' + ml.maxLength);
const len = (txt || '').trim().length;
if (!len && charCountElem)
charCountElem.remove();
if (!len) return;
if (!charCountElem) {
charCountElem = document.createElement('div');
charCountElem.style = "bottom:3px;right:5px;";
parent.appendChild(charCountElem);
}
charCountElem.innerHTML = len + '/2000';
charCountElem.className = `ed_char_count ${ml.maxLength}${len > 2000 ? ' '+em.errorMessage : ''}`;
},
msgListener: function(event) {
// if message is not by current user or in different channel, cancel
if (event.message.author.id !== gcu.getCurrentUser().id || event.message.channel_id !== gci.getChannelId()) return;
const charCounters = document.querySelectorAll('.ed_char_count');
charCounters.forEach(cc => cc.remove());
},
inputListener: function(elem) {
if (!elem) return;
const taElem = elem.querySelector('textarea');
if (!taElem) return;
const parent = elem.parentElement;
if (!parent || !elem.className || !elem.className.includes(cta.channelTextArea)) return;
let charCountElem = parent.querySelector('.' + ml.maxLength);
if (!charCountElem) {
charCountElem = document.createElement('div');
charCountElem.style = "bottom:3px;right:5px;";
parent.appendChild(charCountElem);
}
const chan = gc.getChannel(gci.getChannelId());
let len = (taElem.value || '').trim().length;
if (chan && taElem.value) {
const msgObj = cm.parse(chan, taElem.value);
if (msgObj && msgObj.content)
len = msgObj.content.trim().length;
}
charCountElem.innerHTML = len + '/2000';
charCountElem.className = `ed_char_count ${ml.maxLength}${len > 2000 ? ' '+em.errorMessage : ''}`;
},
unload: function() {
ta.render.unpatch();
window.EDApi.findModule('dispatch').unsubscribe("MESSAGE_CREATE", this.msgListener);
}
});

View file

@ -0,0 +1,149 @@
const Plugin = require("../plugin")
const path = window.require("path")
const fs = window.require("fs")
module.exports = new Plugin({
name: "CSS Loader + Glasscord Integration",
author: "Joe 🎸#7070 + AryToNeX",
description: "Loads and hot-reloads CSS.",
preload: true, //load this before Discord has finished starting up
color: "blue",
config: {
path: {
default: "./plugins/style.css",
parse: function (filePath) {
if (!filePath || !filePath.endsWith(".css")) {
return false
}
if (path.isAbsolute(filePath)) {
if (!fs.existsSync(filePath)) {
return false
}
return path.relative(process.env.injDir, filePath)
} else {
const p = path.join(process.env.injDir, filePath)
if (!fs.existsSync(p)) {
return false
}
return path.relative(process.env.injDir, p)
}
},
},
},
load: async function () {
function readFile(path, encoding = "utf-8") {
return new Promise((resolve, reject) => {
fs.readFile(path, encoding, (err, data) => {
if (err) reject(err)
else resolve(data)
})
})
}
const cssPath = path.join(process.env.injDir, this.settings.path || this.config.path.default)
readFile(cssPath)
.then((css) => {
if (!window.customCss) {
window.customCss = document.createElement("style")
document.head.appendChild(window.customCss)
}
window.customCss.innerHTML = css
this.info("Custom CSS loaded!", window.customCss)
window.require("electron").ipcRenderer.send("glasscord_refresh_variables")
if (window.cssWatcher == null) {
window.cssWatcher = fs.watch(cssPath, { encoding: "utf-8" }, (eventType) => {
if (eventType == "change") {
readFile(cssPath).then((newCss) => {
window.customCss.innerHTML = newCss
window.require("electron").ipcRenderer.send("glasscord_update")
})
}
})
}
})
.catch(() => console.info("Custom CSS not found. Skipping..."))
},
unload: function () {
if (window.customCss) {
document.head.removeChild(window.customCss)
window.customCss = null
}
if (window.cssWatcher) {
window.cssWatcher.close()
window.cssWatcher = null
}
},
generateSettings: function () {
const d = window.ED.classMaps.description
const b = window.ED.classMaps.buttons
const id = window.EDApi.findModule("inputDefault")
const m = window.EDApi.findModule("marginTop8")
const result = `<div class="${d.description} ${
d.modeDefault
}">Custom CSS Path<br>This can be relative to the EnhancedDiscord directory (e.g. <code class="inline">./big_gay.css</code>) or absolute (e.g. <code class="inline">C:/theme.css</code>.)</div><input type="text" class="${
id.inputDefault
}" value="${this.settings.path || this.config.path.default}" maxlength="2000" placeholder="${
this.config.path.default
}" id="custom-css-path"><button type="button" id="save-css-path" class="${b.button} ${
b.lookFilled
} ${b.colorBrand} ${m.marginTop8} ${
m.marginBottom8
}" style="height:24px;margin-right:10px;"><div class="${b.contents}">Save</div></button>`
return result
},
settingListeners: [
{
el: "#save-css-path",
type: "click",
eHandler: function () {
//console.log(this, e.target);
const pathInput = document.getElementById("custom-css-path")
if (!pathInput) return
if (pathInput.value && module.exports.config.path.parse(pathInput.value) == false) {
const cont = this.firstElementChild
cont.innerHTML = "Invalid file."
setTimeout(() => {
try {
cont.innerHTML = "Save"
} catch (err) {
/*do nothing*/
}
}, 3000)
return
}
const newPath =
module.exports.config.path.parse(pathInput.value) || module.exports.config.path.default
const s = module.exports.settings
if (s.path == newPath) {
const cont = this.firstElementChild
cont.innerHTML = "Path was already saved."
setTimeout(() => {
try {
cont.innerHTML = "Save"
} catch (err) {
/*do nothing*/
}
}, 3000)
return
}
s.path = newPath
module.exports.settings = s
module.exports.unload()
module.exports.load()
const cont = this.firstElementChild
cont.innerHTML = "Saved!"
setTimeout(() => {
try {
cont.innerHTML = "Save"
} catch (err) {
/*do nothing*/
}
}, 3000)
},
},
],
})

View file

@ -0,0 +1,104 @@
const Plugin = require('../plugin');
// contains modified code from https://stackoverflow.com/a/47820271
const { dialog } = require('electron').remote;
const http = require('https');
const fs = require('fs');
let ttM = {}, iteM = {};
function saveAs(url, filename, fileExtension) {
const userChosenPath = dialog.showSaveDialog({ defaultPath: filename, title: 'Where would you like to store the stolen memes?', buttonLabel: 'Steal this meme', filters: [{ name: "Stolen meme", extensions: [fileExtension] }] });
if (userChosenPath) {
download(url, userChosenPath, () => {
const wrap = document.createElement('div');
wrap.className = 'theme-dark';
const gay = document.createElement('div');
gay.style = "position: fixed; bottom: 10%; left: calc(50% - 88px);"
gay.className = `${ttM.tooltip} ${ttM.tooltipTop} ${ttM.tooltipBlack}`;
gay.innerHTML = 'Successfully downloaded | ' + userChosenPath;
document.body.appendChild(wrap);
wrap.appendChild(gay);
setTimeout(() => wrap.remove(), 2000);
});
}
}
function download (url, dest, cb) {
const file = fs.createWriteStream(dest);
http.get(url, function(response) {
response.pipe(file);
file.on('finish', function() {
file.close(cb);
});
}).on('error', function(err) {
fs.unlink(dest);
if (cb) cb(err.message);
});
}
function addMenuItem(url, text, filename = true, fileExtension) {
const cmGroups = document.getElementsByClassName(iteM.itemGroup);
if (!cmGroups || cmGroups.length == 0) return;
const newCmItem = document.createElement('div');
newCmItem.className = iteM.item;
newCmItem.innerHTML = text;
const lastGroup = cmGroups[cmGroups.length-1];
lastGroup.appendChild(newCmItem);
newCmItem.onclick = () => saveAs(url, filename, fileExtension);
}
// contains code modified from https://github.com/Metalloriff/BetterDiscordPlugins/blob/master/SaveTo.plugin.js
module.exports = new Plugin({
name: 'Direct Download',
author: 'Joe 🎸#7070',
description: `<del>Download files</del> Steal memes without opening a browser.`,
color: '#18770e',
load: async function() {
this._cmClass = window.EDApi.findModule("contextMenu").contextMenu;
this._contClass = window.EDApi.findModule("embedWrapper").container;
ttM = window.EDApi.findModule('tooltipPointer');
iteM = window.EDApi.findModule('itemBase');
document.addEventListener("contextmenu", this.listener);
},
listener(e) {
if (document.getElementsByClassName(this._cmClass).length == 0) setTimeout(() => module.exports.onContextMenu(e), 0);
else this.onContextMenu(e);
},
onContextMenu(e) {
const messageGroup = e.target.closest('.'+this._contClass);
if (e.target.localName != "a" && e.target.localName != "img" && e.target.localName != "video" && !messageGroup && !e.target.className.includes("guildIcon") && !e.target.className.includes("image-")) return;
let saveLabel = "Download",
url = e.target.poster || e.target.style.backgroundImage.substring(e.target.style.backgroundImage.indexOf(`"`) + 1, e.target.style.backgroundImage.lastIndexOf(`"`)) || e.target.href || e.target.src;
if (e.target.className.includes("guildIcon")) saveLabel = "Download Icon";
else if (e.target.className.includes("image-")) saveLabel = "Download Avatar";
if (!url || e.target.classList.contains("emote") || url.includes("youtube.com/watch?v=") || url.includes("youtu.be/") || url.lastIndexOf("/") > url.lastIndexOf(".")) return;
url = url.split("?")[0];
url = url.replace(".webp", ".png");
let fileName = url.substring(url.lastIndexOf("/") + 1, url.lastIndexOf("."));
const fileExtension = url.substr(url.lastIndexOf(".") + 1, url.length);
if (saveLabel.includes("Avatar") || saveLabel.includes("Icon")) url += "?size=2048";
if (e.target.classList.contains("emoji")) {
saveLabel = "Download Emoji";
fileName = e.target.alt.replace(/[^A-Za-z_-]/g, "");
}
//console.log({url, saveLabel, fileName, fileExtension});
setTimeout(() => addMenuItem(url, saveLabel, fileName, fileExtension), 5);
},
unload: function() {
document.removeEventListener("contextmenu", this.listener);
}
});

View file

@ -0,0 +1,85 @@
const Plugin = require('../plugin');
let contM = {}, cM, eM, dM, mM, ewM = {}, ree;
module.exports = new Plugin({
name: 'Double-Click Edit',
author: 'Joe 🎸#7070',
description: 'Allows you to double-click a message to edit or hold delete + click to delete.',
color: '#ff5900',
deletePressed: false,
load: async function() {
contM = window.EDApi.findModule(m => m.container && m.containerCozy);
cM = window.EDApi.findModule('getChannelId');
eM = window.EDApi.findModule('startEditMessage');
dM = window.EDApi.findModule('deleteMessage');
mM = window.EDApi.findModule('getRawMessages');
ewM = window.EDApi.findModule('embedWrapper');
if (!cM || !eM || !dM || !ewM) {
return this.error('Aborted loading - Failed to find required modules!');
}
ree = this;
document.addEventListener("dblclick", this.editListener, false);
document.addEventListener("keydown", this.keyDownListener);
document.addEventListener("keyup", this.keyUpListener);
document.addEventListener("click", this.deleteListener);
// allow editing in "locked" (read-only) channels
const prot = window.EDApi.findModuleByDisplayName("ChannelEditorContainer").prototype;
window.EDApi.monkeyPatch(prot, 'render', b => {
if (b.thisObject.props.type === 'edit')
b.thisObject.props.disabled = false;
return b.callOriginalMethod(b.methodArguments);
});
},
unload: async function() {
document.removeEventListener("dblclick", this.editListener);
document.removeEventListener("keydown", this.keyDownListener);
document.removeEventListener("keyup", this.keyUpListener);
document.removeEventListener("click", this.deleteListener);
},
editListener: function(e) {
const messageElem = e.target.closest('.'+contM.container);
if (!messageElem) return;
let msgObj;
try {
msgObj = messageElem.__reactInternalInstance$.return.return.memoizedProps.message;
} catch(err) {
ree.error(err);
}
if (!msgObj) return;
const channelId = cM.getChannelId();
if (!channelId) return;
const newMsgObj = mM.getMessage(msgObj.channel_id, msgObj.id);
return eM.startEditMessage(channelId, msgObj.id, newMsgObj.content || '');
},
deleteListener: function(e) {
if (!ree.deletePressed) return;
let messageElem = e.target.closest('.'+contM.container);
const wrapperElem = e.target.closest('.'+ewM.container);
if (!messageElem && wrapperElem)
messageElem = wrapperElem.parentElement.firstElementChild;
if (!messageElem) return;
let msgObj;
try {
msgObj = messageElem.__reactInternalInstance$.return.return.memoizedProps.message;
} catch(err) {
ree.error(err);
}
if (!msgObj) return;
const channelId = cM.getChannelId();
if (!channelId) return;
return dM.deleteMessage(channelId, msgObj.id);
},
keyUpListener: function(e) {
if (e.keyCode == 46)
ree.deletePressed = false;
},
keyDownListener: function(e) {
if (e.keyCode == 46)
ree.deletePressed = true;
}
});

View file

@ -0,0 +1,42 @@
const Plugin = require('../plugin');
let userM = {}, taM = {}, avM = {}, wM = {}, ree;
module.exports = new Plugin({
name: 'Double-Click Mention',
author: 'Joe 🎸#7070',
description: 'Allows you to double-click a user\'s name to mention them.',
color: '#00bbff',
_userTag: '',
load: async function() {
taM = window.EDApi.findModule('textArea');
userM = window.EDApi.findModule('username');
avM = window.EDApi.findModule('avatar');
wM = window.EDApi.findModule(m => m.wrapper && m.avatar);
ree = this;
document.addEventListener("dblclick", this.doubleListener);
},
unload: async function() {
document.removeEventListener("dblclick", this.doubleListener);
},
doubleListener: function(e) {
if (!e || !e.target || !e.target.parentElement) return;
let tag;
try {
if (e.target.className === userM.username)
tag = e.target.parentElement.__reactInternalInstance$.return.return.memoizedProps.message.author.tag;
else if (e.target.className === wM.wrapper && e.target.parentElement.className === avM.avatar)
tag = e.target.parentElement.__reactInternalInstance$.return.return.memoizedProps.user.tag;
} catch(err) {
ree.error(err);
tag = null;
}
if (!tag) return;
const ta = document.querySelector('.'+taM.textArea);
if (!ta) return;
ta.value = `${ta.value ? ta.value.endsWith(' ') ? ta.value : ta.value+' ' : ''}@${tag} `;
}
});

View file

@ -0,0 +1,251 @@
const Plugin = require('../plugin');
function makePluginToggle(opts = {}) {
const a = window.ED.classMaps.alignment;
const sw = window.ED.classMaps.switchItem;
const cb = window.ED.classMaps.checkbox;
const b = window.ED.classMaps.buttons;
const d = window.ED.classMaps.description;
const settingsButton = `<button type="button" class="${b.button} ${b.lookFilled} ${b.colorBrand} ed-plugin-settings" style="height:24px;margin-right:10px;"><div class="${b.contents}">Settings</div></button>`;
return `<div id="${opts.id}-wrap" class="${a.vertical} ${a.justifyStart} ${a.alignStretch} ${a.noWrap} ${sw.switchItem}" style="flex: 1 1 auto;">
<div class="${a.horizontal} ${a.justifyStart} ${a.alignStart} ${a.noWrap}" style="flex: 1 1 auto;">
<h3 class="${sw.titleDefault}" style="flex: 1 1 auto;">${opts.title}</h3>
${opts.color ? ` <div class="status" style="background-color:${opts.color}; box-shadow:0 0 5px 2px ${opts.color};margin-left: 5px; border-radius: 50%; height: 10px; width: 10px; position: relative; top: 6px; margin-right: 8px;"></div>` : ''}
${opts.showSettingsButton ? settingsButton : ''}
${opts.id == 'bdPlugins' ? '' : `<button type="button" class="${b.button} ${b.lookFilled} ${b.colorBrand} ed-plugin-reload" style="height:24px;margin-right:10px;"><div class="${b.contents}">Reload</div></button>`}
<div id="${opts.id}" class="${cb.switchEnabled} ${cb.valueUnchecked} ${cb.sizeDefault} ${cb.themeDefault}">
<input type="checkbox" class="${cb.checkboxEnabled}" value="on">
</div>
</div>
<div class="${d.description} ${d.modeDefault}" style="flex: 1 1 auto;">${opts.desc ? opts.desc : '<i>No Description Provided</i<'}</div>
<div class="${window.ED.classMaps.divider} ${sw.dividerDefault}"></div>
</div>`;
}
module.exports = new Plugin({
name: 'ED Settings',
author: 'Joe 🎸#7070',
description: 'Adds an EnhancedDiscord tab in user settings.',
color: 'darkred',
load: async function() {
const parentThis = this; //Allow use of parent methods in sub functions
if (!window.ED.classMaps) {
window.ED.classMaps = {};
}
const tabsM = window.EDApi.findModule('topPill');
const divM = window.EDApi.findModule(m => m.divider && Object.keys(m).length === 1)
const contentM = window.ED.classMaps.headers = window.EDApi.findModule('defaultMarginh2');
const marginM = window.ED.classMaps.margins = window.EDApi.findModule('marginBottom8');
const div = window.ED.classMaps.divider = divM ? divM.divider : '';
const cbM = window.ED.classMaps.checkbox = window.EDApi.findModule('checkboxEnabled');
const buttM = window.ED.classMaps.buttons = window.EDApi.findModule('lookFilled');
const concentCol = window.EDApi.findModule('contentColumn');
window.ED.classMaps.switchItem = window.EDApi.findModule('switchItem');
window.ED.classMaps.alignment = window.EDApi.findModule('horizontalReverse');
window.ED.classMaps.description = window.EDApi.findModule('formText');
// use this function to trigger the loading of the settings tabs. No MutationObservers this way :)
const gss = window.EDApi.findModule('getUserSettingsSections').default.prototype;
window.EDApi.monkeyPatch(gss, 'render', function() {
const tab = document.getElementsByClassName('ed-settings');
//console.log(tab);
if (!tab || tab.length < 1) {
const parent = document.querySelector('.' + tabsM.side);
if (!parent) {
setTimeout(() => {arguments[0].thisObject.forceUpdate();}, 100);
return arguments[0].callOriginalMethod(arguments[0].methodArguments);
}
const anchor = parent.querySelectorAll(`.${tabsM.separator}`)[3];
if (!anchor)
return arguments[0].callOriginalMethod(arguments[0].methodArguments);
const header = document.createElement('div');
header.className = tabsM.header + ' ed-settings';
header.innerHTML = 'EnhancedDiscord';
anchor.parentNode.insertBefore(header, anchor.nextSibling);
const pluginsTab = document.createElement('div');
const tabClass = `${tabsM.item} ${tabsM.themed} ed-settings`;
pluginsTab.className = tabClass;
pluginsTab.innerHTML = 'Plugins';
header.parentNode.insertBefore(pluginsTab, header.nextSibling);
const settingsTab = document.createElement('div');
settingsTab.className = tabClass;
settingsTab.innerHTML = 'Settings';
pluginsTab.parentNode.insertBefore(settingsTab, pluginsTab.nextSibling);
const sep = document.createElement('div');
sep.className = tabsM.separator;
settingsTab.parentNode.insertBefore(sep, settingsTab.nextSibling);
parent.onclick = function(e) {
if (!e.target.className || e.target.className.indexOf(tabsM.item) == -1 || e.target.innerHTML === 'Change Log') return;
for (const i in tab) {
tab[i].className = (tab[i].className || '').replace(" " + tabsM.selected, '')
}
}
pluginsTab.onclick = function(e) {
const settingsPane = document.querySelector(`.${concentCol.standardSidebarView} .${concentCol.contentColumn} > div`);
const otherTab = document.querySelector('.' + tabsM.item + '.' + tabsM.selected);
if (otherTab) {
otherTab.className = otherTab.className.replace(" " + tabsM.selected, '');
}
this.className += ` ${tabsM.selected}`;
if (settingsPane) {
// ED Header
settingsPane.innerHTML = `<h2 class="${contentM.h2} ${contentM.defaultColor} ${marginM.marginBottom8}">EnhancedDiscord Plugins</h2>`;
// Open Plugins Folder Button
settingsPane.innerHTML += `<button id="ed-openPluginsFolder" style="margin-bottom: 10px;" class="${buttM.button} ${buttM.lookFilled} ${buttM.colorGreen} ${buttM.sizeSmall} ${buttM.grow}"><div class="${buttM.contents}">Open Plugins Directory</div></button>`;
// Divider
settingsPane.innerHTML += `<div class="${div} ${marginM.marginBottom20}"></div>`
for (const id in window.ED.plugins) {
//if (id == 'ed_settings') continue;
settingsPane.innerHTML += makePluginToggle({id, title: window.ED.plugins[id].name, desc: window.ED.plugins[id].description, color: window.ED.plugins[id].color || 'orange', showSettingsButton: typeof window.ED.plugins[id].getSettingsPanel == 'function'});
if (!window.ED.plugins[id].settings || window.ED.plugins[id].settings.enabled !== false) {
const cb = document.getElementById(id);
if (cb && cb.className)
cb.className = cb.className.replace(cbM.valueUnchecked, cbM.valueChecked);
}
}
document.getElementById("ed-openPluginsFolder").onclick = function () {
const s = require("electron").shell.openItem(require("path").join(process.env.injDir, "plugins"))
if (s === false) console.error("[EnhancedDiscord] Unable to open external folder.")
}
}
e.stopPropagation(); // prevent from going to parent click handler
}
settingsTab.onclick = function(e) {
const settingsPane = document.querySelector(`.${concentCol.standardSidebarView} .${concentCol.contentColumn} > div`);
const otherTab = document.querySelector('.' + tabsM.item + '.' + tabsM.selected);
if (otherTab) {
otherTab.className = otherTab.className.replace(" " + tabsM.selected, '');
}
this.className += ` ${tabsM.selected}`;
if (settingsPane) {
settingsPane.innerHTML = `<h2 class="${contentM.h2} ${contentM.defaultColor}">EnhancedDiscord Configuration</h2><div class="${div} ${marginM.marginBottom20}"></div>`;
settingsPane.innerHTML += makePluginToggle({id: 'bdPlugins', title: 'BD Plugins', desc: "Allows ED to load BD plugins natively. (Reload with ctrl+r after enabling/disabling.)"});
const bl = document.getElementById('bdPlugins');
if (bl && window.ED.config.bdPlugins == true)
bl.className = bl.className.replace(cbM.valueUnchecked, cbM.valueChecked);
//console.log(st, at);
for (const id in window.ED.plugins) {
if (window.ED.plugins[id].getSettingsPanel && typeof window.ED.plugins[id].getSettingsPanel == 'function') continue;
if (!window.ED.plugins[id].config || window.ED.config[id].enabled === false || !window.ED.plugins[id].generateSettings) continue;
settingsPane.innerHTML += `<h2 class="${contentM.h2} ${contentM.defaultColor}">${window.ED.plugins[id].name}</h2>`;
settingsPane.innerHTML += window.ED.plugins[id].generateSettings();
settingsPane.innerHTML += `<div class="${div}"></div>`;
if (window.ED.plugins[id].settingListeners) {
setTimeout(() => { // let shit render
for(const eventObject in window.ED.plugins[id].settingListeners){
const currentSettingListener = window.ED.plugins[id].settingListeners[eventObject];
//Check if plugin is using the old format
if(Array.isArray(window.ED.plugins[id].settingListeners)){
const elem = settingsPane.querySelector(currentSettingListener.el);
if (elem)
elem.addEventListener(currentSettingListener.type, currentSettingListener.eHandler);
} else {
const elem = settingsPane.querySelector(eventObject);
if (elem){
parentThis.warn(`Plugin ${window.ED.plugins[id].name} is using a deprecated plugin format (New format: https://github.com/joe27g/EnhancedDiscord/blob/beta/plugins.md#advanced-plugin-functionality). Ignore this unless you're the plugin dev`)
elem.onclick = window.ED.plugins[id].settingListeners[eventObject];
}
}
}
}, 5);
}
}
}
e.stopPropagation(); // prevent from going to parent click handler
}
document.querySelector(`.${concentCol.standardSidebarView} .${concentCol.contentColumn}`).onclick = function(e) {
const parent = e.target.parentElement;
if (e.target.className && ((parent.className.indexOf && parent.className.indexOf('ed-plugin-settings') > -1) || (e.target.className.indexOf && e.target.className.indexOf('ed-plugin-settings') > -1))) {
const box = e.target.className === buttM.contents ? parent.nextElementSibling.nextElementSibling : e.target.nextElementSibling.nextElementSibling;
if (!box || !box.id || !window.ED.plugins[box.id] || box.className.indexOf(cbM.valueChecked) == -1 || !window.ED.config.bdPlugins) return;
return require('../bd_shit').showSettingsModal(window.ED.plugins[box.id]);
}
if (e.target.className && ((parent.className.indexOf && parent.className.indexOf('ed-plugin-reload') > -1) || (e.target.className.indexOf && e.target.className.indexOf('ed-plugin-reload') > -1))) {
const button = e.target.className === buttM.contents ? e.target : e.target.firstElementChild;
const plugin = e.target.className === buttM.contents ? e.target.parentElement.nextElementSibling : e.target.nextElementSibling;
//console.log(plugin);
if (!plugin || !plugin.id || !window.ED.plugins[plugin.id] || plugin.className.indexOf(cbM.valueChecked) == -1) return;
button.innerHTML = 'Reloading...';
try {
window.ED.plugins[plugin.id].reload();
button.innerHTML = 'Reloaded!';
} catch(err) {
console.error(err);
button.innerHTML = `Failed to reload (${err.name} - see console.)`;
}
setTimeout(() => {
try { button.innerHTML = 'Reload'; } catch(err){/*do nothing*/}
}, 3000);
return;
}
if (e.target.tagName !== 'INPUT' || e.target.type !== 'checkbox' || !parent || !parent.className || !parent.id) return;
const p = window.ED.plugins[parent.id];
if (!p && parent.id !== 'bdPlugins') return;
//console.log('settings for '+p.id, p.settings);
if (parent.className.indexOf(cbM.valueChecked) > -1) {
if (p) {
if (p.settings.enabled === false) return;
p.settings.enabled = false;
window.ED.plugins[parent.id].settings = p.settings;
p.unload();
}
else {
const edc = window.ED.config;
if (!edc[parent.id]) return;
edc[parent.id] = false;
window.ED.config = edc;
}
parent.className = parent.className.replace(cbM.valueChecked, cbM.valueUnchecked);
} else {
if (p) {
if (p.settings.enabled !== false) return;
p.settings.enabled = true;
window.ED.plugins[parent.id].settings = p.settings;
p.load();
}
else {
const edc = window.ED.config;
if (edc[parent.id] === true) return;
edc[parent.id] = true;
window.ED.config = edc;
}
parent.className = parent.className.replace(cbM.valueUnchecked, cbM.valueChecked);
}
}
}
return arguments[0].callOriginalMethod(arguments[0].methodArguments);
})
},
unload: function() {
window.EDApi.findModule('getUserSettingsSections').default.prototype.render.unpatch();
}
});

View file

@ -0,0 +1,91 @@
const Plugin = require('../plugin');
function makeToggle() {
const a = window.ED.classMaps.alignment;
const sw = window.ED.classMaps.switchItem;
const cb = window.ED.classMaps.checkbox;
const b = window.ED.classMaps.buttons;
const d = window.ED.classMaps.description;
return `<div class="${window.ED.classMaps.divider} ${sw.dividerDefault}"></div>
<div id="fc_online_wrap" class="${a.vertical} ${a.justifyStart} ${a.alignStretch} ${a.noWrap} ${sw.switchItem}" style="flex: 1 1 auto;">
<div class="${a.horizontal} ${a.justifyStart} ${a.alignStart} ${a.noWrap}" style="flex: 1 1 auto;">
<h3 class="${sw.titleDefault}" style="flex: 1 1 auto;">Online Friends</h3>
<div id="fc_online" class="${cb.switchEnabled} ${(module.exports.settings || {}).onlineOnly ? cb.valueChecked : cb.valueUnchecked} ${cb.sizeDefault} ${cb.themeDefault}">
<input type="checkbox" class="${cb.checkboxEnabled}" value="on">
</div>
</div>
<div class="${d.description} ${d.modeDefault}" style="flex: 1 1 auto;">Only show the number of friends online rather than all friends.</div>
</div>`;
}
module.exports = new Plugin({
name: 'Friend Count',
author: 'Joe 🎸#7070',
description: "Adds the number of friends/online friends under the \"Home\" button in the top left.",
color: 'cornflowerblue',
config: {
onlineOnly: {default: false}
},
load: async function() {
const sep = window.findModule('guildSeparator'), ms = window.findModule('modeSelectable');
const gg = function(b) {
if (!sep) return;
const o = (module.exports.settings || {}).onlineOnly;
const num = o ? window.findModule("getOnlineFriendCount").getOnlineFriendCount() : window.findModule("getFriendIDs").getFriendIDs().length;
let friendCount = document.getElementById('ed_friend_count');
if (friendCount) {
if (num === this._num) return; // don't update if # is the same as before
friendCount.innerHTML = num + (o ? ' Online' : ' Friends');
this._num = num;
return;
}
let separator = document.querySelector(`.${sep.guildSeparator}`);
if (separator) {
friendCount = document.createElement('div');
friendCount.className = `${ms ? ms.description+' ' : ''}${sep.listItem}`;
friendCount.innerHTML = num + (o ? ' Online' : ' Friends');
friendCount.id = 'ed_friend_count';
try {
separator.parentElement.insertAdjacentElement('beforebegin', friendCount);
this._num = num;
} catch(err) {
this.error(err);
}
}
};
const x = window.findModule('getGuilds');
findModule('subscribe').subscribe('CONNECTION_OPEN', x.getGuilds);
window.monkeyPatch(x, 'getGuilds', {silent: true, after: gg});
},
unload: function() {
let m = window.findModule('getGuilds').getGuilds;
if (m && m.__monkeyPatched)
m.unpatch();
let friendCount = document.getElementById('ed_friend_count');
if (friendCount)
friendCount.remove();
},
generateSettings: makeToggle,
settingListeners: [{
el: '#fc_online',
type: 'click',
eHandler: function(e) {
const cb = window.ED.classMaps.checkbox;
module.exports.settings = {onlineOnly: !(module.exports.settings || {}).onlineOnly};
if (module.exports.settings.onlineOnly) {
this.classList.remove(cb.valueUnchecked.split(' ')[0]);
this.classList.add(cb.valueChecked.split(' ')[0]);
} else {
this.classList.remove(cb.valueChecked.split(' ')[0]);
this.classList.add(cb.valueUnchecked.split(' ')[0]);
}
}
}]
});

View file

@ -0,0 +1,53 @@
const Plugin = require('../plugin');
let sep = {}, ms = {}, gg, sub;
module.exports = new Plugin({
name: 'Server Count',
author: 'Joe 🎸#7070',
description: "Adds the number of servers you're currently in right above the list.",
color: 'indigo',
load: async function() {
sep = window.EDApi.findModule('guildSeparator');
ms = window.EDApi.findModule('modeSelectable');
gg = window.EDApi.findModule('getGuilds');
sub = window.EDApi.findModule('subscribe');
window.EDApi.monkeyPatch(gg, 'getGuilds', {after: this.refreshCount, silent: true});
sub.subscribe('CONNECTION_OPEN', gg.getGuilds);
},
refreshCount: function(b) {
if (!sep) return;
const num = Object.keys(b.returnValue).length;
let guildCount = document.getElementById('ed_guild_count');
if (guildCount) {
if (num === this._num) return; // don't update if # is the same as before
guildCount.innerHTML = num + ' Servers';
this._num = num;
return;
}
const separator = document.querySelector(`.${sep.guildSeparator}`);
if (separator) {
guildCount = document.createElement('div');
guildCount.className = `${ms ? ms.description+' ' : ''}${sep.listItem}`;
guildCount.innerHTML = num + ' Servers';
guildCount.id = 'ed_guild_count';
try {
separator.parentElement.insertAdjacentElement('beforebegin', guildCount);
this._num = num;
} catch(err) {
this.error(err);
}
}
return;
},
unload: function() {
gg.getGuilds.unpatch();
const guildCount = document.getElementById('ed_guild_count');
if (guildCount)
guildCount.remove();
sub.unsubscribe('CONNECTION_OPEN', gg.getGuilds);
}
});

View file

@ -0,0 +1,220 @@
const Plugin = require('../plugin');
let getChannel, g_dc, g_cat, ha, disp, chanM, fm, reb, sv, cs, csp, ghp, gs, gsr, pf, sw = {}, g = {}, ai = {};
// copied from Discord's minified JS
function N(e,o,l,n){let r;r||(r="function"==typeof Symbol&&Symbol.for&&Symbol.for("react.element")||60103);const t=e&&e.defaultProps,f=arguments.length-3;if(o||0===f||(o={children:void 0}),o&&t)for(const e in t)void 0===o[e]&&(o[e]=t[e]);else o||(o=t||{});if(1===f)o.children=n;else if(f>1){const e=new Array(f);for(let o=0;o<f;o++)e[o]=arguments[o+3];o.children=e}return{$$typeof:r,type:e,key:void 0===l?null:""+l,ref:null,props:o,_owner:null}}
module.exports = new Plugin({
name: 'Hidden Channels',
description: 'Shows hidden channels and lets you view server permissions.',
color: 'magenta',
author: 'Joe 🎸#7070',
load: async function() {
disp = window.EDApi.findModule("dispatch");
getChannel = window.EDApi.findModule('getChannel').getChannel;
sw = window.EDApi.findModule('switchItem');
g = window.EDApi.findModule(m => m.group && m.item);
ai = window.EDApi.findModule('actionIcon');
const getUser = window.EDApi.findModule('getCurrentUser').getCurrentUser;
const getAllChannels = window.EDApi.findModule('getChannels').getChannels;
const can = window.EDApi.findModule('computePermissions').can;
g_dc = window.EDApi.findModule('getDefaultChannel');
window.EDApi.monkeyPatch(g_dc, 'getChannels', b => {
const og = b.callOriginalMethod(b.methodArguments);
if (!b.methodArguments[0]) return og;
const hidden = [], allChans = getAllChannels();
for (const i in allChans) {
if (allChans[i].guild_id === b.methodArguments[0]) {
if (allChans[i].type !== 4 && !can(1024, getUser(), getChannel(allChans[i].id))) {
hidden.push(allChans[i]);
}
}
}
og.HIDDEN = hidden;
return og;
});
chanM = window.EDApi.findModule(m => m.prototype && m.prototype.isManaged);
chanM.prototype.isHidden = function() {
return [0, 4, 5].includes(this.type) && !can(1024, getUser(), this);
}
g_cat = window.EDApi.findModule(m => m.getCategories && !m.EMOJI_NAME_RE);
window.EDApi.monkeyPatch(g_cat, 'getCategories', b => {
const og = b.callOriginalMethod(b.methodArguments);
const chs = g_dc.getChannels(b.methodArguments[0]);
chs.HIDDEN.forEach(c => {
const result = og[c.parent_id || "null"].filter(item => item.channel.id === c.id);
if (result.length) return; // already added
og[c.parent_id || "null"].push({channel: c, index: 0})
});
return og;
});
ha = window.EDApi.findModule('hasUnread').__proto__;
window.EDApi.monkeyPatch(ha, 'hasUnread', function(b) {
if (getChannel(b.methodArguments[0]) && getChannel(b.methodArguments[0]).isHidden())
return false; // don't show hidden channels as unread.
return b.callOriginalMethod(b.methodArguments);
});
window.EDApi.monkeyPatch(ha, 'hasUnreadPins', function(b) {
if (getChannel(b.methodArguments[0]) && getChannel(b.methodArguments[0]).isHidden())
return false; // don't show icon on hidden channel pins.
return b.callOriginalMethod(b.methodArguments);
});
disp.subscribe("CHANNEL_SELECT", module.exports.dispatchSubscription);
fm = window.EDApi.findModule("fetchMessages");
window.EDApi.monkeyPatch(fm, "fetchMessages", function(b) {
if (getChannel(b.methodArguments[0]) && getChannel(b.methodArguments[0]).isHidden()) return;
return b.callOriginalMethod(b.methodArguments);
});
const clk = window.EDApi.findModuleByDisplayName("Clickable")
//const icon = window.EDApi.findModuleByDisplayName("Icon");
reb = window.EDApi.findModule(m => m.default && m.default.prototype && m.default.prototype.renderEditButton).default.prototype;
/*window.EDApi.monkeyPatch(reb, "renderEditButton", function(b) {
return N(clk, {
className: ai.iconItem,
onClick: function() {
module.exports._editingGuild = null;
module.exports._editingChannel = b.thisObject.props.channel.id;
return b.thisObject.handleEditClick.apply(b.thisObject, arguments);
},
onMouseEnter: b.thisObject.props.onMouseEnter,
onMouseLeave: b.thisObject.props.onMouseLeave
}, void 0, N(icon, {
name: "Gear",
width: 16,
height: 16,
className: ai.actionIcon
}));
});*/
sv = window.EDApi.findModuleByDisplayName("SettingsView").prototype;
window.EDApi.monkeyPatch(sv, 'getPredicateSections', {before: b => {
const permSect = b.thisObject.props.sections.find(item => item.section === 'PERMISSIONS');
if (permSect) permSect.predicate = () => true;
}, silent: true});
cs = window.EDApi.findModuleByDisplayName("FluxContainer(ChannelSettings)").prototype;
window.EDApi.monkeyPatch(cs, 'render', b => {
const egg = b.callOriginalMethod(b.methodArguments);
egg.props.canManageRoles = true;
return egg;
});
csp = window.EDApi.findModuleByDisplayName("FluxContainer(ChannelSettingsPermissions)").prototype;
window.EDApi.monkeyPatch(csp, 'render', b => {
const egg = b.callOriginalMethod(b.methodArguments);
const chan = getChannel(egg.props.channel.id);
if (!chan || !chan.isHidden()) return egg;
egg.props.canSyncChannel = false;
egg.props.locked = true;
setTimeout(() => {
document.querySelectorAll('.'+g.group).forEach(elem => elem.style = "opacity: 0.5; pointer-events: none;");
});
return egg;
});
ghp = window.EDApi.findModuleByDisplayName("FluxContainer(GuildHeaderPopout)").prototype;
window.EDApi.monkeyPatch(ghp, 'render', b => {
const egg = b.callOriginalMethod(b.methodArguments);
egg.props.canAccessSettings = true;
return egg;
});
gs = window.EDApi.findModuleByDisplayName("FluxContainer(GuildSettings)").prototype;
window.EDApi.monkeyPatch(gs, 'render', b => {
const egg = b.callOriginalMethod(b.methodArguments);
module.exports._editingChannel = null;
module.exports._editingGuild = egg.props.guild.id;
egg.props.canManageRoles = true;
return egg;
});
const cancan = window.EDApi.findModuleByProps('can', 'canUser').can;
gsr = window.EDApi.findModuleByDisplayName("FluxContainer(GuildSettingsRoles)").prototype;
window.EDApi.monkeyPatch(gsr, 'render', b => {
const egg = b.callOriginalMethod(b.methodArguments);
const hasPerm = cancan(268435456, { guildId: egg.props.guild.id });
if (hasPerm) return;
setTimeout(() => {
document.querySelectorAll('.'+sw.switchItem).forEach(elem => elem.classList.add(sw.disabled));
});
return egg;
});
const getGuild = window.EDApi.findModule('getGuild').getGuild;
pf = window.EDApi.findModuleByDisplayName("PermissionsForm").prototype;
window.EDApi.monkeyPatch(pf, 'render', b => {
const egg = b.callOriginalMethod(b.methodArguments);
const guild = module.exports._editingGuild ? getGuild(module.exports._editingGuild) : null;
const channel = module.exports._editingChannel ? getChannel(module.exports._editingChannel) : null;
if (!guild && !channel) return egg;
const hasPerm = cancan(268435456, guild ? { guildId: guild.id } : { channelId: channel.id });
if (hasPerm) return;
if (!egg.props.children || !egg.props.children[1]) return egg;
egg.props.children[1].forEach(item => {item.disabled = true; item.props.disabled = true;});
return egg;
});
},
unload: function() {
g_dc.getChannels.unpatch();
g_cat.getCategories.unpatch();
ha.hasUnread.unpatch();
ha.hasUnreadPins.unpatch();
fm.fetchMessages.unpatch();
reb.renderEditButton.unpatch();
for (const mod of [sv, cs, csp, ghp, gs, gsr, pf])
if (mod && mod.render && mod.render.unpatch) mod.render.unpatch();
disp.unsubscribe("CHANNEL_SELECT", module.exports.dispatchSubscription);
},
dispatchSubscription: function (data) {
if (data.type !== "CHANNEL_SELECT") return;
if (getChannel(data.channelId) && getChannel(data.channelId).isHidden()) {
setTimeout(module.exports.attachHiddenChanNotice);
}
},
attachHiddenChanNotice: function () {
const messagesWrapper = document.querySelector(`.${window.EDApi.findModule("messages").messagesWrapper}`);
if (!messagesWrapper) return;
messagesWrapper.firstChild.style.display = "none"; // Remove messages shit.
messagesWrapper.parentElement.children[1].style.display = "none"; // Remove message box.
messagesWrapper.parentElement.parentElement.children[1].style.display = "none"; // Remove user list.
const toolbar = document.querySelector("."+window.EDApi.findModule(m => {
if (m instanceof Window) return;
if (m.toolbar && m.selected) return m;
}).toolbar);
toolbar.style.display = "none";
const hiddenChannelNotif = document.createElement("div");
// Class name modules
const txt = window.EDApi.findModule("h5");
const flx = window.EDApi.findModule("flex");
hiddenChannelNotif.className = flx.flexCenter;
hiddenChannelNotif.style.width = "100%";
hiddenChannelNotif.innerHTML = `
<div class="${flx.flex} ${flx.directionColumn} ${flx.alignCenter}">
<h2 class="${txt.h2} ${txt.defaultColor}">This is a hidden channel.</h2>
<h5 class="${txt.h5} ${txt.defaultColor}">You cannot see the contents of this channel. However, you may see its name and topic.</h5>
</div>`;
messagesWrapper.appendChild(hiddenChannelNotif);
}
});

View file

@ -0,0 +1,39 @@
const Plugin = require('../plugin');
module.exports = new Plugin({
name: 'Quick Save',
author: 'Joe 🎸#7070',
description: 'Use Ctrl+S or Cmd+S to save server, channel, or account settings.',
color: 'salmon',
load: async function() {
const hcModules = window.EDApi.findAllModules('hasChanges');
this._listener = function(e) {
if ((window.navigator.platform.match("Mac") ? e.metaKey : e.ctrlKey) && e.keyCode == 83) {
e.preventDefault();
const types = ['GUILD', 'CHANNEL', 'ACCOUNT', 'GUILD ROLES', 'CHANNEL OVERWRITES'];
let hasChanges = false;
for (const i in types) {
if (hcModules[i] && hcModules[i].hasChanges()) {
hasChanges = true;
//module.exports.log(`saving ${types[i]} settings`);
break;
}
}
if (!hasChanges) {
//module.exports.log('No setting changes detected. Aborting.');
return;
}
const saveButton = document.querySelector('[class*="lookFilled-"][class*="colorGreen-"]');
if (saveButton)
try { saveButton.click(); } catch(err) { module.exports.error(err); }
return;
}
}
document.addEventListener("keydown", this._listener, false);
},
unload: function() {
document.removeEventListener("keydown", this._listener);
delete this._listener;
}
});

View file

@ -0,0 +1,22 @@
const Plugin = require('../plugin');
module.exports = new Plugin({
name: 'Shut up, Clyde',
author: 'Joe 🎸#7070',
description: "Silences Clyde saying stupid shit about Nitro, for users that don't have it.",
color: '#7289da',
load: async function() {
const gg = window.EDApi.findModule(m => m.getChannelId && m.getGuildId && !m.getPings), bs = window.EDApi.findModule('Messages').Messages;
window.EDApi.monkeyPatch(window.EDApi.findModule('sendBotMessage'), 'sendBotMessage', function (b) {
if (gg.getGuildId() !== null) return; // don't send Clyde messages when looking at a server
const message = b.methodArguments[1];
if (message == bs.INVALID_ANIMATED_EMOJI_BODY_UPGRADE || message == bs.INVALID_ANIMATED_EMOJI_BODY || message == bs.INVALID_EXTERNAL_EMOJI_BODY_UPGRADE || message == bs.INVALID_EXTERNAL_EMOJI_BODY) return;
return b.callOriginalMethod(b.methodArguments);
});
},
unload: function() {
window.EDApi.findModule('sendBotMessage').sendBotMessage.unpatch();
}
});

View file

@ -0,0 +1,16 @@
const Plugin = require('../plugin');
module.exports = new Plugin({
name: 'Silent Typing',
author: 'Joe 🎸#7070',
description: `Never appear as typing in any channel.`,
color: 'grey',
disabledByDefault: true,
load: async function() {
window.EDApi.monkeyPatch(window.EDApi.findModule('startTyping'), 'startTyping', () => {});
},
unload: function() {
window.EDApi.findModule('startTyping').startTyping.unpatch();
}
});

View file

@ -0,0 +1,57 @@
@import url(https://enhanceddiscord.com/theme.css);
/* theme settings - uncomment (by removing the /*) and modify these lines in order to change things */
:root {
/* --bg: url(https://i.imgur.com/ybRUHPc.jpg); /* background for the entire window. Almost everything is transparent to this image. */
/* --bg-overlay: rgba(0, 0, 0, 0.8); /* overlay for the background. Generally, this should darken the picture to make text more readable. */
/* --accent-color: #900; /* color of buttons, misc. text, etc. */
/* --mention-color: #f00; /* color of mention text */
/* --mention-bg: rgba(255, 0, 0, 0.15); /* mention background color */
/* --mention-bgh: rgba(255, 0, 0, 0.4); /* mention backgroung while hovering over it */
/* --link-color: #faa61a; /* color of all links */
/* --link-color-hover: #fad61a; /* color of all links while hovering over them */
/* --tag-color: #fff; /* text color of tags (bot tags and custom) */
/* --tag-bg: rgba(255, 0, 0, 0.3); /* background of tags (bot tags and custom) */
/* --popup-background: #222; /* background of modals, confirmation messages etc. */
/* --popup-highlight: #333; /* color of headers and footers of "popouts" (linked to above) */
/* --context-menu-bg: #333; /* color of context (right-click) menus. */
/* --unread-color: #f00; /* color of unread/selected server indicators. */
}
/* nitrofag badge */
.profileBadgePremium-3kZ9Qj, .topSectionNormal-2-vo2m .profileBadgePremium-3kZ9Qj,
.profileBadgePremium-3kZ9Qj, .topSectionPlaying-1J5E4n .profileBadgePremium-3kZ9Qj {
background-image: url(https://discordapp.com/assets/9c252d28ca4980d65054a0258052983b.svg);
}
/* unread indicators */
.theme-dark .unread-2OHH1w:not(.selected-nT-gM3):before {
background: transparent;
opacity: 0.5;
height: 50px;
width: 50px;
left: 0px;
bottom: 0px;
top: 0px;
margin-top: 0;
border-radius: 50%;
border-top-right-radius: 5px;
border-bottom-left-radius: 5px;
/* box-shadow: 16px -16px 10px -14px #f00; */
/* background: -webkit-radial-gradient(transparent 15px, #f00 25px, transparent 40px); */
box-shadow: inset 0 0 12px 2px var(--unread-color);
}
.theme-dark .selected-nT-gM3:before {
background: var(--unread-color);
}
.theme-dark .unread-2OHH1w:hover::before {
border-radius: 15px;
border-top-right-radius: 5px;
border-bottom-left-radius: 5px;
transition: border-radius 150ms;
}
/* uncomment the following lines to hide gif picker */
/*button[aria-label="Open GIF picker"] {
display: none;
}*/

View file

@ -0,0 +1,78 @@
const Plugin = require('../plugin');
let gg = {}, gc = {}, gu = {}, cp = {}, lg = {}, gsc = {};
module.exports = new Plugin({
name: 'TagAll',
author: 'Joe 🎸#7070',
description: `Allows you to mention roles and channels you can't normally.`,
color: 'yellow',
load: async function() {
await this.sleep(1000); // wait for hidden channels to load
gg = window.EDApi.findModule('getGuild');
gc = window.EDApi.findModule('getChannels');
gu = window.EDApi.findModule('getCurrentUser');
cp = window.EDApi.findModule('computePermissions');
lg = window.EDApi.findModule('getLastSelectedGuildId');
gsc = window.EDApi.findModule('getChannel');
this.lis = function(e) {
let text = e.target.value;
const guildID = lg.getLastSelectedGuildId();
const g = gg.getGuild(guildID);
if (!guildID || !g || !text) return;
// mention unmentionable roles
const unMen = [];
for (const id in g.roles)
if (!g.roles[id].mentionable && !g.roles[id].managed) // ignore bot roles
unMen.push(g.roles[id]);
const roles = unMen.map(r => r.name.toLowerCase().replace(/[-[\]/{}()*+?.\\^$|]/g, "\\$&"));
for (const i in roles) {
if (!roles[i]) continue; // empty role names
try {
text = text.replace( new RegExp('@'+roles[i]+'([^#])', 'gi'), `<@&${unMen[i].id}>$1`);
} catch(err) {/*do nothing*/}
}
const hiddenChans = [];
if (window.ED._hiddenChans) { // work with "hidden channels" plugin
for (const i in window.ED._hiddenChans) {
const c = gsc.getChannel(window.ED._hiddenChans[i]);
if (c && c.guild_id === guildID) {
hiddenChans.push(gsc.getChannel(window.ED._hiddenChans[i]));
}
}
} else {
const globalChans = gc.getChannels();
const me = gu.getCurrentUser();
const hiddenChans = [];
for (const id in globalChans) {
if (globalChans[id].guild_id == guildID && !(cp.computePermissions(me, globalChans[id]) & 1024))
hiddenChans.push(globalChans[id]);
}
}
// mention channels you can't see
const chans = hiddenChans.map(c => c.name ? c.name.toLowerCase().replace(/[-[\]/{}()*+?.\\^$|]/g, "\\$&") : '');
for (const i in chans) {
if (!chans[i]) continue;
text = text.replace( new RegExp('#'+chans[i]+'(\\s)', 'gi'), `<#${hiddenChans[i].id}>$1`);
}
if (e.target.value == text) return;
e.target.value = text;
};
document.addEventListener("input", this.lis);
},
unload: function() {
document.removeEventListener("input", this.lis);
this.lis = null;
}
});

View file

@ -1,4 +1,4 @@
{
"optOut": false,
"lastUpdateCheck": 1586022981286
"lastUpdateCheck": 1587026795290
}

View file

@ -12,6 +12,19 @@ abbr --add --global cxmonad "nvim /home/leon/.xmonad/lib/Config.hs"
abbr --add --global gaa "git add --all"
abbr --add --global gc "git commit -m "
abbr --add --global gp "git push"
abbr --add --global gs "git status"
[ (hostname) = "garnix" ] && alias rm='echo "rm is disabled. Please use trash instead."; false'
function run_stuff
set -l commandline (commandline -b)
pipr --out-file /tmp/pipr_out --default "$commandline"
set -l result (cat /tmp/pipr_out)
commandline -r $result
commandline -f repaint
end
bind \ca run_stuff

View file

@ -23,12 +23,12 @@ shadow-opacity = 0.6;
shadow-exclude = [
"! name~=''",
"!focused",
"!focused && ! class_g ?='xfce4-notifyd'",
"name = 'Notification'",
"name = 'Plank'",
"name = 'Docky'",
"name = 'Kupfer'",
"name = 'xfce4-notifyd'",
#"name = 'xfce4-notifyd'",
"name *= 'VLC'",
"name *= 'compton'",
"name *= 'picom'",
@ -37,7 +37,7 @@ shadow-exclude = [
"class_g = 'Synapse'",
"class_g ?= 'Notify-osd'",
"class_g ?= 'Cairo-dock'",
"class_g ?= 'Xfce4-notifyd'",
#"class_g ?= 'Xfce4-notifyd'",
"class_g ?= 'Xfce4-power-manager'",
"_GTK_FRAME_EXTENTS@:c",
"_NET_WM_STATE@:32a *= '_NET_WM_STATE_HIDDEN'"
@ -46,7 +46,7 @@ shadow-exclude = [
shadow-ignore-shaped = false;
# }}}
# Opacity ------------------------------------- {{{
# Opacity and blur ------------------------------------- {{{
inactive-opacity = 1;
active-opacity = 1;
@ -61,6 +61,7 @@ inactive-opacity-override = false;
blur-background = true;
blur-method = "dual_kawase";
#blur-method = "kernel";
blur-strength = 10; # max 20
blur-size = 20;
# Blur background of opaque windows with transparent frames as well.
@ -68,7 +69,7 @@ blur-size = 20;
# Do not let blur radius adjust based on window opacity.
blur-background-fixed = false;
blur-background-exclude = [
"window_type = 'dock'",
#"window_type = 'dock'",
"window_type = 'desktop'",
"! name~=''",

View file

@ -37,7 +37,7 @@ alert = #bd2c40
[bar/main]
width = 100%
height = 27
height = 25
radius = 0.0
enable-ipc = true
padding = 0
@ -77,7 +77,7 @@ font-2 = FontAwesome5Free:style=Solid:size=10;1
modules-left = xmonad
modules-center = timerDisplay spotify mpd gitlab-pipeline player-mpv-tail
modules-right = pulseaudio-control updates-arch network-traffic info-pingrtt pulseaudio filesystem memory cpu date
modules-right = pulseaudio-control updates-arch network-traffic pulseaudio filesystem memory cpu date
tray-position = right
tray-padding = 2
@ -106,7 +106,7 @@ format-mounted = <label-mounted>
;format-mounted-prefix = "disk: "
;format-mounted-prefix-foreground = ${colors.foreground-alt}
format-mounted-prefix = "%{F#0fca42}  %{F-} "
format-mounted-underline = #0fca42
#format-mounted-underline = #0fca42
label-mounted = %{F#0a81f5}%mountpoint%%{F-}: %percentage_used%%
label-unmounted = %mountpoint% not mounted
label-unmounted-foreground = ${colors.foreground-alt}
@ -132,7 +132,7 @@ interval = 2
;format-prefix = "cpu: "
;format-prefix-foreground = ${colors.foreground-alt}
format-prefix = "%{F#f9a000}  %{F-} "
format-underline = #f9a000
#format-underline = #f9a000
label = %percentage:2%%
[module/memory]
@ -141,7 +141,7 @@ interval = 2
;format-prefix = "mem: "
;format-prefix-foreground = ${colors.foreground-alt}
format-prefix = "%{F#0a6cf5}  %{F-} "
format-underline = #0a6cf5
#format-underline = #0a6cf5
label = %percentage_used%%
[module/date]
@ -154,8 +154,8 @@ time-alt = %H:%M:%S
format-prefix = "%{F#fbff8c}  %{F-}"
;format-prefix-foreground = ${colors.foreground-alt}
;format-underline = #4bffdc
format-underline = #fbff8c
;#format-underline = #4bffdc
#format-underline = #fbff8c
label = %time% | %date%
[module/xmonad]
@ -206,7 +206,7 @@ format = "<label> %{A1:dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spo
exec = python ~/.config/polybar/polybar-scripts/spotify_status.py -f '{artist} - {song} {play_pause}'
click-left = "dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.PlayPause"
exec-if = "pgrep spotify"
format-underline = #1db954
#format-underline = #1db954
[module/updates-arch]

View file

@ -6,5 +6,5 @@
config_version: 2
settings:
content.notifications:
https://www.reddit.com: false
content.register_protocol_handler:
https://mail.google.com?extsrc=mailto&url=%25s: false

View file

@ -1,10 +1,10 @@
https://github.com/vizs/declutter-home GitHub - vizs/declutter-home: declutter your home directory!
https://unix.stackexchange.com/questions/125647/get-tmux-scroll-buffer-contents Get TMux scroll buffer contents - Unix & Linux Stack Exchange
https://github.com/Yucklys/polybar-nord-theme GitHub - Yucklys/polybar-nord-theme: A polybar configuration based on Nord colorscheme with support for multiple modules.
https://github.com/manilarome/the-glorious-dotfiles manilarome/the-glorious-dotfiles: A glorified dot files
https://github.com/srid/neuron/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22 Issues · srid/neuron
https://olkb.com/ OLKB
https://github.com/david-janssen/kmonad david-janssen/kmonad: An advanced keyboard manager
https://github.com/david-janssen/kmonad/blob/master/README.md#windows-limitations kmonad/README.md at master · david-janssen/kmonad
https://github.com/mojotech/json-type-validation mojotech/json-type-validation: TypeScript JSON type validation
https://bitbucket-student.it.hs-heilbronn.de/projects/LABAIB/repos/aib_labswp_2020_ss_studez_backend/pull-requests?create&sourceBranch=refs%2Fheads%2Ffeature%2FRUNDUM-93-ich-mochte-mich-anmelden&targetRepoId=551 Create Pull Request LabSWPS_AIB / AIB_LabSWP_2020_SS_StudEZ_Backend - Bitbucket für Studierende der HHN
https://github.com/raine/ramda-cli raine/ramda-cli: A CLI tool for processing data with functional pipelines
http://dbad-license.org/ DBAD by philsturgeon
https://github.com/rhysd/kiro-editor rhysd/kiro-editor: A terminal UTF-8 text editor written in Rust 📝🦀
http://brson.github.io/2016/11/30/starting-with-error-chain Starting a new Rust project right, with error-chain

View file

@ -26,10 +26,13 @@ config.bind("<Alt-j>", "scroll-px 0 40")
config.bind("<Alt-k>", "scroll-px 0 -40")
c.tabs.show = "multiple"
c.tabs.background = True
c.tabs.show_switching_delay = 1000
c.url.open_base_url = True
c.input.insert_mode.auto_enter = True
c.statusbar.hide = False
c.fonts.statusbar = "default_size Iosevka"
c.fonts.default_family = ["JetBrainsMono"]
@ -48,6 +51,7 @@ c.url.searchengines = {
"w": "https://wikipedia.org/wiki/Special:Search/{}"
}
# c.content.user_stylesheets = "user.css"
c.colors.webpage.prefers_color_scheme_dark = True
c.statusbar.padding = {"bottom": 1, "left": 8, "right": 8, "top": 1}

View file

@ -1,8 +1,8 @@
[FileDialog]
history=file:///home/leon
lastVisited=file:///home/leon
history=file:///home/leon/Downloads, file:///home/leon/coding/projects/pipr/target/release, file:///home/leon/Bilder/screenshots, file:///home/leon/coding/projects/pipr/target/x86_64-unknown-linux-musl/release, file:///home/leon/studium/Studium/Sem4/verteilteSysteme/Klausur2003
lastVisited=file:///home/leon/coding/projects/pipr/target/release
qtVersion=5.14.2
shortcuts=file:, file:///home/leon
sidebarWidth=98
treeViewHeader=@ByteArray(\0\0\0\xff\0\0\0\0\0\0\0\x1\0\0\0\0\0\0\0\0\x1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x1\xec\0\0\0\x4\x1\x1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x64\xff\xff\xff\xff\0\0\0\x81\0\0\0\0\0\0\0\x4\0\0\0\xff\0\0\0\x1\0\0\0\0\0\0\0?\0\0\0\x1\0\0\0\0\0\0\0@\0\0\0\x1\0\0\0\0\0\0\0n\0\0\0\x1\0\0\0\0\0\0\x3\xe8\0\xff\xff\xff\xff)
treeViewHeader=@ByteArray(\0\0\0\xff\0\0\0\0\0\0\0\x1\0\0\0\x1\0\0\0\x3\x1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x2\x94\0\0\0\x4\x1\x1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x64\xff\xff\xff\xff\0\0\0\x81\0\0\0\0\0\0\0\x4\0\0\0\xe6\0\0\0\x1\0\0\0\0\0\0\0?\0\0\0\x1\0\0\0\0\0\0\0@\0\0\0\x1\0\0\0\0\0\0\x1/\0\0\0\x1\0\0\0\0\0\0\x3\xe8\0\xff\xff\xff\xff)
viewMode=Detail

View file

@ -0,0 +1,29 @@
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track:vertical {
background: black;
border-left: 1px solid black;
}
::-webkit-scrollbar-thumb:vertical {
background: white;
border-left: 1px solid black;
}
::-webkit-scrollbar-track:horizontal {
background: white;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:horizontal {
background: white;
border-radius: 4px;
box-shadow: inset 0 0 8px black;
}
::-webkit-scrollbar-corner {
background: silver;
}

View file

@ -151,6 +151,7 @@ myStartupHook = do
spawnOnce "clipmenud"
spawn "xset r rate 300 50" -- make key repeat quicker
spawn "/home/leon/.config/polybar/launch.sh"
spawn "feh --bg-fill /home/leon/Bilder/wallpapers/wp2121816-mazda-miata-wallpapers.jpg"
setWMName "LG3D" -- Java stuff hack
-- }}}
@ -191,6 +192,7 @@ myKeys = [ ("M-+", sendMessage zoomIn)
, ("M-e", Rofi.promptRunCommand def specialCommands)
, ("M-C-e", Rofi.promptRunCommand def =<< defaultCommands )
, ("M-o", Rofi.promptRunCommand def withSelectionCommands)
, ("M-S-C-g", spawn "killall -INT -g giph" >> spawn "notify-send gif 'saved gif in ~/Bilder/gifs'") -- stop gif recording
] ++ generatedMappings
where
generatedMappings :: [(String, X ())]
@ -248,6 +250,7 @@ myKeys = [ ("M-+", sendMessage zoomIn)
[ ("screenshot", spawn $ scriptFile "screenshot.sh")
, ("screenshot to file", spawn $ scriptFile "screenshot.sh --tofile")
, ("screenshot full to file", spawn $ scriptFile "screenshot.sh --tofile --fullscreen")
, ("screengif to file", spawn (scriptFile "screengif.sh") >> spawn "notify-send gif 'stop gif-recording with M-S-C-g'")
, ("clipboard history", spawn $ "clipmenu")
, ("toggleOptimal", sendMessage ToggleGaps >> toggleWindowSpacingEnabled)
, ("toggleSpacing", toggleWindowSpacingEnabled)
@ -273,6 +276,7 @@ myManageHook = composeAll
[ resource =? "Dialog" --> ManageHelpers.doCenterFloat
, appName =? "pavucontrol" --> ManageHelpers.doCenterFloat
, className =? "mpv" --> ManageHelpers.doRectFloat (W.RationalRect 0.9 0.9 0.1 0.1)
, title =? "Something" --> doFloat
-- , isFullscreen --> doF W.focusDown <+> doFullFloat
, manageDocks
, namedScratchpadManageHook scratchpads
@ -297,7 +301,7 @@ main = do
myConfig dbus = desktopConfig
{ terminal = myTerminal
, modMask = myModMask
, borderWidth = 1
, borderWidth = 2
, layoutHook = myLayout
, logHook = myLogHook <+> dynamicLogWithPP (polybarPP dbus) <+> logHook def
, startupHook = myStartupHook <+> startupHook def <+> return () >> checkKeymap (myConfig dbus ) myKeys

Binary file not shown.

View file

@ -1,5 +1,5 @@
#!/bin/bash
pngfile=$(echo "$1" | sed 's/\.plantuml$/.png/g')
pngfile="${1//.plantuml/.png}"
function finish {
rm "$pngfile"

5
files/scripts/screengif.sh Executable file
View file

@ -0,0 +1,5 @@
#!/bin/bash
file="$HOME/Bilder/gifs/gif_$(date +%s).gif"
giph -s -l -c 1,1,1,0.3 -b 5 -p 5 "$file"
echo "$file" | xclip -selection clipboard

View file

@ -13,7 +13,6 @@ done
select_flag="-s"
[ $fullscreen -eq 1 ] && select_flag=""
if [ $to_file -eq 1 ]; then
file="$HOME/Bilder/screenshots/screenshot_$(date +%s).png"
echo "$file"