update gitignore

This commit is contained in:
Leon Kowarschick 2020-06-21 12:41:26 +02:00
parent a51720b560
commit 9f4c12af9a
221 changed files with 21696 additions and 0 deletions

21
.gitignore vendored Normal file
View file

@ -0,0 +1,21 @@
BAZECOR
chromium
dconf
discord
EOS-arch-news.conf
EOS-arch-news-for-you.conf
EOS-greeter.conf
EOS-initial-wallpaper.XFCE
pavucontrol.ini
ristretto
systemd
Thunar
user-dirs.dirs
user-dirs.locale
xfce4
yay
.stack-work
**/.surf/cache
xmonad-x86_64-linux
__pycache__
**/kitty/kitty-themes

72
files/.Xresources Normal file
View file

@ -0,0 +1,72 @@
*.background: #282828
color5: #ff0000
Xcursor.size: 16
Xcursor.theme: capitaine-cursors-light
Xft.autohint: 0
Xft.antialias: 1
Xft.hinting: true
Xft.hintstyle: hintslight
Xft.dpi: 96
Xft.rgba: rgb
Xft.lcdfilter: lcddefault
rofi.lines: 5
rofi.eh: 2
rofi.padding: 200
rofi.fullscreen: true
rofi.bw: 0
rofi.separator-style: none
rofi.hide-scrollbar: true
rofi.color-enabled: true
! 'background', 'border', 'separator'
rofi.color-window: argb:c82d303b, #7c8389, #1d1f21
! State: 'bg', 'fg', 'bgalt', 'hlbg', 'hlfg'
rofi.color-normal: argb:031d1f21, #f3f4f5, argb:031d1f21, argb:031d1f21, #9575cd
rofi.color-urgent: argb:031d1f21, #f3f4f5, argb:bc303541, argb:031d1f21, #9575cd
rofi.color-active: argb:031d1f21, #f3f4f5, argb:031d1f21, argb:031d1f21, #9575cd
rofi.font: System San Francisco Display 18
dzen.font: -*-fixed-medium-r-s*--12-87-*-*-*-*-iso10???-1
URxvt.font: xft:scientifica
!URxvt.color24: #076678
!URxvt.color66: #427b58
!URxvt.color88: #9d0006
!URxvt.color96: #8f3f71
!URxvt.color100: #79740e
!URxvt.color108: #8ec07c
!URxvt.color109: #83a598
!URxvt.color130: #af3a03
!URxvt.color136: #b57614
!URxvt.color142: #b8bb26
!URxvt.color167: #fb4934
!URxvt.color175: #d3869b
!URxvt.color208: #fe8019
!URxvt.color214: #fabd2f
!URxvt.color223: #ebdbb2
!URxvt.color228: #f2e5bc
!URxvt.color229: #fbf1c7
!URxvt.color230: #f9f5d7
!URxvt.color234: #1d2021
!URxvt.color235: #282828
!URxvt.color236: #32302f
!URxvt.color237: #3c3836
!URxvt.color239: #504945
!URxvt.color241: #665c54
!URxvt.color243: #7c6f64
!URxvt.color244: #928374
!URxvt.color245: #928374
!URxvt.color246: #a89984
!URxvt.color248: #bdae93
!URxvt.color250: #d5c4a1

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","enabled":false},"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: #282828;
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,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

@ -0,0 +1,353 @@
env:
# if not set, it supposedly check's if a alacritty entry exists and uses xterm-256color otherwise
# TERM: alacritty
TERM: xterm-256color
window:
#dimensions:
# columns: 0
# lines: 0
#position:
# x: 0
# y: 0
padding:
x: 20
y: 20
#Spread additional padding evenly around the terminal content.
dynamic_padding: true
#title: Alacritty
#class:
#instance: Alacritty
#general: Alacritty
gtk_theme_variant: None
scrolling:
history: 10000
multiplier: 3
#selection:
#semantic_escape_chars: ",│`|:\"' ()[]{}<>\t"
# When set to `true`, selected text will be copied to the primary clipboard.
#save_to_clipboard: false
# Allow terminal applications to change Alacritty's window title.
dynamic_title: true
cursor:
style: Block # ▇ Block, _ Underline, | Beam
unfocused_hollow: true
# Live config reload (changes require restart)
live_config_reload: true
shell:
program: /bin/fish
#args:
#- --login
mouse:
double_click: { threshold: 300 }
triple_click: { threshold: 300 }
hide_when_typing: true
url:
launcher:
program: xdg-open
# args: []
# These are the modifiers that need to be held down for opening URLs when clicking
# on them. The available modifiers are documented in the key binding section.
#modifiers: None
# Mouse bindings --------------------------------------------- {{{
# - `mouse`:
#
# - Middle
# - Left
# - Right
# - Numeric identifier such as `5`
#
# - `action` (see key bindings)
#
# And optionally:
#
# - `mods` (see key bindings)
#mouse_bindings:
# - { mouse: Middle, action: PasteSelection }
# }}}
# Key bindings -------------------------------------------- {{{
#
# Key bindings are specified as a list of objects. For example, this is the
# default paste binding:
#
# `- { key: V, mods: Control|Shift, action: Paste }`
#
# Each key binding will specify a:
#
# - `key`: Identifier of the key pressed
#
# - A-Z
# - F1-F24
# - Key0-Key9
#
# A full list with available key codes can be found here:
# https://docs.rs/glutin/*/glutin/event/enum.VirtualKeyCode.html#variants
#
# Instead of using the name of the keys, the `key` field also supports using
# the scancode of the desired key. Scancodes have to be specified as a
# decimal number. This command will allow you to display the hex scancodes
# for certain keys:
#
# `showkey --scancodes`.
#
# Then exactly one of:
#
# - `chars`: Send a byte sequence to the running application
#
# The `chars` field writes the specified string to the terminal. This makes
# it possible to pass escape sequences. To find escape codes for bindings
# like `PageUp` (`"\x1b[5~"`), you can run the command `showkey -a` outside
# of tmux. Note that applications use terminfo to map escape sequences back
# to keys. It is therefore required to update the terminfo when changing an
# escape sequence.
#
# - `action`: Execute a predefined action
#
# - Copy
# - Paste
# - PasteSelection
# - IncreaseFontSize
# - DecreaseFontSize
# - ResetFontSize
# - ScrollPageUp
# - ScrollPageDown
# - ScrollLineUp
# - ScrollLineDown
# - ScrollToTop
# - ScrollToBottom
# - ClearHistory
# - Hide
# - Minimize
# - Quit
# - ToggleFullscreen
# - SpawnNewInstance
# - ClearLogNotice
# - ReceiveChar
# - None
#
# (macOS only):
# - ToggleSimpleFullscreen: Enters fullscreen without occupying another space
#
# - `command`: Fork and execute a specified command plus arguments
#
# The `command` field must be a map containing a `program` string and an
# `args` array of command line parameter strings. For example:
# `{ program: "alacritty", args: ["-e", "vttest"] }`
#
# And optionally:
# - `mods`: Key modifiers to filter binding actions
# - Command
# - Control
# - Option
# - Super
# - Shift
# - Alt
#
# Multiple `mods` can be combined using `|` like this:
# `mods: Control|Shift`.
# Whitespace and capitalization are relevant and must match the example.
#
# - `mode`: Indicate a binding for only specific terminal reported modes
#
# This is mainly used to send applications the correct escape sequences
# when in different modes.
#
# - AppCursor
# - AppKeypad
# - Alt
#
# A `~` operator can be used before a mode to apply the binding whenever
# the mode is *not* active, e.g. `~Alt`.
#
# Bindings are always filled by default, but will be replaced when a new
# binding with the same triggers is defined. To unset a default binding, it can
# be mapped to the `ReceiveChar` action. Alternatively, you can use `None` for
# a no-op if you do not wish to receive input characters for that binding.
#
# If the same trigger is assigned to multiple actions, all of them are executed
# at once.
#key_bindings:
# (Windows, Linux, and BSD only)
#- { key: V, mods: Control|Shift, action: Paste }
#- { key: C, mods: Control|Shift, action: Copy }
#- { key: Insert, mods: Shift, action: PasteSelection }
#- { key: Key0, mods: Control, action: ResetFontSize }
#- { key: Equals, mods: Control, action: IncreaseFontSize }
#- { key: Add, mods: Control, action: IncreaseFontSize }
#- { key: Subtract, mods: Control, action: DecreaseFontSize }
#- { key: Minus, mods: Control, action: DecreaseFontSize }
# (Windows only)
#- { key: Return, mods: Alt, action: ToggleFullscreen }
#- { key: Paste, action: Paste }
#- { key: Copy, action: Copy }
#- { key: L, mods: Control, action: ClearLogNotice }
#- { key: L, mods: Control, chars: "\x0c" }
#- { key: PageUp, mods: Shift, action: ScrollPageUp, mode: ~Alt }
#- { key: PageDown, mods: Shift, action: ScrollPageDown, mode: ~Alt }
#- { key: Home, mods: Shift, action: ScrollToTop, mode: ~Alt }
#- { key: End, mods: Shift, action: ScrollToBottom, mode: ~Alt }
# }}}
#debug:
# Display the time it takes to redraw each frame.
#render_timer: false
# Keep the log file after quitting Alacritty.
#persistent_logging: false
# Log level
#
# Values for `log_level`:
# - None
# - Error
# - Warn
# - Info
# - Debug
# - Trace
#log_level: Warn
# Print all received window events.
#print_events: false
# schemes --------------------------------------------------------- {{{
schemes:
blueish: &blueish
colors:
# Default colors
primary:
background: '0x3f5163'
foreground: '0xe2efe6'
# Normal colors
normal:
black: '0x111111'
red: '0xa54242'
green: '0xa9b254'
yellow: '0xde935f'
blue: '0x1bcdee'
magenta: '0xbd88ce'
cyan: '0x5bc5b7'
white: '0xbceff7'
# Bright colors
bright:
black: '0xc5d2e6'
red: '0xff8484'
green: '0xebf39c'
yellow: '0xf4c76e'
blue: '0x97cfff'
magenta: '0xc3a2cd'
cyan: '0xa5f9ee'
white: '0xe7fcff'
dracula: &dracula
primary:
background: '#282a36'
foreground: '#f8f8f2'
normal:
black: '#000000'
red: '#ff5555'
green: '#50fa7b'
yellow: '#f1fa8c'
blue: '#caa9fa'
magenta: '#ff79c6'
cyan: '#8be9fd'
white: '#bfbfbf'
bright:
black: '#575b70'
red: '#ff6e67'
green: '#5af78e'
yellow: '#f4f99d'
blue: '#caa9fa'
magenta: '#ff92d0'
cyan: '#9aedfe'
white: '#e6e6e6'
gruvbox: &gruvbox
primary:
#background: '#1d2021' # hard contrast: background = '#1d2021'
background: '#282828' # hard contrast: background = '#1d2021'
foreground: '#ebdbb2' # soft contrast: background = '#32302f'
normal:
black: '#282828'
red: '#cc241d'
green: '#98971a'
yellow: '#d79921'
blue: '#458588'
magenta: '#b16286'
cyan: '#689d6a'
white: '#a89984'
bright:
black: '#928374'
red: '#fb4934'
green: '#b8bb26'
yellow: '#fabd2f'
blue: '#83a598'
magenta: '#d3869b'
cyan: '#8ec07c'
white: '#ebdbb2'
onedark: &onedark
primary:
background: '#282c34'
foreground: '#abb2bf'
normal:
black: '#282c34' # NOTE: Use '#131613' for the `black` color if you'd like to see black text on the background.
red: '#e06c75'
green: '#98c379'
yellow: '#d19a66'
blue: '#61afef'
magenta: '#c678dd'
cyan: '#56b6c2'
white: '#abb2bf'
bright:
black: '#5c6370'
red: '#e06c75'
green: '#98c379'
yellow: '#d19a66'
blue: '#61afef'
magenta: '#c678dd'
cyan: '#56b6c2'
white: '#ffffff'
# }}}
# https://github.com/alacritty/alacritty/wiki/Color-schemes
colors: *gruvbox
#background_opacity: 0.95
background_opacity: 1.0
font:
#size: 10
size: 12
normal:
#family: JetBrainsMono Nerd Font
#family: Iosevka Term
#family: cozette
family: Terminus (TTF)
#family: cherry
#family: lucy tewi2a
#family: Scientifica
offset:
x: 0
y: 0

View file

@ -0,0 +1,25 @@
#!/bin/bash
case $1 in "-a") PROMPT="Goto:"; MODE="go" ;; "-R") PROMPT="Bring:"; MODE="bring" ;; esac
if [ -n "$2" ]; then
WINDOW="$2"
else
WINDOW=$(paste \
<(xdotool search .) \
<(xdotool search . get_desktop_for_window %@ 2> /dev/null) \
<(xdotool search . getwindowname %@) |\
awk '{FS="\t"; if($2 != -1) printf "%10d [%d] %s\n",$1,$2+1,$3}' |\
vmenu --no-refocus -p $PROMPT |\
sed 's/^ *//g' |\
cut -d ' ' -f 1)
fi
if [ -n "$WINDOW" ]; then
if [ bring = $MODE ]; then
if DESK=$(xdotool get_desktop 2> /dev/null); then
xdotool set_desktop_for_window "$WINDOW" $DESK
sleep 0.005 # wait for wm to notice
fi
fi
xdotool windowmap "$WINDOW" windowactivate "$WINDOW" windowfocus "$WINDOW" windowraise "$WINDOW"
fi

View file

@ -0,0 +1,11 @@
#!/bin/bash
if [ -z "$1" ]; then
echo "Usage: $0 <name of hidden scratchpad window>"
exit 1
fi
pids=$(xdotool search --class "${1}")
for pid in $pids; do
echo "Toggle $pid"
bspc node "$pid" --flag hidden -f
done

View file

@ -0,0 +1,107 @@
#!/bin/bash
# Config
# ======
# Where to look for wid files:
TMPDIR="$XDG_RUNTIME_DIR/drawers.wids/"
# Find and show/hide the window if it exists
# ==========================================
NAME=$1
if [[ -e $TMPDIR$NAME ]]; then
read -r WINDOW < "$TMPDIR$NAME"
# Window exists? Show/hide it and we're done.
if xdotool getwindowname "$WINDOW" &> /dev/null; then
if xdotool search --onlyvisible . | grep -q "$WINDOW"; then
xdotool windowminimize "$WINDOW"
else
~/.config/bspwm/bspwm_scripts/bringwindow -R "$WINDOW"
fi
exit
fi
fi
if [[ $# -lt 5 ]]; then
echo "Not enough args to launch a new $NAME."
exit 1
fi
# No window to show/hide, better create it. Do setup for that.
# ============================================================
# no xinerama for now
CFG=($(xdotool getdisplaygeometry))
SCR_WIDTH=${CFG[0]}
SCR_HEIGHT=${CFG[1]}
SCR_LEFT=0 #${CFG[2]}
SCR_TOP=0 #${CFG[3]}
SIDE=$2
WIDTH=$3
HEIGHT=$4
shift 4
# Handle fractions of screen size for width and height
# ====================================================
if [[ $WIDTH == *% ]]; then
WIDTH=${WIDTH:0:-1} # chomp '%'
WIDTH=$(( (WIDTH*10*SCR_WIDTH)/1000 ))
fi
if [[ $HEIGHT == *% ]]; then
HEIGHT=${HEIGHT:0:-1} # chomp '%'
HEIGHT=$(( (HEIGHT*10*SCR_HEIGHT)/1000 ))
fi
# Figure out where to put the window
# ==================================
TOP_ADJ=$(( (SCR_HEIGHT-HEIGHT)/2 ))
LEFT_ADJ=$(( (SCR_WIDTH-WIDTH)/2 ))
case $SIDE in
"left")
LEFT=$SCR_LEFT
TOP=$(( SCR_TOP + TOP_ADJ ))
;;
"right")
LEFT=$(( SCR_WIDTH - WIDTH ))
TOP=$(( SCR_TOP + TOP_ADJ ))
;;
"bottom")
LEFT=$(( SCR_LEFT + LEFT_ADJ ))
TOP=$(( SCR_HEIGHT - HEIGHT ))
;;
"top")
LEFT=$(( SCR_LEFT + LEFT_ADJ ))
TOP=$SCR_TOP
esac
# Create the window
# =================
$@ &
# await new window:
countWins() {
xdotool search --onlyvisible . 2> /dev/null | wc -l
}
WIN_CNT="$(countWins)"
while [[ $(countWins) = "$WIN_CNT" ]]; do sleep 0.1; done
sleep 0.25
# new window should now be active, make it our window:
WINDOW="$(xdotool getactivewindow)"
xdotool set_window --role "drawer" $WINDOW
mkdir -p "$TMPDIR"
echo "$WINDOW" > "$TMPDIR$NAME"
border_width="$(xgetres awesome border_width)"
which awesome-client &> /dev/null && echo "client.focus.floating = true; client.focus.border_width=$border_width" | awesome-client
xdotool windowmove $WINDOW $LEFT $TOP windowsize $WINDOW $WIDTH $HEIGHT windowfocus $WINDOW windowraise $WINDOW

View file

@ -0,0 +1,14 @@
#!/bin/bash
options="screenshot\nscreengif"
selected="$(echo -e "$options" | rofi -dmenu -i -theme ~/scripts/rofi-scripts/default_theme.rasi)"
case "$selected" in
screenshot)
~/scripts/screenshot.sh
;;
screengif)
notify-send gif "press M-S-C-g to end gif"
~/scripts/screengif.sh
;;
esac

View file

@ -0,0 +1,2 @@
#!/bin/bash
pgrep "$@" > /dev/null || ("$@" &)

View file

@ -0,0 +1,2 @@
#!/bin/dash
appres "$1" | grep "$2" | cut -f 2-

34
files/.config/bspwm/bspwmrc Executable file
View file

@ -0,0 +1,34 @@
#! /bin/sh
sxhkd &
killall -q picom && picom --config ~/.config/picom.conf --experimental-backends --no-fading-openclose &
killall -q pasystray && pasystray &
killall -q nm-applet && nm-applet &
killall -q clipmenud && clipmenud &
xset r rate 300 50 &
/home/leon/.config/polybar/launch.sh &
feh --bg-fill /home/leon/Bilder/wallpapers/mountains_with_clounds.jpg &
bspc monitor -d 1 2 3 4 5 6 7 8 9 10
bspc config border_width 2
bspc config window_gap 14
bspc config focus_follows_pointer true
bspc config split_ratio 0.50
bspc config borderless_monocle false
bspc config gapless_monocle false
bspc config automatic_scheme alternate
bspc rule -a mplayer2 state=floating
bspc rule -a Kupfer.py focus=on
bspc rule -a Screenkey manage=off
bspc rule -a feh state=floating
bspc rule -a Sxiv state=floating
#bspc rule -a kitty_scratchpad sticky=on state=floating hidden=on
#kitty --class kitty_scratchpad &

View file

@ -0,0 +1,3 @@
{
"lastUpdate": 1584601643868
}

View file

@ -0,0 +1 @@
{"dependencies":{}}

View file

@ -0,0 +1 @@
{}

View file

@ -0,0 +1,4 @@
{
"optOut": false,
"lastUpdateCheck": 1592658266558
}

View file

@ -0,0 +1,7 @@
# Path to Oh My Fish install.
set -q XDG_DATA_HOME
and set -gx OMF_PATH "$XDG_DATA_HOME/omf"
or set -gx OMF_PATH "$HOME/.local/share/omf"
# Load Oh My Fish configuration.
source $OMF_PATH/init.fish

View file

@ -0,0 +1,77 @@
fish_vi_key_bindings
# fish_default_key_bindings
# disable truecolor for dvtm
# set -e fish_term24bit
#source /home/leon/.config/fish/gruvbox-colors.fish
#set -U FZF_TMUX 1
set -U FZF_DEFAULT_COMMANDS "--filepath-word --cycle"
set -U FZF_PREVIEW_FILE_CMD "head -n 10 | bat --color=always --decorations=never"
set -U fish_greeting
#function fish_greeting
#end
alias ls=lsd
alias tcolors="env TERM=xterm-256color tcolors"
abbr --add --global vim nvim
abbr --add --global tsh trash
#abbr --add --global clear "clear && ls"
abbr --add --global cxmonad "nvim /home/leon/.xmonad/lib/Config.hs"
#if status is-interactive
#and not set -q TMUX
#exec tmux
#end
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
function c
set -l result (/home/leon/scripts/conf)
commandline -r "$result"
commandline -f execute
end
bind \ca run_stuff
function replace_with_yay
set -l cmd (commandline -b)
switch $cmd
case "*pacman*"
set edited (echo $cmd | sed 's/sudo //g' | sed 's/pacman/yay/g')
case "yay*"
set edited (echo $cmd | sed 's/yay/sudo pacman/g')
end
commandline -r "$edited"
commandline -f repaint
end
bind \cy replace_with_yay
# fff file manager cd on exit
function f
fff $argv
set -q XDG_CACHE_HOME; or set XDG_CACHE_HOME $HOME/.cache
cd (cat $XDG_CACHE_HOME/fff/.fff_d)
end
set -x EDITOR "nvim"
set -x FFF_TRASH_CMD "trash" # make fff's trash function use trash-cli

View file

@ -0,0 +1,50 @@
# This file contains fish universal variable definitions.
# VERSION: 3.0
SETUVAR FZF_DEFAULT_COMMANDS:\x2d\x2dfilepath\x2dword\x20\x2d\x2dcycle
SETUVAR FZF_DEFAULT_OPTS:\x2d\x2dheight\x2040\x25
SETUVAR FZF_ENABLE_OPEN_PREVIEW:1
SETUVAR FZF_LEGACY_KEYBINDINGS:0
SETUVAR FZF_PREVIEW_DIR_CMD:ls
SETUVAR FZF_PREVIEW_FILE_CMD:head\x20\x2dn\x2010\x20\x7c\x20bat\x20\x2d\x2dcolor\x3dalways\x20\x2d\x2ddecorations\x3dnever
SETUVAR FZF_TMUX:1
SETUVAR FZF_TMUX_HEIGHT:40\x25
SETUVAR SXHKD_SHELL:sh
SETUVAR __fish_initialized:3100
SETUVAR dangerous_colors:000000\x1e083743\x1e445659\x1efdf6e3\x1eb58900\x1ecb4b16\x1edc121f\x1eaf005f\x1e6c71c4\x1e268bd2\x1e2aa198\x1e859900
SETUVAR dangerous_cursors:\x5c033\x5d12\x3b\x23268bd2\x5c007\x1e\x5c033\x5d12\x3b\x23b58900\x5c007\x1e\x5c033\x5d12\x3b\x23af005f\x5c007\x1e\x5c033\x5d12\x3b\x236c71c4\x5c007
SETUVAR dangerous_day:000000\x1e333333\x1e666666\x1effffff\x1effff00\x1eff6600\x1eff0000\x1eff0033\x1e3300ff\x1e00aaff\x1e00ffff\x1e00ff00
SETUVAR dangerous_night:000000\x1e083743\x1e445659\x1efdf6e3\x1eb58900\x1ecb4b16\x1edc121f\x1eaf005f\x1e6c71c4\x1e268bd2\x1e2aa198\x1e859900
SETUVAR dangerous_nocmdhist:c\x1ed\x1ell\x1els\x1em\x1es
SETUVAR dangerous_pwdstyle:short\x1elong\x1enone
SETUVAR dangerous_sessions_active:\x1d
SETUVAR dangerous_sessions_active_pid:\x1d
SETUVAR fish_color_autosuggestion:555
SETUVAR fish_color_cancel:normal
SETUVAR fish_color_command:0087d7
SETUVAR fish_color_comment:990000
SETUVAR fish_color_cwd:008000
SETUVAR fish_color_cwd_root:800000
SETUVAR fish_color_end:009900
SETUVAR fish_color_error:ff0000
SETUVAR fish_color_escape:00a6b2
SETUVAR fish_color_history_current:normal
SETUVAR fish_color_host:normal
SETUVAR fish_color_host_remote:yellow
SETUVAR fish_color_match:normal
SETUVAR fish_color_normal:normal
SETUVAR fish_color_operator:00a6b2
SETUVAR fish_color_param:0087af
SETUVAR fish_color_quote:999900
SETUVAR fish_color_redirection:00afff
SETUVAR fish_color_search_match:ffff00
SETUVAR fish_color_selection:c0c0c0
SETUVAR fish_color_status:red
SETUVAR fish_color_user:00ff00
SETUVAR fish_color_valid_path:normal
SETUVAR fish_greeting:\x1d
SETUVAR fish_key_bindings:fish_vi_key_bindings
SETUVAR fish_pager_color_completion:normal
SETUVAR fish_pager_color_description:B3A06D\x1eyellow
SETUVAR fish_pager_color_prefix:white\x1e\x2d\x2dbold\x1e\x2d\x2dunderline
SETUVAR fish_pager_color_progress:brwhite\x1e\x2d\x2dbackground\x3dcyan
SETUVAR fish_user_paths:/home/leon/\x2efzf/bin

View file

@ -0,0 +1 @@
/home/leon/.local/share/omf/themes/lambda_better/fish_prompt.fish

View file

@ -0,0 +1,2 @@
function fish_user_key_bindings
end

View file

@ -0,0 +1 @@
/home/leon/.local/share/omf/themes/lambda/fish_prompt.fish

View file

@ -0,0 +1,67 @@
function fish_prompt
# Cache exit status
set -l last_status $status
set -l normal (set_color normal)
set -l white (set_color FFFFFF)
set -l turquoise (set_color 5fdfff)
set -l orange (set_color df5f00)
set -l hotpink (set_color df005f)
set -l blue (set_color blue)
set -l limegreen (set_color 87ff00)
set -l purple (set_color af5fff)
# Configure __fish_git_prompt
set -g __fish_git_prompt_char_stateseparator ' '
set -g __fish_git_prompt_color 5fdfff
set -g __fish_git_prompt_color_flags df5f00
set -g __fish_git_prompt_color_prefix white
set -g __fish_git_prompt_color_suffix white
set -g __fish_git_prompt_showdirtystate true
set -g __fish_git_prompt_showuntrackedfiles true
set -g __fish_git_prompt_showstashstate true
set -g __fish_git_prompt_show_informative_status true
set -l current_user (whoami)
set -l vi_mode (__fish_vi_mode_prompt_real)
set -l git_prompt (__fish_git_prompt " (%s)")
#(pwd|sed "s=$HOME=~=")
set -g fish_prompt_pwd_dir_length 1
echo -n $white'╭─'$vi_mode
echo -n $white'─'$hotpink$current_user$white' in '$limegreen(prompt_pwd)
echo -n $turquoise$git_prompt
if test $last_status -gt 0
echo -n ' '$hotpink$last_status
end
echo
echo -n $white'╰─λ '
echo -n $normal
end
function __fish_vi_mode_prompt_real
set -l turquoise (set_color 5fdfff)
set -l orange (set_color df5f00)
switch $fish_bind_mode
case insert
echo -n "─"
case default
echo -n $turquoise'N'
case visual
echo -n $orange'V'
case replace_one
echo -n $turquoise'R'
end
end
# needed so fish doesn't draw it by itself
function fish_mode_prompt
end
# ⌁

View file

@ -0,0 +1,27 @@
set -l orange '#fe8019'
set -l aqua '#8ec07c'
set -l blue '#83a598'
set -l limegreen '#b8bb26'
set -l purple '#d3869b'
set -l aqua '#8ec07c'
set -l gwhite '#ebdbb2'
set -l gray2 '#665c54'
set -l purple '#d3869b'
set -l gray '#a89984'
set -l yellow '#fabd2f'
#set -U fish_color_command $blue
#set -U fish_color_normal $gwhite
#set -U fish_color_quote $aqua
#set -U fish_color_redirection $orange
#set -U fish_color_param $aqua
#set -U fish_color_comment $gray
#set -U fish_color_match $yellow
#set -U fish_color_search_match $aqua
#set -U fish_color_autosuggestion $gray2
#set -U fish_color_cancel $aqua
#set fish_color_selection
#set fish_color_end
#set fish_color_error
#set fish_color_operator

View file

@ -0,0 +1,2 @@
file:///home/leon/coding/projects
file:///home/leon/studium/Studium

View file

@ -0,0 +1,77 @@
@define-color theme_fg_color #eff0f1;
@define-color theme_bg_color #31363b;
@define-color theme_text_color #eff0f1;
@define-color theme_base_color #232629;
@define-color theme_view_hover_decoration_color #3daee9;
@define-color theme_hovering_selected_bg_color #3daee9;
@define-color theme_selected_bg_color #3daee9;
@define-color theme_selected_fg_color #eff0f1;
@define-color theme_view_active_decoration_color #3daee9;
@define-color theme_button_background_normal #31363b;
@define-color theme_button_decoration_hover #3daee9;
@define-color theme_button_decoration_focus #3daee9;
@define-color theme_button_foreground_normal #eff0f1;
@define-color theme_button_foreground_active #eff0f1;
@define-color borders #606468;
@define-color warning_color #f67400;
@define-color success_color #27ae60;
@define-color error_color #da4453;
@define-color theme_unfocused_fg_color #eff0f1;
@define-color theme_unfocused_text_color #eff0f1;
@define-color theme_unfocused_bg_color #31363b;
@define-color theme_unfocused_base_color #232629;
@define-color theme_unfocused_selected_bg_color_alt #224e65;
@define-color theme_unfocused_selected_bg_color #224e65;
@define-color theme_unfocused_selected_fg_color #eff0f1;
@define-color theme_button_background_backdrop #31363b;
@define-color theme_button_decoration_hover_backdrop #3daee9;
@define-color theme_button_decoration_focus_backdrop #3daee9;
@define-color theme_button_foreground_backdrop #eff0f1;
@define-color theme_button_foreground_active_backdrop #eff0f1;
@define-color unfocused_borders #606468;
@define-color warning_color_backdrop #f67400;
@define-color success_color_backdrop #27ae60;
@define-color error_color_backdrop #da4453;
@define-color insensitive_fg_color #6e7175;
@define-color insensitive_base_fg_color #65686a;
@define-color insensitive_bg_color #2e3338;
@define-color insensitive_base_color #212427;
@define-color insensitive_selected_bg_color #2e3338;
@define-color insensitive_selected_fg_color #6e7175;
@define-color theme_button_background_insensitive #2e3338;
@define-color theme_button_decoration_hover_insensitive #325b72;
@define-color theme_button_decoration_focus_insensitive #325b72;
@define-color theme_button_foreground_insensitive #6e7175;
@define-color theme_button_foreground_active_insensitive #6e7175;
@define-color insensitive_borders #3e4347;
@define-color warning_color_insensitive #683e19;
@define-color success_color_insensitive #225139;
@define-color error_color_insensitive #5e2e35;
@define-color insensitive_unfocused_fg_color #6e7175;
@define-color theme_unfocused_view_text_color #65686a;
@define-color insensitive_unfocused_bg_color #2e3338;
@define-color theme_unfocused_view_bg_color #212427;
@define-color insensitive_unfocused_selected_bg_color #2e3338;
@define-color insensitive_unfocused_selected_fg_color #6e7175;
@define-color theme_button_background_backdrop_insensitive #2e3338;
@define-color theme_button_decoration_hover_backdrop_insensitive #325b72;
@define-color theme_button_decoration_focus_backdrop_insensitive #325b72;
@define-color theme_button_foreground_backdrop_insensitive #6e7175;
@define-color theme_button_foreground_active_backdrop_insensitive #6e7175;
@define-color unfocused_insensitive_borders #3e4347;
@define-color warning_color_insensitive_backdrop #683e19;
@define-color success_color_insensitive_backdrop #225139;
@define-color error_color_insensitive_backdrop #5e2e35;
@define-color link_color #2980b9;
@define-color link_visited_color #7f8c8d;
@define-color tooltip_text #eff0f1;
@define-color tooltip_background #31363b;
@define-color tooltip_border #606468;
@define-color content_view_bg #232629;
@define-color theme_titlebar_background rgb(49,54,59);
@define-color theme_titlebar_foreground rgb(239,240,241);
@define-color theme_titlebar_background_light #31363b;
@define-color theme_titlebar_foreground_backdrop rgb(127,140,141);
@define-color theme_titlebar_background_backdrop rgb(49,54,59);
@define-color theme_titlebar_foreground_insensitive rgb(127,140,141);
@define-color theme_titlebar_foreground_insensitive_backdrop rgb(127,140,141);

View file

@ -0,0 +1,8 @@
.termite {
padding: 15px;
}
vte-terminal {
padding: 10px;
}
@import 'colors.css';

View file

@ -0,0 +1,25 @@
[Settings]
gtk-button-images=1
gtk-cursor-theme-name=capitaine-cursors-light
gtk-cursor-theme-size=0
gtk-enable-event-sounds=0
gtk-enable-input-feedback-sounds=0
gtk-font-name=Sans 9
gtk-icon-theme-name=Arc-X-D
gtk-menu-images=1
;gtk-theme-name=Adwaita-dark
gtk-theme-name=phocus
gtk-toolbar-icon-size=GTK_ICON_SIZE_LARGE_TOOLBAR
gtk-toolbar-style=GTK_TOOLBAR_ICONS
gtk-xft-antialias=1
gtk-xft-hinting=1
gtk-xft-hintstyle=hintslight
gtk-xft-rgba=rgb
gtk-application-prefer-dark-theme=true
gtk-decoration-layout=:
;gtk-font-name = DejaVu Sans 1 121
;gtk-font-name = cozette 10
gtk-font-name = xos4 Terminus Regular 12
;gtk-font-name = Terminus 12
;gtk-font-name = cozette 10

26
files/.config/htop/htoprc Normal file
View file

@ -0,0 +1,26 @@
# Beware! This file is rewritten by htop when settings are changed in the interface.
# The parser is also very primitive, and not human-friendly.
fields=0 48 17 18 38 39 40 2 46 47 49 1
sort_key=47
sort_direction=1
hide_threads=1
hide_kernel_threads=1
hide_userland_threads=1
shadow_other_users=0
show_thread_names=0
show_program_path=1
highlight_base_name=0
highlight_megabytes=1
highlight_threads=1
tree_view=0
header_margin=1
detailed_cpu_time=0
cpu_count_from_zero=0
update_process_names=0
account_guest_in_cpu_meter=0
color_scheme=0
delay=15
left_meters=AllCPUs Memory Swap
left_meter_modes=1 1 1
right_meters=Tasks LoadAverage Uptime
right_meter_modes=2 2 2

View file

@ -0,0 +1 @@
/home/leon/.config/kitty/kitty-themes/themes/gruvbox_dark.conf

View file

@ -0,0 +1,68 @@
include ./gruvbox.conf
#include ./onedark.conf
#shell tmux
#background_opacity 0.95
background_opacity 1
#font_family VictorMono Nerd Font Semibold
#font_family DejaVuSansMono Nerd
font_family Iosevka Nerd Font
#font_family JetbrainsMono Nerd Font
bold_font auto
italic_font auto
bold_italic_font auto
#font_family monospace
font_size 13
enable_audio_bell no
draw_minimal_borders yes
window_padding_width 0
window_margin_width 10
tab_bar_style powerline
hide_window_decorations yes
placement_strategy top-left
clipboard_control write-clipboard write-primary read-primary read-clipboard
dynamic_background_opacity yes
allow_remote_control yes
sync_to_monitor yes
active_tab_background #6272a4
active_tab_foreground #f8f8f2
inactive_tab_background #44475a
inactive_tab_foreground #f8f8f2
# Keymaps {{{
# Windows
map kitty_mod+enter new_window_with_cwd
map ctrl+shift+left neighboring_window left
map ctrl+shift+down neighboring_window down
map ctrl+shift+right neighboring_window right
map ctrl+shift+up neighboring_window up
# tabs
#map kitty_mod+t new_tab
map kitty_mod+n new_tab_with_cwd
map ctrl+shift+l next_tab
map ctrl+shift+h previous_tab
# new os window
map kitty_mod+backspace new_os_window_with_cwd
# Other
map ctrl+shift+plus change_font_size all +2.0
map ctrl+shift+alt+h show_scrollback
# --type=overlay
# --stdin-source=@screen_scrollback
# https://sw.kovidgoyal.net/kitty/launch.html
map ctrl+shift+space launch --stdin-source=@screen --stdin-add-formatting --type=window /home/leon/scripts/autocompleteWords.sh @active-kitty-window-id
# }}}

View file

@ -0,0 +1,21 @@
background #282c34
foreground #c4c8c5
cursor #d0d0d0
selection_background #444444
selection_foreground #161718
color0 #000000
color1 #fc5ef0
color2 #86c38a
color3 #ffd6b1
color4 #85befd
color5 #b9b5fc
color6 #85befd
color7 #dfdfdf
color8 #808080
color9 #fc5ef0
color10 #94f936
color11 #f5ffa7
color12 #95cbfe
color13 #b9b5fc
color14 #85befd
color15 #dfdfdf

View file

@ -0,0 +1,19 @@
<!DOCTYPE Menu PUBLIC "-//freedesktop//DTD Menu 1.0//EN"
"http://www.freedesktop.org/standards/menu-spec/menu-1.0.dtd">
<!-- Do not edit manually - generated and managed by xdg-desktop-menu -->
<Menu>
<Name>Applications</Name>
<Menu>
<Name>chrome-apps</Name>
<Directory>chrome-apps.directory</Directory>
<Include>
<Filename>chrome-jjphmlaoffndcnecccgemfdaaoighkel-Default.desktop</Filename>
<Filename>chrome-ooiklbnjmhbcgemelgfhaeaocllobloj-Default.desktop</Filename>
<Filename>chrome-njhehnieenekbompacofnhlljnobgcga-Default.desktop</Filename>
<Filename>chrome-hmjkmjkepdijhoojdojkdfohbdgmmhki-Default.desktop</Filename>
<Filename>chrome-mjcnijlhddpbdemagnpefmlkjdagkogk-Default.desktop</Filename>
<Filename>chrome-gbchcmhmhahfdphkhkmpfmihenigjmpp-Default.desktop</Filename>
<Filename>chrome-hnpfjngllnobngcgfapefoaidbinmjnm-Default.desktop</Filename>
</Include>
</Menu>
</Menu>

View file

@ -0,0 +1,14 @@
[:0.0]
file=/home/leon/Bilder/wallpapers/wallhaven-ox2gr9.jpg
mode=5
bgcolor=#000000
[xin_0]
file=/home/leon/Bilder/wallpapers/green_leaves.jpg
mode=5
bgcolor=#000000
[xin_1]
file=/home/leon/Bilder/wallpapers/green_leaves.jpg
mode=5
bgcolor=#000000

View file

@ -0,0 +1,12 @@
[geometry]
posx=20
posy=50
sizex=1297
sizey=696
[nitrogen]
view=icon
recurse=true
sort=alpha
icon_caps=false
dirs=/home/leon/Bilder/wallpapers;

178
files/.config/picom.conf Normal file
View file

@ -0,0 +1,178 @@
# Thank you code_nomad: http://9m.no/ꪯ鵞
# and Arch Wiki contributors: https://wiki.archlinux.org/index.php/Compton
# Backend --------------------- {{{
# Backend to use: "xrender" or "glx".
# GLX backend is typically much faster but depends on a sane driver.
backend = "glx";
glx-no-stencil = true;
glx-copy-from-front = false;
# }}}
# Shadows -------------------------------- {{{
shadow = true;
#shadow-radius = 20;
#shadow-offset-x = -20;
#shadow-offset-y = -20;
#hadow-radius = 3;
#shadow-offset-x = 3;
#shadow-offset-y = 3;
#shadow-opacity = 0.6;
shadow-radius = 18;
shadow-offset-x = -14;
shadow-offset-y = -10;
shadow-opacity = 1.0;
# shadow-red = 0.0;
# shadow-green = 0.0;
# shadow-blue = 0.0;
shadow-exclude = [
"! name~=''",
#"!focused && ! class_g ?='xfce4-notifyd'",
#"name *= 'polybar'",
"name = 'Notification'",
"name = 'Plank'",
"name = 'Docky'",
"name = 'Kupfer'",
#"name = 'xfce4-notifyd'",
"name *= 'VLC'",
"name *= 'compton'",
"name *= 'picom'",
"class_g = 'Conky'",
"class_g = 'Kupfer'",
"class_g = 'Synapse'",
"class_g ?= 'Notify-osd'",
"class_g ?= 'Cairo-dock'",
#"class_g ?= 'Xfce4-notifyd'",
"class_g ?= 'Xfce4-power-manager'",
"_GTK_FRAME_EXTENTS@:c",
"_NET_WM_STATE@:32a *= '_NET_WM_STATE_HIDDEN'"
];
# Avoid drawing shadow on all shaped windows (see also: --detect-rounded-corners)
shadow-ignore-shaped = false;
# }}}
# Opacity and blur ------------------------------------- {{{
inactive-opacity = 1;
active-opacity = 1;
frame-opacity = 1;
#inactive-opacity-override = true;
# Dim inactive windows. (0.0 - 1.0)
inactive-dim = 1;
# Do not let dimness adjust based on window opacity.
#inactive-dim-fixed = true;
# Blur background of transparent windows. Bad performance with X Render backend. GLX backend is preferred.
blur-background = false;
#blur-method = "dual_kawase";
#blur-method = "kernel";
blur-strength = 20; # max 20
blur-size = 20;
# Blur background of opaque windows with transparent frames as well.
blur-background-frame = false;
# Do not let blur radius adjust based on window opacity.
blur-background-fixed = false;
blur-background-exclude = [
#"window_type = 'dock'",
"window_type = 'desktop'",
"! name~=''",
"name *= 'slop'",
"name = 'Notification'",
"name = 'xfce4-notifyd'",
"name *= 'compton'",
"name *= 'picom'",
"class_g ?= 'Xfce4-notifyd'",
"class_g ?= 'Xfce4-power-manager'",
"_GTK_FRAME_EXTENTS@:c",
"_NET_WM_STATE@:32a *= '_NET_WM_STATE_HIDDEN'"
];
# }}}
# Fading ---------------------------------- {{{
# Fade windows during opacity changes.
fading = false;
# The time between steps in a fade in milliseconds. (default 10).
fade-delta = 4;
# Opacity change between steps while fading in. (default 0.028).
fade-in-step = 0.03;
# Opacity change between steps while fading out. (default 0.03).
fade-out-step = 0.03;
# Fade windows in/out when opening/closing
no-fading-openclose = false;
# Specify a list of conditions of windows that should not be faded.
fade-exclude = [ ];
# }}}
# Other ---------------------------------- {{{
# Try to detect WM windows and mark them as active.
mark-wmwin-focused = true;
# Mark all non-WM but override-redirect windows active (e.g. menus).
mark-ovredir-focused = true;
# Use EWMH _NET_WM_ACTIVE_WINDOW to determine which window is focused instead of using FocusIn/Out events.
# Usually more reliable but depends on a EWMH-compliant WM.
use-ewmh-active-win = true;
# Detect rounded corners and treat them as rectangular when --shadow-ignore-shaped is on.
detect-rounded-corners = true;
# Detect _NET_WM_OPACITY on client windows, useful for window managers not passing _NET_WM_OPACITY of client windows to frame windows.
# This prevents opacity being ignored for some apps.
# For example without this enabled my xfce4-notifyd is 100% opacity no matter what.
detect-client-opacity = true;
vsync = true;
# Enable DBE painting mode, intended to use with VSync to (hopefully) eliminate tearing. Reported to have no effect, though.
dbe = false;
# Unredirect all windows if a full-screen opaque window is detected, to maximize performance for full-screen windows, like games.
# Known to cause flickering when redirecting/unredirecting windows.
unredir-if-possible = false;
# Specify a list of conditions of windows that should always be considered focused.
focus-exclude = [ ];
# Use WM_TRANSIENT_FOR to group windows, and consider windows in the same group focused at the same time.
detect-transient = true;
# Use WM_CLIENT_LEADER to group windows, and consider windows in the same group focused at the same time.
# WM_TRANSIENT_FOR has higher priority if --detect-transient is enabled, too.
detect-client-leader = true;
# }}}
# Window type settings ---------------------------------- {{{
wintypes:
{
menu = {
opacity = 1;
shadow = true;
fade = true;
full-shadow = true;
};
dropdown_menu = {
opacity = 1;
shadow = true;
fade = true;
full-shadow = true;
};
tooltip = {
fade = true;
shadow = true;
opacity = 1.00;
focus = true;
};
};
xrender-sync-fence = true;
# }}}

View file

@ -0,0 +1,237 @@
;==========================================================
;
;
; ██████╗ ██████╗ ██╗ ██╗ ██╗██████╗ █████╗ ██████╗
; ██╔══██╗██╔═══██╗██║ ╚██╗ ██╔╝██╔══██╗██╔══██╗██╔══██╗
; ██████╔╝██║ ██║██║ ╚████╔╝ ██████╔╝███████║██████╔╝
; ██╔═══╝ ██║ ██║██║ ╚██╔╝ ██╔══██╗██╔══██║██╔══██╗
; ██║ ╚██████╔╝███████╗██║ ██████╔╝██║ ██║██║ ██║
; ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝
;
; ; To learn more about how to configure Polybar
; go to https://github.com/polybar/polybar
;
; The README contains a lot of information
;
;==========================================================
;; Colors ------------------------------------------- {{{
[colors]
;background = ${xrdb:color0:#222}
;background = #bb282828
;background = #88282828
background = #00282828
;background = #222
background-alt = #444
;foreground = ${xrdb:color7:#222}
foreground = #dfdfdf
foreground-alt = #888
primary = #ffb52a
secondary = #e60053
alert = #bd2c40
;; }}}
;; Bar config ----------------------------------------- {{{
[bar/main]
width = 100%
height = 25
radius = 0.0
enable-ipc = true
padding = 0
;; center centered modules on screen, not between other modules
fixed-center = true
separator = "%{F#aaa}|%{F-}"
separator-padding = 1
background = ${colors.background}
foreground = ${colors.foreground}
line-size = 3
line-color = #f00
border-size = 0
border-color = #00000000
padding-left = 0
padding-right = 2
module-margin-left = 1
module-margin-right = 2
font-0 = fixed:pixelsize=10;1
;font-1 = unifont:fontformat=truetype:size=8:antialias=false;0
;font-2 = siji:pixelsize=10;1
;font-7 = NotoEmoji:size=7;
;font-7 = "JetBrainsMono Nerd Font:size=7"
;font-0 = "JetBrainsMono Nerd Font:fontformat=truetype:size=10;2"
;font-0 = "Iosevka Nerd Font:size=10;1"
;font-1 = "NotoEmoji:scale=10;1"
font-1 = "Symbola:size=10;1"
font-2 = FontAwesome5Free:style=Solid:size=8;0
;font-1 = "FontAwesome:fontformat=truetype:size=12;1"
modules-left = xmonad
modules-center = timerDisplay spotify mpd gitlab-pipeline player-mpv-tail
modules-right = pulseaudio-control updates-arch network-traffic pulseaudio filesystem memory cpu date
tray-position = right
tray-padding = 2
tray-maxsize = 16
tray-background = ${colors.background}
;tray-background = #0063ff
cursor-click = pointer
cursor-scroll = ns-resize
;; }}}
;; MODULES ----------------------------------------------- {{{
; show's currently focussed window, already contained in xmonad module
[module/xwindow]
type = internal/xwindow
label = %title:0:30:...%
[module/filesystem]
type = internal/fs
interval = 25
mount-0 = /
format-mounted = <label-mounted>
;format-mounted-prefix = "disk: "
;format-mounted-prefix-foreground = ${colors.foreground-alt}
format-mounted-prefix = "%{F#0fca42}  %{F-} "
;format-mounted-prefix = "  "
;format-mounted-underline = #0fca42
;label-mounted = %{F#0a81f5}%mountpoint%%{F-}: %percentage_used%%
label-mounted = %percentage_used%%
label-unmounted = %mountpoint% not mounted
label-unmounted-foreground = ${colors.foreground-alt}
[module/mpd]
type = internal/mpd
format-online = <label-song> <icon-prev> <icon-stop> <toggle> <icon-next>
icon-prev =
icon-stop =
icon-play =
icon-pause =
icon-next =
label-song-maxlen = 25
label-song-ellipsis = true
[module/cpu]
type = internal/cpu
interval = 2
;format-prefix = "cpu: "
;format-prefix-foreground = ${colors.foreground-alt}
format-prefix = "%{F#f9a000}  %{F-} "
;format-prefix = "  "
#format-underline = #f9a000
label = %percentage:2%%
[module/memory]
type = internal/memory
interval = 2
;format-prefix = "mem: "
;format-prefix-foreground = ${colors.foreground-alt}
format-prefix = "%{F#0a6cf5}  %{F-} "
;format-prefix = " "
#format-underline = #0a6cf5
label = %percentage_used%%
[module/date]
type = internal/date
interval = 5
date = %a %d-%m-%y
date-alt = %d-%m-%Y
time = %H:%M
time-alt = %H:%M:%S
format-prefix = "%{F#fbff8c}  %{F-}"
;format-prefix = "  "
;format-prefix-foreground = ${colors.foreground-alt}
;#format-underline = #4bffdc
#format-underline = #fbff8c
label = %time% | %date%
[module/xmonad]
type = custom/script
exec = xmonad-log
tail = true
[module/timerDisplay]
type = custom/script
exec = "[ -f ~/scripts/remainingTime.txt ] && head -n 1 scripts/remainingTime.txt"
interval = 1
;[module/gitlab-pipeline]
;type = custom/script
;exec = ~/scripts/fetch-running-pipelines.sh
;interval = 10
[module/info-pingrtt]
type = custom/script
exec = ~/.config/polybar/polybar-scripts/info-pingrtt.sh
interval = 10
[module/player-mpv-tail]
type = custom/script
exec = ~/.config/polybar/polybar-scripts/player-mpv-tail.py -t 42 -c '#abb2bf'
tail = true
click-left = ~/.config/polybar/polybar-scripts/player-mpv-tail.py -p pause
click-middle = ~/.config/polybar/polybar-scripts/player-mpv-tail.py -p playlist-pos -1
click-right = ~/.config/polybar/polybar-scripts/player-mpv-tail.py -p playlist-pos +1
scroll-up = ~/.config/polybar/polybar-scripts/player-mpv-tail.py -p time-pos -10
scroll-down = ~/.config/polybar/polybar-scripts/player-mpv-tail.py -p time-pos +10
[module/network-traffic]
; configure interval, etc in script
type = custom/script
exec = ~/.config/polybar/polybar-scripts/network-traffic.sh
tail = true
[module/spotify]
type = custom/script
interval = 1
format = "<label> %{A1:dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.Previous:}%{A-} %{A1:dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.Next:}%{A-}"
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
[module/updates-arch]
type = custom/script
exec = ~/.config/polybar/polybar-scripts/updates-arch-combined.sh
interval = 600
[module/pulseaudio-control]
type = custom/script
tail = true
label = %output%
click-right = exec pavucontrol &
exec = bash ~/.config/polybar/polybar-scripts/pulseaudio-control.bash listen
click-left = bash ~/.config/polybar/polybar-scripts/pulseaudio-control.bash togmute
click-middle = bash ~/.config/polybar/polybar-scripts/pulseaudio-control.bash next-sink
scroll-up = bash ~/.config/polybar/polybar-scripts/pulseaudio-control.bash up
scroll-down = bash ~/.config/polybar/polybar-scripts/pulseaudio-control.bash down
label-padding = 2
label-foreground = ${colors.foreground}

View file

@ -0,0 +1,252 @@
;==========================================================
;
;
; ██████╗ ██████╗ ██╗ ██╗ ██╗██████╗ █████╗ ██████╗
; ██╔══██╗██╔═══██╗██║ ╚██╗ ██╔╝██╔══██╗██╔══██╗██╔══██╗
; ██████╔╝██║ ██║██║ ╚████╔╝ ██████╔╝███████║██████╔╝
; ██╔═══╝ ██║ ██║██║ ╚██╔╝ ██╔══██╗██╔══██║██╔══██╗
; ██║ ╚██████╔╝███████╗██║ ██████╔╝██║ ██║██║ ██║
; ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝
;
; ; To learn more about how to configure Polybar
; go to https://github.com/polybar/polybar
;
; The README contains a lot of information
;
;==========================================================
;; Colors ------------------------------------------- {{{
[colors]
;background = ${xrdb:color0:#222}
;background = #bb282828
;background = #88282828
;background = #88282828
background = #aa282c34
;background = #ff282c34
;background = #222
background-alt = #444
;foreground = ${xrdb:color7:#222}
foreground = #dfdfdf
foreground-alt = #888
primary = #ffb52a
secondary = #e60053
alert = #bd2c40
;; }}}
;; Bar config ----------------------------------------- {{{
[bar/main]
width = 100%:-28
height = 30
offset-x = 14
offset-y = 7
radius = 0
locale = de_DE.UTF-8
enable-ipc = true
padding = 0
;; center centered modules on screen, not between other modules
fixed-center = true
separator = "%{F#aaa}|%{F-}"
separator-padding = 1
background = ${colors.background}
foreground = ${colors.foreground}
line-size = 3
line-color = #f00
border-size = 0
border-color = #00000000
padding-left = 0
padding-right = 2
module-margin-left = 1
module-margin-right = 2
font-0 = fixed:pixelsize=10;1
;font-1 = unifont:fontformat=truetype:size=8:antialias=false;0
;font-2 = siji:pixelsize=10;1
;font-7 = NotoEmoji:size=7;
;font-7 = "JetBrainsMono Nerd Font:size=7"
;font-0 = "JetBrainsMono Nerd Font:fontformat=truetype:size=10;2"
;font-0 = "Iosevka Nerd Font:size=10;1"
;font-1 = "NotoEmoji:scale=10;1"
font-1 = "Symbola:size=10;1"
font-2 = FontAwesome5Free:style=Solid:size=8;0
;font-1 = "FontAwesome:fontformat=truetype:size=12;1"
modules-left = xmonad
modules-center = timerDisplay spotify mpd gitlab-pipeline player-mpv-tail date
modules-right = pulseaudio-control updates-arch pulseaudio filesystem memory cpu
; network-traffic
tray-position = right
tray-padding = 2
tray-maxsize = 16
tray-background = ${colors.background}
;tray-background = #0063ff
cursor-click = pointer
cursor-scroll = ns-resize
;; }}}
;; MODULES ----------------------------------------------- {{{
; show's currently focussed window, already contained in xmonad module
[module/xwindow]
type = internal/xwindow
label = %title:0:30:...%
[module/filesystem]
type = internal/fs
interval = 25
mount-0 = /
format-mounted = <label-mounted>
;format-mounted-prefix = "disk: "
;format-mounted-prefix-foreground = ${colors.foreground-alt}
format-mounted-prefix = "%{F#0fca42}  %{F-} "
;format-mounted-prefix = "  "
;format-mounted-underline = #0fca42
;label-mounted = %{F#0a81f5}%mountpoint%%{F-}: %percentage_used%%
label-mounted = %percentage_used%%
label-unmounted = %mountpoint% not mounted
label-unmounted-foreground = ${colors.foreground-alt}
[module/mpd]
type = internal/mpd
format-online = <label-song> <icon-prev> <icon-stop> <toggle> <icon-next>
icon-prev = 
icon-stop = 
icon-play = 
icon-pause = 
icon-next = 
label-song-maxlen = 25
label-song-ellipsis = true
[module/cpu]
type = internal/cpu
interval = 2
;format-prefix = "cpu: "
;format-prefix-foreground = ${colors.foreground-alt}
format-prefix = "%{F#f9a000}  %{F-} "
;format-prefix = "  "
#format-underline = #f9a000
label = %percentage:2%%
[module/memory]
type = internal/memory
interval = 2
;format-prefix = "mem: "
;format-prefix-foreground = ${colors.foreground-alt}
format-prefix = "%{F#0a6cf5}  %{F-} "
;format-prefix = " "
#format-underline = #0a6cf5
label = %percentage_used%%
[module/date]
type = internal/date
interval = 5
date = %a %d-%m-%y
date-alt = %d-%m-%Y
time = %H:%M
time-alt = %H:%M:%S
;format-prefix = "%{F#fbff8c}  %{F-}"
;format-prefix = "  "
;format-prefix-foreground = ${colors.foreground-alt}
;#format-underline = #4bffdc
#format-underline = #fbff8c
label = %time% | %date%
[module/xmonad]
type = custom/script
exec = xmonad-log
tail = true
[module/timerDisplay]
type = custom/script
exec = "[ -f ~/scripts/remainingTime.txt ] && head -n 1 scripts/remainingTime.txt"
interval = 1
;[module/gitlab-pipeline]
;type = custom/script
;exec = ~/scripts/fetch-running-pipelines.sh
;interval = 10
[module/info-pingrtt]
type = custom/script
exec = ~/.config/polybar/polybar-scripts/info-pingrtt.sh
interval = 10
[module/player-mpv-tail]
type = custom/script
exec = ~/.config/polybar/polybar-scripts/player-mpv-tail.py -t 42 -c '#abb2bf'
tail = true
click-left = ~/.config/polybar/polybar-scripts/player-mpv-tail.py -p pause
click-middle = ~/.config/polybar/polybar-scripts/player-mpv-tail.py -p playlist-pos -1
click-right = ~/.config/polybar/polybar-scripts/player-mpv-tail.py -p playlist-pos +1
scroll-up = ~/.config/polybar/polybar-scripts/player-mpv-tail.py -p time-pos -10
scroll-down = ~/.config/polybar/polybar-scripts/player-mpv-tail.py -p time-pos +10
[module/network-traffic]
; configure interval, etc in script
type = custom/script
exec = ~/.config/polybar/polybar-scripts/network-traffic.sh
tail = true
[module/spotify]
type = custom/script
interval = 1
format = "<label> %{A1:dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.Previous:}%{A-} %{A1:dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.Next:}%{A-}"
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
[module/updates-arch]
type = custom/script
exec = ~/.config/polybar/polybar-scripts/updates-arch-combined.sh
interval = 600
[module/pulseaudio-control]
type = custom/script
tail = true
label = %output%
click-right = exec pavucontrol &
exec = bash ~/.config/polybar/polybar-scripts/pulseaudio-control.bash listen
click-left = bash ~/.config/polybar/polybar-scripts/pulseaudio-control.bash togmute
click-middle = bash ~/.config/polybar/polybar-scripts/pulseaudio-control.bash next-sink
scroll-up = bash ~/.config/polybar/polybar-scripts/pulseaudio-control.bash up
scroll-down = bash ~/.config/polybar/polybar-scripts/pulseaudio-control.bash down
label-padding = 2
label-foreground = ${colors.foreground}
[global/wm]
margin-bottom = -7

View file

@ -0,0 +1,219 @@
;==========================================================
;
;
; ██████╗ ██████╗ ██╗ ██╗ ██╗██████╗ █████╗ ██████╗
; ██╔══██╗██╔═══██╗██║ ╚██╗ ██╔╝██╔══██╗██╔══██╗██╔══██╗
; ██████╔╝██║ ██║██║ ╚████╔╝ ██████╔╝███████║██████╔╝
; ██╔═══╝ ██║ ██║██║ ╚██╔╝ ██╔══██╗██╔══██║██╔══██╗
; ██║ ╚██████╔╝███████╗██║ ██████╔╝██║ ██║██║ ██║
; ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝
;
; ; To learn more about how to configure Polybar
; go to https://github.com/polybar/polybar
;
; The README contains a lot of information
;
;==========================================================
;; Colors ------------------------------------------- {{{
[colors]
;background = ${xrdb:color0:#222}
;background = #bb282828
;background = #88282828
;background = #88282828
;background = #aa282c34
background = #ff282828
;background = #222
background-alt = #444
;foreground = ${xrdb:color7:#222}
foreground = #fbf1c7
foreground-alt = #ebdbb2
primary = #ffb52a
secondary = #e60053
alert = #bd2c40
;; }}}
;; Bar config ----------------------------------------- {{{
[global/wm]
margin-bottom = 0
[bar/main]
monitor = ${env:MONITOR:}
override-redirect = true
;wm-restack = xmonad
;width = 100%:-28
;height = 30
;offset-x = 14
;offset-y = 7
width = 100%
height = 30
offset-x = 0
offset-y = 0
radius = 0
locale = de_DE.UTF-8
enable-ipc = true
padding = 0
;; center centered modules on screen, not between other modules
fixed-center = true
separator = "%{F#aaa}|%{F-}"
separator-padding = 1
background = ${colors.background}
foreground = ${colors.foreground}
line-size = 3
line-color = #f00
border-size = 0
border-color = #00000000
padding-left = 0
padding-right = 2
module-margin-left = 0
module-margin-right = 0
;font-0 = fixed:pixelsize=10;2
;font-1 = unifont:fontformat=truetype:size=8:antialias=false;0
;font-2 = siji:pixelsize=10;1
;font-7 = NotoEmoji:size=7;
;font-7 = "JetBrainsMono Nerd Font:size=7"
;font-0 = "JetBrainsMono Nerd Font:fontformat=truetype:size=10;2"
;font-1 = "NotoEmoji:scale=10;1"
;font-0 = "JetbrainsMono Bold:size=10;2"
;font-0 = "JetbrainsMono:weight=medium:size=10;2"
;font-0 = "scientifica:size=10;2"
;font-0 = "Terminus (TTF):size=12;2"
font-0 = "cherry:size=12;2"
;font-0 = "cozette:size=10;2"
font-1 = "Symbola:size=8;1"
font-2 = "FontAwesome5Free:style=Solid:size=8;2"
font-3 = "Iosevka Nerd Font:size=10;2"
font-4 = "Symbola:size=9;2"
;font-1 = "FontAwesome:fontformat=truetype:size=12;1"
modules-left = xmonad
modules-center = timerDisplay spotify gitlab-pipeline player-mpv-tail time
modules-right = pulseaudio-control updates-arch gpuinfo filesystem memory cpu date
tray-position = ${env:TRAY_POSITION:right}
tray-padding = 2
tray-maxsize = 16
tray-background = ${colors.background}
cursor-click = pointer
cursor-scroll = ns-resize
;; }}}
;; MODULES ----------------------------------------------- {{{
[module/filesystem]
type = internal/fs
interval = 25
mount-0 = /
format-mounted = <label-mounted>
format-mounted-prefix = "%{F#8ec07c}%{F-} "
label-mounted = %percentage_used%%
[module/cpu]
type = internal/cpu
interval = 2
format-prefix = "%{A1:~/.config/polybar/polybar-scripts/toggle_gpuinfo_window.sh 'top':}%{F#fe8019}%{F-} %{A}"
label = %percentage:2%%
click-left = ""
[module/memory]
type = internal/memory
interval = 2
format-prefix = "%{F#83a598}%{F-} "
label = %percentage_used%%
[module/date]
type = internal/date
interval = 500
date = %a, %d.%m.%Y
label = "%date%"
[module/time]
type = internal/date
interval = 1
time = %H:%M:%S
label = "%time%"
[module/xmonad]
type = custom/script
exec = "~/.config/polybar/polybar-scripts/xmonad-status.sh"
label = " %output%"
tail = true
[module/timerDisplay]
type = custom/script
exec = "[ -f ~/scripts/remainingTime.txt ] && head -n 1 scripts/remainingTime.txt"
interval = 1
[module/player-mpv-tail]
type = custom/script
exec = ~/.config/polybar/polybar-scripts/player-mpv-tail.py -t 42 -c '#8ec07c'
tail = true
click-left = ~/.config/polybar/polybar-scripts/player-mpv-tail.py -p pause
click-middle = ~/.config/polybar/polybar-scripts/player-mpv-tail.py -p playlist-pos -1
click-right = ~/.config/polybar/polybar-scripts/player-mpv-tail.py -p playlist-pos +1
scroll-up = ~/.config/polybar/polybar-scripts/player-mpv-tail.py -p time-pos -10
scroll-down = ~/.config/polybar/polybar-scripts/player-mpv-tail.py -p time-pos +10
[module/network-traffic]
; configure interval, etc in script
type = custom/script
exec = ~/.config/polybar/polybar-scripts/network-traffic.sh
tail = true
[module/spotify]
type = custom/script
interval = 1
format = "<label> %{A1:dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.Previous:}%{A-} %{A1:dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.Next:}%{A-}"
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"
[module/updates-arch]
type = custom/script
exec = ~/.config/polybar/polybar-scripts/updates-arch-combined.sh
interval = 600
[module/gpuinfo]
type = custom/script
exec = "sudo ~/scripts/gpuinfo.sh"
format = "%{F#d3869b}%{T3}%{T-}%{F-} <label>"
click-left = "~/.config/polybar/polybar-scripts/toggle_gpuinfo_window.sh 'sudo /home/leon/scripts/gpuinfo.sh --watch'"
interval = 2
[module/pulseaudio-control]
type = custom/script
tail = true
label-foreground = ${colors.foreground}
click-right = exec pavucontrol &
exec = bash ~/.config/polybar/polybar-scripts/pulseaudio-control.bash listen
click-middle = bash ~/.config/polybar/polybar-scripts/pulseaudio-control.bash togmute
click-left = bash ~/.config/polybar/polybar-scripts/pulseaudio-control.bash next-sink
scroll-up = bash ~/.config/polybar/polybar-scripts/pulseaudio-control.bash up
scroll-down = bash ~/.config/polybar/polybar-scripts/pulseaudio-control.bash down

24
files/.config/polybar/launch.sh Executable file
View file

@ -0,0 +1,24 @@
#!/bin/bash
killall -q polybar
echo "---" | tee -a /tmp/polybar1.log /tmp/polybar2.log
outputs=$(xrandr --query | grep " connected" | cut -d" " -f1)
tray_output=HDMI-A-0
for m in $outputs; do
if [[ $m != "DisplayPort-1" ]]; then
tray_output=$m
fi
done
for m in $outputs; do
export MONITOR=$m
export TRAY_POSITION=none
if [[ $m == "$tray_output" ]]; then
TRAY_POSITION=right
fi
MONITOR=$m polybar -r --config=/home/leon/.config/polybar/config.ini main & # >>/tmp/polybar1.log 2>&1 &
done
#polybar --config=/home/leon/.config/polybar/config.ini main >>/tmp/polybar1.log 2>&1 &

View file

@ -0,0 +1,19 @@
#!/bin/sh
HOST="google.de"
if ! ping=$(ping -n -c 1 -W 1 $HOST); then
echo "# ping failed"
else
rtt=$(echo "$ping" | sed -rn 's/.*time=([0-9]{1,})\.?[0-9]{0,} ms.*/\1/p')
if [ "$rtt" -lt 50 ]; then
icon="%{F#3cb703}#%{F-}"
elif [ "$rtt" -lt 150 ]; then
icon="%{F#f9dd04}#%{F-}"
else
icon="%{F#d60606}#%{F-}"
fi
echo "$icon $rtt ms"
fi

View file

@ -0,0 +1,63 @@
#!/bin/bash
print_bytes() {
if [ "$1" -eq 0 ] || [ "$1" -lt 1000 ]; then
bytes="0 kB/s"
elif [ "$1" -lt 1000000 ]; then
bytes="$(echo "scale=0;$1/1000" | bc -l ) kB/s"
else
bytes="$(echo "scale=1;$1/1000000" | bc -l ) MB/s"
fi
echo "$bytes"
}
print_bit() {
if [ "$1" -eq 0 ] || [ "$1" -lt 10 ]; then
bit="0 B"
elif [ "$1" -lt 100 ]; then
bit="$(echo "scale=0;$1*8" | bc -l ) B"
elif [ "$1" -lt 100000 ]; then
bit="$(echo "scale=0;$1*8/1000" | bc -l ) K"
else
bit="$(echo "scale=1;$1*8/1000000" | bc -l ) M"
fi
echo "$bit"
}
INTERVAL=5
INTERFACES="enp0s31f6"
declare -A bytes
for interface in $INTERFACES; do
bytes[past_rx_$interface]="$(cat /sys/class/net/"$interface"/statistics/rx_bytes)"
bytes[past_tx_$interface]="$(cat /sys/class/net/"$interface"/statistics/tx_bytes)"
done
while true; do
down=0
up=0
for interface in $INTERFACES; do
bytes[now_rx_$interface]="$(cat /sys/class/net/"$interface"/statistics/rx_bytes)"
bytes[now_tx_$interface]="$(cat /sys/class/net/"$interface"/statistics/tx_bytes)"
bytes_down=$((((${bytes[now_rx_$interface]} - ${bytes[past_rx_$interface]})) / INTERVAL))
bytes_up=$((((${bytes[now_tx_$interface]} - ${bytes[past_tx_$interface]})) / INTERVAL))
down=$(((( "$down" + "$bytes_down" ))))
up=$(((( "$up" + "$bytes_up" ))))
bytes[past_rx_$interface]=${bytes[now_rx_$interface]}
bytes[past_tx_$interface]=${bytes[now_tx_$interface]}
done
echo "%{F#f00}  %{F-}$(print_bytes $down) %{F#0f0}  %{F-}$(print_bytes $up)"
#echo "D: $(print_bytes $down) U: $(print_bytes $up)"
#echo "Download: $(print_bytes $down) / Upload: $(print_bytes $up)"
# echo "Download: $(print_bit $down) / Upload: $(print_bit $up)"
sleep $INTERVAL
done

View file

@ -0,0 +1,148 @@
#!/usr/bin/env python
import socket
import json
import argparse
def get_property_cmd(property, request_id=None):
cmd = {'command': ['get_property', property]}
if isinstance(request_id, int):
cmd['request_id'] = request_id
return (json.dumps(cmd) + '\n').encode('utf-8')
def update_property(property, get_new_value):
client.send(get_property_cmd(property))
msg = client.recv(BUFSIZE).decode('utf-8')
value = get_new_value(msg)
cmd = {'command': ['set_property', property, value]}
client.send((json.dumps(cmd) + '\n').encode('utf-8'))
MPV_SOCKET = '/tmp/mpvsocket'
BUFSIZE = 1024
METADATA_CMD = get_property_cmd('filtered-metadata', 1)
OBSERVE_METADATA_CMD = b'{ "command": ["observe_property", 1, "filtered-metadata"] }\n'
MEDIA_TITLE_CMD = get_property_cmd('media-title', 2)
PERCENT_POS_CMD = get_property_cmd('percent-pos', 3)
parser = argparse.ArgumentParser()
parser.add_argument('-t', '--truncate', metavar='N', type=int, help='truncate output to N characters')
parser.add_argument('-c', '--color', default='#fff', metavar='C', help='set color of underline (progress bar)')
parser.add_argument('-p', '--property', choices=['pause', 'time-pos', 'playlist-pos'], help='update mpv property and exit')
parser.add_argument('args', nargs=argparse.REMAINDER)
args = parser.parse_args()
if args.property:
try:
client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
client.connect(MPV_SOCKET)
except (FileNotFoundError, ConnectionRefusedError):
raise RuntimeError('Failed connecting to mpv socket.')
if args.property == 'pause':
update_property('pause', lambda msg: not json.loads(msg)['data'])
elif args.property in ['time-pos', 'playlist-pos']:
update_property(args.property, lambda msg: json.loads(msg)['data'] + int(args.args[0]))
client.close()
exit()
import time
import sys
from threading import Thread
def first_value(data, keys):
for key in keys:
value = data.get(key)
if value:
return value
def maybe_truncate(text):
limit = args.truncate
return (text[:limit - 1] + '') if limit and len(text) > limit else text
def puts(output='', pos=0):
if pos > 0:
output = f'%{{u{args.color}}}%{{+u}}{output[0:pos]}%{{-u}}{output[pos:len(output)]}'
sys.stdout.write(output + '\n')
sys.stdout.flush()
def create_client():
client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
while True:
try:
client.connect(MPV_SOCKET)
client.send(METADATA_CMD)
client.send(OBSERVE_METADATA_CMD)
Thread(target=percent_pos_sender, args=[client]).start()
break
except (FileNotFoundError, ConnectionRefusedError):
time.sleep(1)
return client
def parse_msg(msg):
messages = msg.decode('utf-8').split('\n')
not_empty = filter(lambda s: s, messages)
parsed = map(lambda m: json.loads(m), not_empty)
return list(parsed)
def format_data(data):
artist = first_value(data, ['Artist', 'Album_Artist']) or ''
title = first_value(data, ['Title', 'icy-title']) or ''
not_empty = filter(lambda d: d, [artist.strip(), title.strip()])
return ' - '.join(not_empty)
def percent_pos_sender(client):
try:
while True:
time.sleep(1)
client.send(PERCENT_POS_CMD)
except BrokenPipeError:
pass
client = create_client()
current_output = ''
current_pos = 0
while True:
msg = client.recv(BUFSIZE)
# If connection is broken
if len(msg) == 0:
current_output = ''
current_pos = 0
puts()
time.sleep(1)
client = create_client()
continue
for m in parse_msg(msg):
data = m.get('data')
if m.get('event') == 'seek':
client.send(PERCENT_POS_CMD)
# 'filtered-metadata' event or response
elif (m.get('id') == 1 or m.get('request_id') == 1) and isinstance(data, dict):
text = format_data(data)
if text:
output = maybe_truncate(text)
if output != current_output:
current_output = output
puts(output)
client.send(PERCENT_POS_CMD)
else:
client.send(MEDIA_TITLE_CMD)
# 'media-title' response
elif m.get('request_id') == 2:
current_output = maybe_truncate(data)
puts(current_output)
client.send(PERCENT_POS_CMD)
# 'percent-pos' response
elif m.get('request_id') == 3 and isinstance(data, float):
length = len(current_output)
pos = round(length / 100 * data)
if pos != current_pos:
current_pos = pos
puts(current_output, pos)

View file

@ -0,0 +1,353 @@
#!/bin/bash
##################################################################
# Polybar Pulseaudio Control #
# https://github.com/marioortizmanero/polybar-pulseaudio-control #
##################################################################
# Script configuration (more info in the README)
OSD="no" # On Screen Display message for KDE if enabled
INC=2 # Increment when lowering/rising the volume
MAX_VOL=130 # Maximum volume
AUTOSYNC="yes" # All programs have the same volume if enabled
VOLUME_ICONS=("🔈 " "🔉 " "🔊 ")
MUTED_ICON=" " # Muted volume icon
MUTED_COLOR="%{F#6b6b6b}" # Color when the audio is muted
NOTIFICATIONS="no" # Notifications when switching sinks if enabled
#SINK_ICON="🔈 " # The default sink icon if a custom one isn't found
SINK_ICON=""
# Blacklist of PulseAudio sink names when switching between them. To obtain
# the names of your active sinks, use `pactl list sinks short`.
SINK_BLACKLIST=(
"alsa_output.pci-0000_00_1f.3.iec958-stereo"
"alsa_output.pci-0000_28_00.4.iec958-stereo"
"alsa_output.pci-0000_26_00.1.hdmi-stereo-extra2"
)
# Maps PulseAudio sink names to human-readable names
declare -A SINK_NICKNAMES
SINK_NICKNAMES["alsa_output.usb-Native_Instruments_Komplete_Audio_6_77316682-00.analog-surround-21"]="📢"
SINK_NICKNAMES["alsa_output.usb-Fujitsu_UC_C_USB_Value_Headset_Fujitsu_UC_C_USB_Value_Headset-00.analog-stereo"]="🎧"
# Environment & global constants for the script
LANGUAGE=en_US # Some calls depend on English outputs of pactl
END_COLOR="%{F-}"
# Saves the currently default sink into a variable named `curSink`. It will
# return an error code when pulseaudio isn't running.
function getCurSink() {
if ! pulseaudio --check; then return 1; fi
curSink=$(pacmd list-sinks | awk '/\* index:/{print $3}')
}
# Saves the sink passed by parameter's volume into a variable named `curVol`.
function getCurVol() {
curVol=$(pacmd list-sinks | grep -A 15 'index: '"$1"'' | grep 'volume:' | grep -E -v 'base volume:' | awk -F : '{print $3}' | grep -o -P '.{0,3}%' | sed 's/.$//' | tr -d ' ')
}
# Saves the name of the sink passed by parameter into a variable named
# `sinkName`.
function getSinkName() {
sinkName=$(pactl list sinks short | awk -v sink="$1" '{ if ($1 == sink) {print $2} }')
}
# Saves the name to be displayed for the sink passed by parameter into a
# variable called `nickname`.
# If a mapping for the sink name exists, that is used. Otherwise, the string
# "Sink #<index>" is used.
function getNickname() {
getSinkName "$1"
if [ -n "${SINK_NICKNAMES[$sinkName]}" ]; then
nickname="${SINK_NICKNAMES[$sinkName]}"
else
nickname="Sink #$1"
fi
}
# Saves the status of the sink passed by parameter into a variable named
# `isMuted`.
function getIsMuted() {
isMuted=$(pacmd list-sinks | grep -A 15 "index: $1" | awk '/muted/{print $2}')
}
# Saves all the sink inputs of the sink passed by parameter into a string
# named `sinkInputs`.
function getSinkInputs() {
sinkInputs=$(pacmd list-sink-inputs | grep -B 4 "sink: $1 " | awk '/index:/{print $2}')
}
function volUp() {
# Obtaining the current volume from pacmd into $curVol.
if ! getCurSink; then
echo "PulseAudio not running"
return 1
fi
getCurVol "$curSink"
local maxLimit=$((MAX_VOL - INC))
# Checking the volume upper bounds so that if MAX_VOL was 100% and the
# increase percentage was 3%, a 99% volume would top at 100% instead
# of 102%. If the volume is above the maximum limit, nothing is done.
if [ "$curVol" -le "$MAX_VOL" ] && [ "$curVol" -ge "$maxLimit" ]; then
pactl set-sink-volume "$curSink" "$MAX_VOL%"
elif [ "$curVol" -lt "$maxLimit" ]; then
pactl set-sink-volume "$curSink" "+$INC%"
fi
if [ $OSD = "yes" ]; then showOSD "$curSink"; fi
if [ $AUTOSYNC = "yes" ]; then volSync; fi
}
function volDown() {
# Pactl already handles the volume lower bounds so that negative values
# are ignored.
if ! getCurSink; then
echo "PulseAudio not running"
return 1
fi
pactl set-sink-volume "$curSink" "-$INC%"
if [ $OSD = "yes" ]; then showOSD "$curSink"; fi
if [ $AUTOSYNC = "yes" ]; then volSync; fi
}
function volSync() {
if ! getCurSink; then
echo "PulseAudio not running"
return 1
fi
getSinkInputs "$curSink"
getCurVol "$curSink"
# Every output found in the active sink has their volume set to the
# current one. This will only be called if $AUTOSYNC is `yes`.
for each in $sinkInputs; do
pactl set-sink-input-volume "$each" "$curVol%"
done
}
function volMute() {
# Switch to mute/unmute the volume with pactl.
if ! getCurSink; then
echo "PulseAudio not running"
return 1
fi
if [ "$1" = "toggle" ]; then
getIsMuted "$curSink"
if [ "$isMuted" = "yes" ]; then
pactl set-sink-mute "$curSink" "no"
else
pactl set-sink-mute "$curSink" "yes"
fi
elif [ "$1" = "mute" ]; then
pactl set-sink-mute "$curSink" "yes"
elif [ "$1" = "unmute" ]; then
pactl set-sink-mute "$curSink" "no"
fi
if [ $OSD = "yes" ]; then showOSD "$curSink"; fi
}
function nextSink() {
# The final sinks list, removing the blacklisted ones from the list of
# currently available sinks.
if ! getCurSink; then
echo "PulseAudio not running"
return 1
fi
# Obtaining a tuple of sink indexes after removing the blacklisted devices
# with their name.
sinks=()
local i=0
while read -r line; do
index=$(echo "$line" | cut -f1)
name=$(echo "$line" | cut -f2)
# If it's in the blacklist, continue the main loop. Otherwise, add
# it to the list.
for sink in "${SINK_BLACKLIST[@]}"; do
if [ "$sink" = "$name" ]; then
continue 2
fi
done
sinks[$i]="$index"
i=$((i + 1))
done < <(pactl list short sinks)
# If the resulting list is empty, nothing is done
if [ ${#sinks[@]} -eq 0 ]; then return; fi
# If the current sink is greater or equal than last one, pick the first
# sink in the list. Otherwise just pick the next sink avaliable.
local newSink
if [ "$curSink" -ge "${sinks[-1]}" ]; then
newSink=${sinks[0]}
else
for sink in "${sinks[@]}"; do
if [ "$curSink" -lt "$sink" ]; then
newSink=$sink
break
fi
done
fi
# The new sink is set
pacmd set-default-sink "$newSink"
# Move all audio threads to new sink
local inputs=$(pactl list short sink-inputs | cut -f 1)
for i in $inputs; do
pacmd move-sink-input "$i" "$newSink"
done
if [ $NOTIFICATIONS = "yes" ]; then
getNickname "$newSink"
notify-send "PulseAudio" "Changed output to $nickname" --icon=audio-headphones-symbolic &
fi
}
# This function assumes that PulseAudio is already running. It only supports
# KDE OSDs for now. It will show a system message with the status of the
# sink passed by parameter, or the currently active one by default.
function showOSD() {
if [ -z "$1" ]; then
curSink="$1"
else
getCurSink
fi
getCurVol "$curSink"
getIsMuted "$curSink"
qdbus org.kde.kded /modules/kosd showVolume "$curVol" "$isMuted"
}
function listen() {
local firstRun=0
# Listen for changes and immediately create new output for the bar.
# This is faster than having the script on an interval.
LANG=$LANGUAGE pactl subscribe 2>/dev/null | {
while true; do
{
# If this is the first time just continue and print the current
# state. Otherwise wait for events. This is to prevent the
# module being empty until an event occurs.
if [ $firstRun -eq 0 ]; then
firstRun=1
else
read -r event || break
# Avoid double events
if ! echo "$event" | grep -e "on card" -e "on sink" -e "on server"; then
continue
fi
fi
} &>/dev/null
output
done
}
}
function output() {
if ! getCurSink; then
echo "PulseAudio not running"
return 1
fi
getCurVol "$curSink"
getIsMuted "$curSink"
# Fixed volume icons over max volume
local iconsLen=${#VOLUME_ICONS[@]}
if [ "$iconsLen" -ne 0 ]; then
local volSplit=$((MAX_VOL / iconsLen))
for i in $(seq 1 "$iconsLen"); do
if [ $((i * volSplit)) -ge "$curVol" ]; then
volIcon="${VOLUME_ICONS[$((i-1))]}"
break
fi
done
else
volIcon=""
fi
getNickname "$curSink"
# Showing the formatted message
if [ "$isMuted" = "yes" ]; then
echo "${MUTED_COLOR}${nickname} ${curVol}%${END_COLOR}"
else
echo "${nickname} ${curVol}%"
fi
}
function usage() {
echo "Usage: $0 ACTION"
echo ""
echo "Actions:"
echo " help display this help and exit"
echo " output print the PulseAudio status once"
echo " listen listen for changes in PulseAudio to automatically"
echo " update this script's output"
echo " up, down increase or decrease the default sink's volume"
echo " mute, unmute mute or unmute the default sink's audio"
echo " togmute switch between muted and unmuted"
echo " next-sink switch to the next available sink"
echo " sync synchronize all the output streams volume to"
echo " the be the same as the current sink's volume"
echo ""
echo "Author:"
echo " Mario O. M."
echo "More info on GitHub:"
echo " https://github.com/marioortizmanero/polybar-pulseaudio-control"
}
case "$1" in
up)
volUp
;;
down)
volDown
;;
togmute)
volMute toggle
;;
mute)
volMute mute
;;
unmute)
volMute unmute
;;
sync)
volSync
;;
listen)
listen
;;
next-sink)
nextSink
;;
output)
output
;;
*)
usage
;;
esac

View file

@ -0,0 +1,123 @@
#!/bin/python
import sys
import dbus
import argparse
parser = argparse.ArgumentParser()
parser.add_argument(
'-t',
'--trunclen',
type=int,
metavar='trunclen'
)
parser.add_argument(
'-f',
'--format',
type=str,
metavar='custom format',
dest='custom_format'
)
parser.add_argument(
'-p',
'--playpause',
type=str,
metavar='play-pause indicator',
dest='play_pause'
)
parser.add_argument(
'--font',
type=str,
metavar='the index of the font to use for the main label',
dest='font'
)
parser.add_argument(
'--playpause-font',
type=str,
metavar='the index of the font to use to display the playpause indicator',
dest='play_pause_font'
)
args = parser.parse_args()
def fix_string(string):
# corrects encoding for the python version used
if sys.version_info.major == 3:
return string
else:
return string.encode('utf-8')
# Default parameters
output = fix_string(u'{play_pause} {artist}: {song}')
trunclen = 25
play_pause = fix_string(u'\u25B6,\u23F8') # first character is play, second is paused
label_with_font = '%{{T{font}}}{label}%{{T-}}'
font = args.font
play_pause_font = args.play_pause_font
# parameters can be overwritten by args
if args.trunclen is not None:
trunclen = args.trunclen
if args.custom_format is not None:
output = args.custom_format
if args.play_pause is not None:
play_pause = args.play_pause
try:
session_bus = dbus.SessionBus()
spotify_bus = session_bus.get_object(
'org.mpris.MediaPlayer2.spotify',
'/org/mpris/MediaPlayer2'
)
spotify_properties = dbus.Interface(
spotify_bus,
'org.freedesktop.DBus.Properties'
)
metadata = spotify_properties.Get('org.mpris.MediaPlayer2.Player', 'Metadata')
status = spotify_properties.Get('org.mpris.MediaPlayer2.Player', 'PlaybackStatus')
# Handle play/pause label
play_pause = play_pause.split(',')
if status == 'Playing':
play_pause = play_pause[0]
elif status == 'Paused':
play_pause = play_pause[1]
else:
play_pause = str()
if play_pause_font:
play_pause = label_with_font.format(font=play_pause_font, label=play_pause)
# Handle main label
artist = fix_string(metadata['xesam:artist'][0]) if metadata['xesam:artist'] else ''
song = fix_string(metadata['xesam:title']) if metadata['xesam:title'] else ''
album = fix_string(metadata['xesam:album']) if metadata['xesam:album'] else ''
if not artist and not song and not album:
print('')
else:
if len(song) > trunclen:
song = song[0:trunclen]
song += '...'
if ('(' in song) and (')' not in song):
song += ')'
if font:
artist = label_with_font.format(font=font, label=artist)
song = label_with_font.format(font=font, label=song)
album = label_with_font.format(font=font, label=album)
print(output.format(artist=artist, song=song, play_pause=play_pause, album=album))
except Exception as e:
if isinstance(e, dbus.exceptions.DBusException):
print('')
else:
print(e)

View file

@ -0,0 +1,7 @@
#!/bin/bash
if [[ -n $(xdotool search --class bar_system_status_indicator) ]]; then
xdotool search --class bar_system_status_indicator windowkill &
else
termite --class bar_system_status_indicator -e "$1" &
fi

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