├── README.md ├── HideMenuIcons.theme.css ├── HideNewTimestamps.theme.css ├── OldMessagesLoader.theme.css ├── XposeLoader.theme.css ├── UploadPlaceholder.plugin.js ├── IMEFix.plugin.js ├── XposeCompanion.plugin.js ├── OldMessages.theme.css ├── URLDecode.plugin.js ├── LICENSE ├── SendTimestamps.plugin.js ├── EditUploads.plugin.js └── XposeRAW.theme.css /README.md: -------------------------------------------------------------------------------- 1 | # BetterDiscord Addons 2 | 3 | A collection of various BetterDiscord themes/plugins. 4 | -------------------------------------------------------------------------------- /HideMenuIcons.theme.css: -------------------------------------------------------------------------------- 1 | //META{"name":"HideMenuIcons","description":"Hides the new dropdown menu icons","author":"PseudoResonance","version":"1.0"}*// 2 | 3 | /* Hide menu icons */ 4 | .da-hint { 5 | display:none; 6 | } 7 | -------------------------------------------------------------------------------- /HideNewTimestamps.theme.css: -------------------------------------------------------------------------------- 1 | //META{"name":"HideNewTimestamps","description":"Hides the new timestamps to the left of messages","author":"PseudoResonance","version":"1.0"}*// 2 | 3 | /* Hide timestamps */ 4 | .da-timestampVisibleOnHover { 5 | display:none; 6 | } 7 | -------------------------------------------------------------------------------- /OldMessagesLoader.theme.css: -------------------------------------------------------------------------------- 1 | /** 2 | * @name OldMessagesLoader 3 | * @author PseudoResonance 4 | * @version 1.1 5 | * @description Restores old messages style 6 | * @source https://github.com/PseudoResonance/BetterDiscord-Theme/blob/master/OldMessages.theme.css 7 | */ 8 | 9 | @import url("https://pseudoresonance.github.io/BetterDiscord-Theme/OldMessages.theme.css"); 10 | 11 | :root, .theme-dark, .theme-light { 12 | --message-spacing:1.0625rem; 13 | --header-height:1.375rem; 14 | } 15 | -------------------------------------------------------------------------------- /XposeLoader.theme.css: -------------------------------------------------------------------------------- 1 | /** 2 | * @name XposeLoader 3 | * @author PseudoResonance 4 | * @version 1.1 5 | * @description Custom theme loader. 6 | * @source https://github.com/PseudoResonance/BetterDiscord-Theme/blob/master/XposeRAW.theme.css 7 | */ 8 | 9 | @import url("https://pseudoresonance.github.io/BetterDiscord-Theme/XposeRAW.theme.css"); 10 | 11 | :root, .theme-dark, .theme-light { 12 | --background-screen-cover: 0, 0, 0 !important; /* Color of screen over background */ 13 | --background-screen-opacity: 0.55 !important; /* Value from 0-1 - 0 being least opaque, 1 being most opaque */ 14 | --background-url: url("https://i.imgur.com/MoKvTtu.jpg") !important; /* URL for background */ 15 | } 16 | -------------------------------------------------------------------------------- /UploadPlaceholder.plugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @name UploadPlaceholder 3 | * @authorLink https://github.com/PseudoResonance 4 | * @donate https://bit.ly/3hAnec5 5 | * @source https://github.com/PseudoResonance/BetterDiscord-Theme/blob/master/UploadPlaceholder.plugin.js 6 | */ 7 | 8 | module.exports = (() => 9 | { 10 | const config = 11 | { 12 | info: 13 | { 14 | name: "UploadPlaceholder", 15 | authors: 16 | [ 17 | { 18 | name: "PseudoResonance", 19 | discord_id: "152927763605618689", 20 | github_username: "PseudoResonance" 21 | } 22 | ], 23 | version: "2.1.1", 24 | description: "Companion plugin for Xpose theme.", 25 | github: "https://github.com/PseudoResonance/BetterDiscord-Theme/blob/master/UploadPlaceholder.plugin.js", 26 | github_raw: "https://raw.githubusercontent.com/PseudoResonance/BetterDiscord-Theme/master/UploadPlaceholder.plugin.js" 27 | }, 28 | changelog: [ 29 | { 30 | title: "Plugin renamed to XposeCompanion", 31 | type: "fixed", 32 | items: [ 33 | "UploadPlaceholder renamed to XposeCompanion", 34 | "Please delete the UploadPlaceholder plugin if XposeCompanion is working!" 35 | ] 36 | } 37 | ] 38 | }; 39 | 40 | return !global.ZeresPluginLibrary ? class 41 | { 42 | constructor() { this._config = config; } 43 | 44 | getName = () => config.info.name; 45 | getAuthor = () => config.info.description; 46 | getVersion = () => config.info.version; 47 | 48 | load() 49 | { 50 | BdApi.showConfirmationModal("Library Missing", `The library plugin needed for ${config.info.name} is missing. Please click Download Now to install it.`, { 51 | confirmText: "Download Now", 52 | cancelText: "Cancel", 53 | onConfirm: () => 54 | { 55 | require("request").get("https://rauenzi.github.io/BDPluginLibrary/release/0PluginLibrary.plugin.js", async (err, res, body) => 56 | { 57 | if (err) return require("electron").shell.openExternal("https://betterdiscord.net/ghdl?url=https://raw.githubusercontent.com/rauenzi/BDPluginLibrary/master/release/0PluginLibrary.plugin.js"); 58 | await new Promise(r => require("fs").writeFile(require("path").join(BdApi.Plugins.folder, "0PluginLibrary.plugin.js"), body, r)); 59 | }); 60 | } 61 | }); 62 | } 63 | 64 | start() { } 65 | stop() { } 66 | } : (([Plugin, Api]) => { 67 | 68 | const plugin = (Plugin, Api) => 69 | { 70 | const { DiscordAPI, PluginUpdater, PluginUtilities } = Api; 71 | 72 | return class UploadPlaceholder extends Plugin 73 | { 74 | constructor() 75 | { 76 | super(); 77 | } 78 | 79 | onStart() 80 | { 81 | require("request").get("https://raw.githubusercontent.com/PseudoResonance/BetterDiscord-Theme/master/XposeCompanion.plugin.js", async (err, res, body) => 82 | { 83 | if (err) return require("electron").shell.openExternal("https://raw.githubusercontent.com/PseudoResonance/BetterDiscord-Theme/master/XposeCompanion.plugin.js"); 84 | await new Promise(r => require("fs").writeFile(require("path").join(BdApi.Plugins.folder, "XposeCompanion.plugin.js"), body, { flag: 'wx' }, r)); 85 | }); 86 | } 87 | 88 | } 89 | 90 | }; 91 | return plugin(Plugin, Api); 92 | })(global.ZeresPluginLibrary.buildPlugin(config)); 93 | })(); 94 | -------------------------------------------------------------------------------- /IMEFix.plugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @name IMEFix 3 | * @authorLink https://github.com/PseudoResonance 4 | * @donate https://bit.ly/3hAnec5 5 | * @source https://github.com/PseudoResonance/BetterDiscord-Theme/blob/master/IMEFix.plugin.js 6 | */ 7 | 8 | module.exports = (() => 9 | { 10 | const config = 11 | { 12 | info: 13 | { 14 | name: "IMEFix", 15 | authors: 16 | [ 17 | { 18 | name: "PseudoResonance", 19 | discord_id: "152927763605618689", 20 | github_username: "PseudoResonance" 21 | } 22 | ], 23 | version: "1.0.0", 24 | description: "Fix IME input on Discord", 25 | github: "https://github.com/PseudoResonance/BetterDiscord-Theme/blob/master/IMEFix.plugin.js", 26 | github_raw: "https://raw.githubusercontent.com/PseudoResonance/BetterDiscord-Theme/master/IMEFix.plugin.js" 27 | }, 28 | changelog: [ 29 | { 30 | title: "Initial Release", 31 | type: "added", 32 | items: [ 33 | "Fixed IME input on Discord text inputs" 34 | ] 35 | } 36 | ] 37 | }; 38 | 39 | return !global.ZeresPluginLibrary ? class 40 | { 41 | constructor() { this._config = config; } 42 | 43 | getName = () => config.info.name; 44 | getAuthor = () => config.info.description; 45 | getVersion = () => config.info.version; 46 | 47 | load() 48 | { 49 | BdApi.showConfirmationModal("Library Missing", `The library plugin needed for ${config.info.name} is missing. Please click Download Now to install it.`, { 50 | confirmText: "Download Now", 51 | cancelText: "Cancel", 52 | onConfirm: () => 53 | { 54 | require("request").get("https://rauenzi.github.io/BDPluginLibrary/release/0PluginLibrary.plugin.js", async (err, res, body) => 55 | { 56 | if (err) return require("electron").shell.openExternal("https://betterdiscord.net/ghdl?url=https://raw.githubusercontent.com/rauenzi/BDPluginLibrary/master/release/0PluginLibrary.plugin.js"); 57 | await new Promise(r => require("fs").writeFile(require("path").join(BdApi.Plugins.folder, "0PluginLibrary.plugin.js"), body, r)); 58 | }); 59 | } 60 | }); 61 | } 62 | 63 | start() { } 64 | stop() { } 65 | } : (([Plugin, Api]) => { 66 | 67 | const plugin = (Plugin, Api) => 68 | { 69 | const { DiscordModules, Patcher } = Api; 70 | 71 | let composition = false; 72 | 73 | let domNode = null; 74 | let cancelPatch = null; 75 | 76 | return class IMEFix extends Plugin 77 | { 78 | 79 | constructor() 80 | { 81 | super(); 82 | this.onStart = this.onStart.bind(this); 83 | this.onStop = this.onStop.bind(this); 84 | this.compositionStart = this.compositionStart.bind(this); 85 | this.compositionEnd = this.compositionEnd.bind(this); 86 | this.input = this.input.bind(this); 87 | } 88 | 89 | onStart() 90 | { 91 | this.cancelPatch = BdApi.monkeyPatch(BdApi.findModule(m => m.displayName === 'SlateChannelTextArea').prototype, 'componentDidMount', 92 | { 93 | after: _ => 94 | { 95 | this.composition = false; 96 | this.domNode = DiscordModules.ReactDOM.findDOMNode(_.thisObject); 97 | this.domNode.addEventListener("compositionstart", this.compositionStart, true); 98 | this.domNode.addEventListener("compositionend", this.compositionEnd, true); 99 | this.domNode.addEventListener("input", this.input, true); 100 | } 101 | }); 102 | } 103 | 104 | onStop() 105 | { 106 | this.cancelPatch(); 107 | if (this.domNode != null) 108 | { 109 | this.domNode.removeEventListener("compositionstart", this.compositionStart, true); 110 | this.domNode.removeEventListener("compositionend", this.compositionEnd, true); 111 | this.domNode.removeEventListener("input", this.input, true); 112 | } 113 | } 114 | 115 | compositionStart(event) 116 | { 117 | this.composition = true; 118 | } 119 | 120 | compositionEnd(event) 121 | { 122 | this.composition = false; 123 | } 124 | 125 | input(event) 126 | { 127 | if (this.composition) 128 | { 129 | event.stopPropagation(); 130 | } 131 | } 132 | 133 | } 134 | 135 | }; 136 | return plugin(Plugin, Api); 137 | })(global.ZeresPluginLibrary.buildPlugin(config)); 138 | })(); 139 | -------------------------------------------------------------------------------- /XposeCompanion.plugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @name XposeCompanion 3 | * @authorLink https://github.com/PseudoResonance 4 | * @donate https://bit.ly/3hAnec5 5 | * @source https://github.com/PseudoResonance/BetterDiscord-Theme/blob/master/XposeCompanion.plugin.js 6 | */ 7 | 8 | module.exports = (() => { 9 | const config = { 10 | info: { 11 | name: "XposeCompanion", 12 | authors: 13 | [{ 14 | name: "PseudoResonance", 15 | discord_id: "152927763605618689", 16 | github_username: "PseudoResonance" 17 | } 18 | ], 19 | version: "2.3.0", 20 | description: "Companion plugin for Xpose theme.", 21 | github: "https://github.com/PseudoResonance/BetterDiscord-Theme/blob/master/XposeCompanion.plugin.js", 22 | github_raw: "https://raw.githubusercontent.com/PseudoResonance/BetterDiscord-Theme/master/XposeCompanion.plugin.js" 23 | }, 24 | changelog: [{ 25 | title: "Fixed Folder Backgrounds", 26 | type: "fixed", 27 | items: [ 28 | "Fixed folder colors not showing initially" 29 | ] 30 | } 31 | ], 32 | defaultConfig: [{ 33 | type: 'category', 34 | id: 'appearance', 35 | name: 'Appearance Settings', 36 | collapsible: true, 37 | shown: true, 38 | settings: [{ 39 | name: 'Expanded server folder has color', 40 | id: 'guildFolderColor', 41 | type: 'switch', 42 | value: 'true' 43 | } 44 | ] 45 | } 46 | ] 47 | }; 48 | 49 | return !global.ZeresPluginLibrary ? class { 50 | constructor() { 51 | this._config = config; 52 | } 53 | 54 | getName = () => config.info.name; 55 | getAuthor = () => config.info.description; 56 | getVersion = () => config.info.version; 57 | 58 | load() { 59 | BdApi.showConfirmationModal("Library Missing", `The library plugin needed for ${config.info.name} is missing. Please click Download Now to install it.`, { 60 | confirmText: "Download Now", 61 | cancelText: "Cancel", 62 | onConfirm: () => { 63 | require("request").get("https://rauenzi.github.io/BDPluginLibrary/release/0PluginLibrary.plugin.js", async(err, res, body) => { 64 | if (err) 65 | return require("electron").shell.openExternal("https://betterdiscord.net/ghdl?url=https://raw.githubusercontent.com/rauenzi/BDPluginLibrary/master/release/0PluginLibrary.plugin.js"); 66 | await new Promise(r => require("fs").writeFile(require("path").join(BdApi.Plugins.folder, "0PluginLibrary.plugin.js"), body, r)); 67 | }); 68 | } 69 | }); 70 | } 71 | 72 | start() {} 73 | stop() {} 74 | } 75 | : (([Plugin, Api]) => { 76 | 77 | const plugin = (Plugin, Api) => { 78 | const { 79 | DiscordModules, 80 | DOMTools, 81 | PluginUtilities, 82 | Utilities, 83 | Patcher 84 | } = Api; 85 | 86 | let guildListObserver; 87 | 88 | return class XposeCompanion extends Plugin { 89 | constructor() { 90 | super(); 91 | this.updateFolderBackgrounds = this.updateFolderBackgrounds.bind(this); 92 | this.handleUserSettingsChange = this.handleUserSettingsChange.bind(this); 93 | const pluginInst = this; 94 | guildListObserver = new MutationObserver(function (mutationsList, observer) { 95 | for (const mutation of mutationsList) { 96 | if (mutation.type === 'childList') { 97 | for (const node of mutation.addedNodes) { 98 | if (pluginInst.listStartsWith(node.classList, "wrapper-")) { 99 | const entry = DOMTools.query('[class^="expandedFolderBackground-"]', node); 100 | if (entry != null) { 101 | var icon = DOMTools.query('[class^="folderIconWrapper-"]', entry.nextSibling); 102 | var backgroundColor = icon.style.backgroundColor; 103 | if (backgroundColor == "") { 104 | backgroundColor = DOMTools.query('[class^="expandedFolderIconWrapper-"] > svg', icon).style.color; 105 | backgroundColor = backgroundColor.substring(0, backgroundColor.length - 1) + ", 0.4"; 106 | } 107 | entry.style.backgroundColor = backgroundColor; 108 | } 109 | } else if (pluginInst.listStartsWith(node.classList, "expandedFolderBackground-")) { 110 | var icon = DOMTools.query('[class^="folderIconWrapper-"]', node.nextSibling); 111 | var backgroundColor = icon.style.backgroundColor; 112 | if (backgroundColor == "") { 113 | backgroundColor = DOMTools.query('[class^="expandedFolderIconWrapper-"] > svg', icon).style.color; 114 | backgroundColor = "rgba" + backgroundColor.substring(3, backgroundColor.length - 1) + ", 0.4)"; 115 | } 116 | node.style.backgroundColor = backgroundColor; 117 | } 118 | } 119 | } else if (mutation.type === 'attributes') { 120 | if (pluginInst.listStartsWith(mutation.target.classList, "folderIconWrapper-")) { 121 | const backgroundColor = mutation.target.style.backgroundColor; 122 | if (backgroundColor != "") 123 | DOMTools.parents(mutation.target, '[class^="listItem-"]')[0].previousSibling.style.backgroundColor = backgroundColor; 124 | } else if (mutation.target.nodeName == 'svg' && pluginInst.listStartsWith(mutation.target.parentElement.classList, "expandedFolderIconWrapper-")) { 125 | var backgroundColor = mutation.target.style.color; 126 | backgroundColor = "rgba" + backgroundColor.substring(3, backgroundColor.length - 1) + ", 0.4)"; 127 | DOMTools.parents(mutation.target, '[class^="listItem-"]')[0].previousSibling.style.backgroundColor = backgroundColor; 128 | } 129 | } 130 | } 131 | }); 132 | } 133 | 134 | onStart() { 135 | this.updateFolderBackgrounds(); 136 | DiscordModules.UserSettingsStore.addChangeListener(this.handleUserSettingsChange); 137 | } 138 | 139 | onStop() { 140 | Patcher.unpatchAll(); 141 | guildListObserver.disconnect(); 142 | DiscordModules.UserSettingsStore.removeChangeListener(this.handleUserSettingsChange); 143 | } 144 | 145 | updateFolderBackgrounds() { 146 | if (this.settings.appearance.guildFolderColor) { 147 | guildListObserver.observe(document.querySelector('[data-list-id="guildsnav"]'), { 148 | subtree: true, 149 | childList: true, 150 | attributeFilter: ["style"] 151 | }); 152 | const folderBackgrounds = document.querySelectorAll('[class^="expandedFolderBackground-"]'); 153 | for (const folderBackground of folderBackgrounds) { 154 | const parent = folderBackground.parentElement; 155 | const colorDecimal = Utilities.findInTree(BdApi.getInternalInstance(parent).return, node => { 156 | return node?.folderNode 157 | }, { 158 | walkable: ["props", "children", "child", "sibling", "memoizedProps"] 159 | }).folderNode.color; 160 | const backgroundColor = "rgba(" + ((colorDecimal >> 16) & 0xFF) + "," + ((colorDecimal >> 8) & 0xFF) + "," + (colorDecimal & 0xFF) + ", 0.4)"; 161 | folderBackground.style.backgroundColor = backgroundColor; 162 | } 163 | } else { 164 | guildListObserver.disconnect(); 165 | const folderBackgrounds = document.querySelectorAll('[class^="expandedFolderBackground-"]'); 166 | for (const folderBackground of folderBackgrounds) { 167 | folderBackground.style.backgroundColor = null; 168 | } 169 | } 170 | } 171 | 172 | getSettingsPanel() { 173 | const panel = this.buildSettingsPanel(); 174 | return panel.getElement(); 175 | } 176 | 177 | saveSettings(category, setting, value) { 178 | this.settings[category][setting] = value; 179 | PluginUtilities.saveSettings(config.info.name, this.settings); 180 | if (category === 'appearance') { 181 | if (setting === 'guildFolderColor') { 182 | this.updateFolderBackgrounds(); 183 | } 184 | } 185 | } 186 | 187 | async handleUserSettingsChange() { 188 | this.updateFolderBackgrounds(); 189 | } 190 | 191 | listStartsWith(list, str) { 192 | if (list) { 193 | for (const value of list.entries()) { 194 | if (value[1].startsWith(str)) { 195 | return true; 196 | } 197 | } 198 | } 199 | return false; 200 | } 201 | 202 | } 203 | 204 | }; 205 | return plugin(Plugin, Api); 206 | })(global.ZeresPluginLibrary.buildPlugin(config)); 207 | })(); 208 | -------------------------------------------------------------------------------- /OldMessages.theme.css: -------------------------------------------------------------------------------- 1 | /** 2 | * @name OldMessages 3 | * @author PseudoResonance 4 | * @version 1.3.2 5 | * @description Restores old messages style 6 | * @source https://github.com/PseudoResonance/BetterDiscord-Theme/blob/master/OldMessages.theme.css 7 | */ 8 | 9 | :root, .theme-dark, .theme-light { 10 | --message-spacing:1.0625rem; 11 | --header-height:1.375rem; 12 | --new-message-color:#f04747; 13 | --background-mentioned-hover:transparent; 14 | --background-message-hover:transparent; 15 | --oldMessagesVersion:"1.3.2"; 16 | } 17 | 18 | /* Restore Reactions */ 19 | [class*="reaction-"] { 20 | background:var(--background-modifier-accent); 21 | border-radius:.25rem; 22 | border:none; 23 | margin:0 .125rem .125rem 0; 24 | padding:0; 25 | -webkit-transition:background-color .1s ease; 26 | transition:background-color .1s ease; 27 | } 28 | 29 | [class*="reaction-"]:hover, [class*="reaction-"]:active { 30 | background-color:rgba(252, 245, 255, 0.05); 31 | } 32 | 33 | [class*="reaction-"][class*="reactionMe-"] { 34 | background-color:rgba(114,137,218,.3); 35 | } 36 | 37 | [class*="reaction-"] [class*="reactionInner-"] { 38 | padding:0 .375rem; 39 | } 40 | 41 | [class*="reaction-"] [class*="reactionCount-"]:not(#foo) { 42 | color:var(--text-muted); 43 | } 44 | 45 | [class*="reaction-"]:hover [class*="reactionCount-"]:not(#foo) { 46 | color:var(--interactive-hover); 47 | } 48 | 49 | [class*="reaction-"][class*="reactionMe-"] [class*="reactionCount-"]:not(#foo), [class*="reaction-"][class*="reactionMe-"]:hover [class*="reactionCount-"]:not(#foo) { 50 | color:#7289da; 51 | } 52 | 53 | /* Disable Message Hover */ 54 | [class*="messagesWrapper-"] [class*="message-"]:not([class*="mentioned-"]):hover, [class*="messagesWrapper-"] [class*="message-"]:not([class*="mentioned-"]):active, [class*="messagesWrapper-"] [class*="message-"][class*="selected-"]:not([class*="mentioned-"]) { 55 | background-color:transparent !important; 56 | } 57 | 58 | [class*="messagesWrapper-"] [class*="message-"][class*="mentioned-"], [class*="messagesWrapper-"] [class*="message-"][class*="mentioned-"]:hover, [class*="messagesWrapper-"] [class*="message-"][class*="mentioned-"]:active, [class*="messagesWrapper-"] [class*="message-"][class*="selected-"][class*="mentioned-"] { 59 | background-color:transparent !important; 60 | //background-color:var(--background-mentioned) !important; /* Replace transparent background with background-mentioned if you want to keep the new mention highlighting */ 61 | } 62 | 63 | /* Hide Yellow Bar to Left of New Mention Highlighting */ 64 | [class*="messagesWrapper-"] [class*="message-"][class*="mentioned-"]::before { 65 | background-color:transparent !important; 66 | } 67 | 68 | /* Mentions - Comment these two sections out if you want to keep the new mention highlighting */ 69 | [class*="messagesWrapper-"] [class*="message-"][class*="mentioned-"] > [class*="contents-"] > [class*="messageContent-"] { 70 | background-color:var(--background-mentioned) !important; 71 | margin-left:-8px; 72 | padding-left:4px; 73 | height:auto; 74 | border-left:4px solid #faa61a; 75 | border-radius:3px; 76 | } 77 | 78 | [class*="messagesWrapper-"] [class*="message-"][class*="mentioned-"] > [class*="contents-"] > [class*="messageContent-"]::before { 79 | content:""; 80 | display:block; 81 | position:absolute; 82 | left:61px; 83 | height:inherit; 84 | pointer-events:none; 85 | width:2px; 86 | } 87 | 88 | /* Disable Mention Background */ 89 | [class*="mention"] { 90 | border-radius:0; 91 | transition:background-color 50ms ease-out,color 50ms ease-out; 92 | -webkit-transition:background-color 50ms ease-out,color 50ms ease-out; 93 | } 94 | 95 | [class*="mention"][class*="interactive"], [class*="mention"][class*="roleMention"] { 96 | background-color:rgba(114,137,218,.1); 97 | color:#7289da; 98 | } 99 | 100 | [class*="mention"][class*="interactive"]:hover, [class*="mention"][class*="roleMention"]:hover { 101 | background-color:rgba(114,137,218,.7); 102 | color:#fff; 103 | } 104 | 105 | [class*="messagesWrapper-"] [class*="message-"][class*="mentioned-"] [class*="mention"][class*="interactive"]:hover { 106 | color:#7289da; 107 | } 108 | 109 | /* Remove Spacer Between Messages and Text Input */ 110 | [class*="messagesWrapper-"] [class*="scrollerSpacer-"] { 111 | height:25px; 112 | } 113 | 114 | [class*="messagesWrapper-"] ~ form [class*="channelTextArea-"] { 115 | padding-top:20px; 116 | margin-top:0px; 117 | } 118 | 119 | [class*="messagesWrapper-"] ~ form [class*="channelTextArea-"] { 120 | border-top:thin solid var(--background-modifier-accent); 121 | border-radius:0; 122 | } 123 | 124 | /* Resize/Move Message Buttons */ 125 | [class*="messagesWrapper-"] [class*="buttonContainer-"] [class*="buttons-"] [class*="button-"] { 126 | height:16px; 127 | width:16px; 128 | min-height:16px; 129 | min-width:16px; 130 | color:rgba(185, 187, 190, 0.6); 131 | } 132 | 133 | [class*="messagesWrapper-"] [class*="buttonContainer-"] [class*="buttons-"] [class*="button-"]:hover, [class*="messagesWrapper-"] [class*="buttonContainer-"] [class*="buttons-"] [class*="button-"]:active, [class*="messagesWrapper-"] [class*="buttonContainer-"] [class*="buttons-"] [class*="button-"][class*="selected-"] { 134 | color:rgba(155, 157, 160, 0.6) !important; 135 | background-color:transparent !important; 136 | } 137 | 138 | [class*="messagesWrapper-"] [class*="buttonContainer-"] [class*="buttons-"] { 139 | top:-4px; 140 | padding-right:0.875rem; 141 | } 142 | 143 | [class*="messagesWrapper-"] [class*="groupStart-"]:not([class*="compact-"]) [class*="buttonContainer-"] { 144 | top:var(--header-height); 145 | } 146 | 147 | [class*="messagesWrapper-"] [class*="buttonContainer-"] [class*="buttons-"] [class*="button-"] { 148 | padding:0px; 149 | } 150 | 151 | [class*="messagesWrapper-"] [class*="buttonContainer-"] [class*="buttons-"] div { 152 | box-shadow:none !important; 153 | border:0px !important; 154 | } 155 | 156 | /* Disable Background on Message Buttons */ 157 | [class*="messagesWrapper-"] [class*="buttonContainer-"] [class*="buttons-"] [class*="wrapper-"] { 158 | background-color:transparent !important; 159 | } 160 | 161 | /* Rotate Settings Button */ 162 | [class*="messagesWrapper-"] [class*="buttonContainer-"] [class*="buttons-"] [class*="button-"]:last-child:not([class*="dangerous-"]) { 163 | transform:rotate(90deg); 164 | padding-right:0px; 165 | padding-left:0px; 166 | margin-right:0px; 167 | margin-left:0px; 168 | } 169 | 170 | /* Margin on Text to Accomodate Buttons */ 171 | [class*="messagesWrapper-"] [class*="message-"] [class*="markup-"]:not([contenteditable="true"])::before { 172 | content:''; 173 | width:6.75rem; 174 | height:16px; 175 | display:inline-flex; 176 | float:right; 177 | } 178 | 179 | /* Make Text Area Wider */ 180 | [class*="messagesWrapper-"] [class*="message-"] { 181 | padding-right:0.875rem !important; 182 | } 183 | 184 | /* Spacer Between Message Groups */ 185 | [class*="messagesWrapper-"] [class*="groupStart-"]::after { 186 | content:''; 187 | border-top:thin solid var(--background-modifier-accent); 188 | top:calc(0px - var(--message-spacing)); 189 | left:1rem; 190 | right:0.875rem; 191 | display:block; 192 | position:absolute; 193 | } 194 | 195 | [class*="messagesWrapper-"] [class*="groupStart-"]:not([class*="compact-"]) { 196 | margin-top:calc(var(--message-spacing) * 2); 197 | } 198 | 199 | [class*="messagesWrapper-"] [class*="divider-"] + [class*="groupStart-"]::after { 200 | display:none; 201 | } 202 | 203 | [class*="messagesWrapper-"] [class*="divider-"] + [class*="groupStart-"]:not([class*="compact-"]) { 204 | margin-top:calc(var(--message-spacing) * 0.5); 205 | } 206 | 207 | [class*="messagesWrapper-"] [class*="divider-"] + [class*="groupStart-"][class*="compact-"] { 208 | margin-top:0; 209 | } 210 | 211 | /* Spacing Between Messages Within a Group */ 212 | [class*="messagesWrapper-"] [class*="message-"] { 213 | padding-top:0px; 214 | padding-bottom:0px; 215 | } 216 | 217 | /* Message Divider */ 218 | [class*="messagesWrapper-"] [class*="divider-"] { 219 | width:100%; 220 | margin-left:0; 221 | border:none; 222 | height:calc(var(--message-spacing) * 1.5) !important; 223 | } 224 | 225 | [class*="messagesWrapper-"] [class*="divider-"] span { 226 | background-color:transparent; 227 | padding:0; 228 | display:flex; 229 | align-items:center; 230 | text-align:center; 231 | font-size:14px; 232 | line-height:22px; 233 | height:calc(var(--message-spacing) * 2) !important; 234 | left:1rem; 235 | right:0.875rem; 236 | position:absolute; 237 | } 238 | 239 | [class*="messagesWrapper-"] [class*="divider-"] span::before { 240 | margin-right:8px; 241 | } 242 | 243 | [class*="messagesWrapper-"] [class*="divider-"] span::after { 244 | margin-left:8px; 245 | } 246 | 247 | [class*="messagesWrapper-"] [class*="divider-"] span::before, [class*="messagesWrapper-"] [class*="divider-"] span::after { 248 | content:''; 249 | flex:1; 250 | } 251 | 252 | [class*="messagesWrapper-"] [class*="divider-"][class*="isUnread-"] span:nth-child(2) { 253 | display:none; 254 | } 255 | 256 | /* Spacing Correction */ 257 | .group-spacing-0 [class*="divider-"][class*="hasContent-"] { 258 | margin-top:4px; 259 | margin-bottom:4px; 260 | } 261 | 262 | .group-spacing-4 [class*="divider-"][class*="hasContent-"] { 263 | margin-top:8px; 264 | margin-bottom:8px; 265 | } 266 | 267 | .group-spacing-8 [class*="divider-"][class*="hasContent-"] { 268 | margin-top:12px; 269 | margin-bottom:12px; 270 | } 271 | 272 | .group-spacing-16 [class*="divider-"][class*="hasContent-"] { 273 | margin-top:20px; 274 | margin-bottom:20px; 275 | } 276 | 277 | .group-spacing-24 [class*="divider-"][class*="hasContent-"] { 278 | margin-top:28px; 279 | margin-bottom:28px; 280 | } 281 | 282 | /* New Message Color */ 283 | [class*="messagesWrapper-"] [class*="divider-"][class*="isUnread-"] span { 284 | color:var(--new-message-color); 285 | } 286 | 287 | [class*="messagesWrapper-"] [class*="divider-"][class*="isUnread-"] span::before, [class*="messagesWrapper-"] [class*="divider-"][class*="isUnread-"] span::after { 288 | border-bottom:thin solid var(--new-message-color); 289 | } 290 | 291 | [class*="messagesWrapper-"] [class*="divider-"][class*="isUnread-"] span svg { 292 | display:none; 293 | } 294 | 295 | /* Timestamp Divider Color */ 296 | [class*="messagesWrapper-"] [class*="divider-"]:not([class*="isUnread-"]) span::before, [class*="messagesWrapper-"] [class*="divider-"]:not([class*="isUnread-"]) span::after { 297 | border-bottom:thin solid var(--background-modifier-accent); 298 | } 299 | 300 | /* Display version info */ 301 | [class*="sidebar-"] [role="tabbar"] [class*="info-"]::after { 302 | content:"Old Messages Version: v" var(--oldMessagesVersion); 303 | color:var(--text-muted); 304 | font-size:12px; 305 | line-height:16px; 306 | } 307 | 308 | .bd-addon-list [id*="OldMessagesLoader"] .bd-addon-header .bd-title .bd-meta::after { 309 | content:"Theme Version: v" var(--oldMessagesVersion); 310 | padding-left:1em; 311 | } 312 | -------------------------------------------------------------------------------- /URLDecode.plugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @name URLDecode 3 | * @author PseudoResonance 4 | * @version 2.0.1 5 | * @description URL/embed decoder for non-ASCII text. 6 | * @authorLink https://github.com/PseudoResonance 7 | * @donate https://bit.ly/3hAnec5 8 | * @source https://github.com/PseudoResonance/BetterDiscord-Theme/blob/master/URLDecode.plugin.js 9 | * @updateUrl https://raw.githubusercontent.com/PseudoResonance/BetterDiscord-Theme/master/URLDecode.plugin.js 10 | */ 11 | 12 | const config = { 13 | info: { 14 | name: "URLDecode", 15 | authors: 16 | [{ 17 | name: "PseudoResonance", 18 | discord_id: "152927763605618689", 19 | github_username: "PseudoResonance" 20 | } 21 | ], 22 | version: "2.0.1", 23 | description: "URL/embed decoder for non-ASCII text.", 24 | github: "https://github.com/PseudoResonance/BetterDiscord-Theme/blob/master/URLDecode.plugin.js", 25 | github_raw: "https://raw.githubusercontent.com/PseudoResonance/BetterDiscord-Theme/master/URLDecode.plugin.js" 26 | }, 27 | changelog: [{ 28 | title: "Fixed", 29 | type: "fixed", 30 | items: [ 31 | "Updated to BetterDiscord 1.9.3" 32 | ] 33 | } 34 | ], 35 | defaultConfig: [{ 36 | type: 'category', 37 | id: 'general', 38 | name: 'General Settings', 39 | collapsible: true, 40 | shown: true, 41 | settings: [{ 42 | name: 'Decode chat URLs', 43 | id: 'decodeChat', 44 | type: 'switch', 45 | value: 'true' 46 | }, { 47 | name: 'Decode embed titles', 48 | id: 'decodeEmbed', 49 | type: 'switch', 50 | value: 'true' 51 | } 52 | ] 53 | } 54 | ] 55 | }; 56 | 57 | class Dummy { 58 | constructor() { 59 | this._config = config; 60 | } 61 | start() {} 62 | stop() {} 63 | } 64 | 65 | if (!global.ZeresPluginLibrary) { 66 | BdApi.UI.showConfirmationModal("Library Missing", `The library plugin needed for ${config.name ?? config.info.name} is missing. Please click Download Now to install it.`, { 67 | confirmText: "Download Now", 68 | cancelText: "Cancel", 69 | onConfirm: () => { 70 | require("request").get("https://betterdiscord.app/gh-redirect?id=9", async(err, resp, body) => { 71 | if (err) 72 | return require("electron").shell.openExternal("https://betterdiscord.app/Download?id=9"); 73 | if (resp.statusCode === 302) { 74 | require("request").get(resp.headers.location, async(error, response, content) => { 75 | if (error) 76 | return require("electron").shell.openExternal("https://betterdiscord.app/Download?id=9"); 77 | await new Promise(r => require("fs").writeFile(require("path").join(BdApi.Plugins.folder, "0PluginLibrary.plugin.js"), content, r)); 78 | }); 79 | } else { 80 | await new Promise(r => require("fs").writeFile(require("path").join(BdApi.Plugins.folder, "0PluginLibrary.plugin.js"), body, r)); 81 | } 82 | }); 83 | } 84 | }); 85 | } 86 | 87 | module.exports = !global.ZeresPluginLibrary ? Dummy : (([Plugin, Api]) => { 88 | const plugin = (Plugin, Api) => { 89 | const { 90 | PluginUtilities, 91 | Patcher 92 | } = Api; 93 | 94 | class StripInvalidTrailingEncoding { 95 | /** 96 | * https://github.com/jridgewell/strip-invalid-trailing-encoding 97 | * 98 | * MIT License 99 | * 100 | * Copyright (c) 2017 Justin Ridgewell 101 | * 102 | * Permission is hereby granted, free of charge, to any person obtaining a copy 103 | * of this software and associated documentation files (the "Software"), to deal 104 | * in the Software without restriction, including without limitation the rights 105 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 106 | * copies of the Software, and to permit persons to whom the Software is 107 | * furnished to do so, subject to the following conditions: 108 | * 109 | * The above copyright notice and this permission notice shall be included in all 110 | * copies or substantial portions of the Software. 111 | * 112 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 113 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 114 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 115 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 116 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 117 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 118 | * SOFTWARE. 119 | */ 120 | 121 | /** 122 | * Parses a (possibly) hex char into its int value. 123 | * If the char is not valid hex char, returns 16. 124 | * 125 | * @param {string} char 126 | * @return {number} 127 | */ 128 | static toHex(char) { 129 | const i = char.charCodeAt(0); 130 | // 0 - 9 131 | if (i >= 48 && i <= 57) { 132 | return i - 48; 133 | } 134 | 135 | const a = (i | 0x20); 136 | // Normalize A-F into a-f. 137 | if (a >= 97 && a <= 102) { 138 | return a - 87; 139 | } 140 | 141 | // Invalid Hex 142 | return 16; 143 | } 144 | 145 | /** 146 | * Determines if a '%' character occurs in the last 3 characters of a string. 147 | * If none, returns 3. 148 | * 149 | * @param {string} string 150 | * @param {number} length 151 | * @return {boolean} 152 | */ 153 | static hasPercent(string, length) { 154 | if (length > 0 && string[length - 1] === '%') { 155 | return 1; 156 | } 157 | if (length > 1 && string[length - 2] === '%') { 158 | return 2; 159 | } 160 | if (length > 2 && string[length - 3] === '%') { 161 | return 0; 162 | } 163 | 164 | return 3; 165 | } 166 | 167 | /** 168 | * Strips invalid Percent Encodings that occur at the end of a string. 169 | * This is highly optimized to trim only _broken_ sequences at the end. 170 | * 171 | * Note that this **IS NOT** a string sanitizer. It will not prevent native 172 | * decodeURIComponent from throwing errors. This is only to prevent "good" 173 | * strings that were invalidly truncated in the middle of a percent encoding 174 | * from throwing. Attackers can craft strings will not be "fixed" by stripping. 175 | * 176 | * @param {string} string 177 | * @param {number} length The length of the string. 178 | * @param {number} shift Position of the rightmost %. 179 | * @return {string, stripped} Stripped string and stripped portion 180 | */ 181 | static _strip(string, length, shift) { 182 | let end = length - shift; 183 | let num = -shift; 184 | let high = '8'; 185 | let low = '0'; 186 | let continuation = false; 187 | 188 | for (let pos = length - 1; pos >= 0; pos--) { 189 | const char = string[pos]; 190 | num++; 191 | 192 | if (char !== '%') { 193 | // If we have backtracked 3 characters and we don't find a "%", we know the 194 | // string did not end in an encoding. 195 | if (num % 3 === 0) { 196 | if (continuation) { 197 | // Someone put extra continuations. 198 | return { 199 | string: "", 200 | stripped: string 201 | }; 202 | } 203 | 204 | break; 205 | } 206 | 207 | // Else, we need to keep backtracking. 208 | low = high; 209 | high = char; 210 | continue; 211 | } 212 | 213 | const h = this.toHex(high); 214 | const l = this.toHex(low); 215 | if (h === 16 || l === 16) { 216 | // Someone put non hex values. 217 | return { 218 | string: "", 219 | stripped: string 220 | }; 221 | } 222 | 223 | // & %26 224 | // %26 00100110 225 | // α %CE%B1 226 | // %CE 11001110 227 | // %B1 10110001 228 | // ⚡ %E2%9A%A1 229 | // %E2 11100010 230 | // %9A 10011010 231 | // %A1 10100001 232 | // 𝝰 %F0%9D%9D%B0 233 | // %F0 11110000 234 | // %9D 10011101 235 | // %9D 10011101 236 | // %B0 10110000 237 | // Single encodings are guaranteed to have a leading "0" bit in the byte. 238 | // The first of a multi sequence always starts with "11" bits, while the 239 | // "continuation"s always start with "10" bits. 240 | // Spec: http://www.ecma-international.org/ecma-262/6.0/#table-43 241 | const isSingle = (h & 8) === 0; 242 | const isContinuationStart = (~h & 12) === 0; 243 | 244 | if (isSingle || isContinuationStart) { 245 | continuation = false; 246 | 247 | // If a single is full (has 3 chars), we don't need to truncate it. 248 | // If a continuation is full (chars depends on the offset of the leftmost 249 | // "0" bit), we don't need to truncate it. 250 | let escapes = 3; 251 | if (isContinuationStart) { 252 | if ((h & 2) === 0) { 253 | escapes = 6; 254 | } else if ((h & 1) === 0) { 255 | escapes = 9; 256 | } else if ((l & 8) === 0) { 257 | escapes = 12; 258 | } else if (num > 0 && num % 3 === 0) { 259 | // Someone put random hex values together. 260 | return { 261 | string: "", 262 | stripped: string 263 | }; 264 | } 265 | } 266 | 267 | if (num > escapes) { 268 | // Someone put extra continuations. 269 | return { 270 | string: "", 271 | stripped: string 272 | }; 273 | } 274 | 275 | if (num < escapes) { 276 | // We're at a broken sequence, truncate to here. 277 | end = pos; 278 | } 279 | 280 | break; 281 | } else { 282 | // A trailing % does not count as a continuation. 283 | if (pos < length - 1) { 284 | continuation = true; 285 | } 286 | } 287 | 288 | // Detect possible DOS attacks. Credible strings can never be worse than 289 | // the longest (4) escape sequence (3 chars) minus one (the trim). 290 | if (num > 4 * 3 - 1) { 291 | return { 292 | string: "", 293 | stripped: string 294 | }; 295 | } 296 | 297 | // Intentionally set a bad hex value 298 | high = low = 'e'; 299 | } 300 | 301 | if (end === length) { 302 | return { 303 | string, 304 | stripped: "" 305 | }; 306 | } 307 | 308 | return { 309 | string: string.substr(0, end), 310 | stripped: string.substr(end, length) 311 | }; 312 | } 313 | 314 | static strip(string) { 315 | const length = string.length; 316 | const shift = this.hasPercent(string, length); 317 | 318 | // If no % in the last 3 chars, then the string wasn't trimmed. 319 | if (shift === 3) { 320 | return { 321 | string, 322 | stripped: "" 323 | }; 324 | } 325 | 326 | return this._strip(string, length, shift); 327 | } 328 | } 329 | 330 | return class URLDecode extends Plugin { 331 | constructor() { 332 | super(); 333 | this.onStart = this.onStart.bind(this); 334 | this.getSettingsPanel = this.getSettingsPanel.bind(this); 335 | this.saveSettings = this.saveSettings.bind(this); 336 | } 337 | 338 | onStart() { 339 | const msgModule = BdApi.Webpack.getModule(BdApi.Webpack.Filters.byStrings("childrenRepliedMessage"), { 340 | defaultExport: false 341 | }); 342 | Patcher.before(msgModule, "Z", (_, args) => { 343 | for (const item of args) { 344 | if (item.childrenMessageContent) { 345 | const msg = item.childrenMessageContent; 346 | if (this.settings.general.decodeChat) { 347 | if (msg.props && msg.props.content && Symbol.iterator in msg.props.content) { 348 | for (const elem of msg.props.content) { 349 | if (!(elem instanceof String || typeof elem === "string")) { 350 | if (elem.props && elem.props.href) { 351 | const newUrl = this.decodeText(elem.props.href); 352 | elem.props.title = newUrl; 353 | elem.props.children.forEach((element, index, arr) => { 354 | if (element instanceof String || typeof element === "string") { 355 | arr[index] = arr[index].replace(elem.props.href, newUrl); 356 | } 357 | }); 358 | } else if (elem.props && elem.props.renderTextElement && !elem.props.renderTextElementURLDecode) { 359 | const func = elem.props.renderTextElement; 360 | elem.props.renderTextElement = (e, t) => { 361 | if (!(e instanceof String || typeof e === "string") && e.props && e.props.href) { 362 | const newUrl = this.decodeText(e.props.href); 363 | e.props.title = newUrl; 364 | e.props.children.forEach((element, index, arr) => { 365 | if (element instanceof String || typeof element === "string") { 366 | arr[index] = arr[index].replace(e.props.href, newUrl); 367 | } 368 | }); 369 | } 370 | return func(e, t); 371 | }; 372 | elem.props.renderTextElementURLDecode = true; 373 | } 374 | } 375 | } 376 | } 377 | } 378 | if (this.settings.general.decodeEmbed) { 379 | if (msg.props && msg.props.message && msg.props.message.embeds && Symbol.iterator in msg.props.message.embeds) { 380 | for (const embed of msg.props.message.embeds) { 381 | if (embed.url) { 382 | embed.rawTitle = this.decodeText(embed.rawTitle); 383 | embed.rawDescription = this.decodeText(embed.rawDescription); 384 | } 385 | } 386 | } 387 | } 388 | } 389 | return; 390 | } 391 | }); 392 | } 393 | 394 | onStop() { 395 | Patcher.unpatchAll(); 396 | } 397 | 398 | decodeText(text) { 399 | if (text) { 400 | text = text.replace(/\+/g, " "); 401 | try { 402 | return decodeURIComponent(text); 403 | } catch { 404 | try { 405 | let suffix = ""; 406 | if (text.endsWith("...")) { 407 | text = text.slice(0, -3); 408 | suffix = "..."; 409 | } 410 | const strippedData = StripInvalidTrailingEncoding.strip(text); 411 | if (strippedData.string) { 412 | return decodeURIComponent(strippedData.string) + suffix; 413 | } 414 | } catch {} 415 | } 416 | } 417 | return text; 418 | } 419 | 420 | getSettingsPanel() { 421 | const panel = this.buildSettingsPanel(); 422 | return panel.getElement(); 423 | } 424 | 425 | saveSettings(category, setting, value) { 426 | this.settings[category][setting] = value; 427 | PluginUtilities.saveSettings(config.info.name, this.settings); 428 | } 429 | }; 430 | } 431 | return plugin(Plugin, Api); 432 | })(global.ZeresPluginLibrary.buildPlugin(config)); 433 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /SendTimestamps.plugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @name SendTimestamps 3 | * @version 2.2.2 4 | * @description Send timestamps in your messages easily by right clicking the text input. 5 | * @author Taimoor 6 | * @authorId 220161488516546561 7 | * @authorLink https://github.com/Taimoor-Tariq 8 | * @source https://github.com/PseudoResonance/BetterDiscord-Theme/blob/master/SendTimestamps.plugin.js 9 | * @updateUrl https://raw.githubusercontent.com/PseudoResonance/BetterDiscord-Theme/master/SendTimestamps.plugin.js 10 | */ 11 | /*@cc_on 12 | @if (@_jscript) 13 | 14 | // Offer to self-install for clueless users that try to run this directly. 15 | var shell = WScript.CreateObject("WScript.Shell"); 16 | var fs = new ActiveXObject("Scripting.FileSystemObject"); 17 | var pathPlugins = shell.ExpandEnvironmentStrings("%APPDATA%\\BetterDiscord\\plugins"); 18 | var pathSelf = WScript.ScriptFullName; 19 | // Put the user at ease by addressing them in the first person 20 | shell.Popup("It looks like you've mistakenly tried to run me directly. \n(Don't do that!)", 0, "I'm a plugin for BetterDiscord", 0x30); 21 | if (fs.GetParentFolderName(pathSelf) === fs.GetAbsolutePathName(pathPlugins)) { 22 | shell.Popup("I'm in the correct folder already.", 0, "I'm already installed", 0x40); 23 | } else if (!fs.FolderExists(pathPlugins)) { 24 | shell.Popup("I can't find the BetterDiscord plugins folder.\nAre you sure it's even installed?", 0, "Can't install myself", 0x10); 25 | } else if (shell.Popup("Should I copy myself to BetterDiscord's plugins folder for you?", 0, "Do you need some help?", 0x34) === 6) { 26 | fs.CopyFile(pathSelf, fs.BuildPath(pathPlugins, fs.GetFileName(pathSelf)), true); 27 | // Show the user where to put plugins in the future 28 | shell.Exec("explorer " + pathPlugins); 29 | shell.Popup("I'm installed!", 0, "Successfully installed", 0x40); 30 | } 31 | WScript.Quit(); 32 | 33 | @else@*/ 34 | 35 | /* 36 | Licensed under GPL version 2 by Taimoor 37 | https://github.com/Taimoor-Tariq/BetterDiscordStuff/blob/main/Plugins/SendTimestamps/SendTimestamps.plugin.js 38 | 39 | GNU GENERAL PUBLIC LICENSE 40 | Version 2, June 1991 41 | 42 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 43 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 44 | Everyone is permitted to copy and distribute verbatim copies 45 | of this license document, but changing it is not allowed. 46 | 47 | Preamble 48 | 49 | The licenses for most software are designed to take away your 50 | freedom to share and change it. By contrast, the GNU General Public 51 | License is intended to guarantee your freedom to share and change free 52 | software--to make sure the software is free for all its users. This 53 | General Public License applies to most of the Free Software 54 | Foundation's software and to any other program whose authors commit to 55 | using it. (Some other Free Software Foundation software is covered by 56 | the GNU Lesser General Public License instead.) You can apply it to 57 | your programs, too. 58 | 59 | When we speak of free software, we are referring to freedom, not 60 | price. Our General Public Licenses are designed to make sure that you 61 | have the freedom to distribute copies of free software (and charge for 62 | this service if you wish), that you receive source code or can get it 63 | if you want it, that you can change the software or use pieces of it 64 | in new free programs; and that you know you can do these things. 65 | 66 | To protect your rights, we need to make restrictions that forbid 67 | anyone to deny you these rights or to ask you to surrender the rights. 68 | These restrictions translate to certain responsibilities for you if you 69 | distribute copies of the software, or if you modify it. 70 | 71 | For example, if you distribute copies of such a program, whether 72 | gratis or for a fee, you must give the recipients all the rights that 73 | you have. You must make sure that they, too, receive or can get the 74 | source code. And you must show them these terms so they know their 75 | rights. 76 | 77 | We protect your rights with two steps: (1) copyright the software, and 78 | (2) offer you this license which gives you legal permission to copy, 79 | distribute and/or modify the software. 80 | 81 | Also, for each author's protection and ours, we want to make certain 82 | that everyone understands that there is no warranty for this free 83 | software. If the software is modified by someone else and passed on, we 84 | want its recipients to know that what they have is not the original, so 85 | that any problems introduced by others will not reflect on the original 86 | authors' reputations. 87 | 88 | Finally, any free program is threatened constantly by software 89 | patents. We wish to avoid the danger that redistributors of a free 90 | program will individually obtain patent licenses, in effect making the 91 | program proprietary. To prevent this, we have made it clear that any 92 | patent must be licensed for everyone's free use or not licensed at all. 93 | 94 | The precise terms and conditions for copying, distribution and 95 | modification follow. 96 | 97 | GNU GENERAL PUBLIC LICENSE 98 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 99 | 100 | 0. This License applies to any program or other work which contains 101 | a notice placed by the copyright holder saying it may be distributed 102 | under the terms of this General Public License. The "Program", below, 103 | refers to any such program or work, and a "work based on the Program" 104 | means either the Program or any derivative work under copyright law: 105 | that is to say, a work containing the Program or a portion of it, 106 | either verbatim or with modifications and/or translated into another 107 | language. (Hereinafter, translation is included without limitation in 108 | the term "modification".) Each licensee is addressed as "you". 109 | 110 | Activities other than copying, distribution and modification are not 111 | covered by this License; they are outside its scope. The act of 112 | running the Program is not restricted, and the output from the Program 113 | is covered only if its contents constitute a work based on the 114 | Program (independent of having been made by running the Program). 115 | Whether that is true depends on what the Program does. 116 | 117 | 1. You may copy and distribute verbatim copies of the Program's 118 | source code as you receive it, in any medium, provided that you 119 | conspicuously and appropriately publish on each copy an appropriate 120 | copyright notice and disclaimer of warranty; keep intact all the 121 | notices that refer to this License and to the absence of any warranty; 122 | and give any other recipients of the Program a copy of this License 123 | along with the Program. 124 | 125 | You may charge a fee for the physical act of transferring a copy, and 126 | you may at your option offer warranty protection in exchange for a fee. 127 | 128 | 2. You may modify your copy or copies of the Program or any portion 129 | of it, thus forming a work based on the Program, and copy and 130 | distribute such modifications or work under the terms of Section 1 131 | above, provided that you also meet all of these conditions: 132 | 133 | a) You must cause the modified files to carry prominent notices 134 | stating that you changed the files and the date of any change. 135 | 136 | b) You must cause any work that you distribute or publish, that in 137 | whole or in part contains or is derived from the Program or any 138 | part thereof, to be licensed as a whole at no charge to all third 139 | parties under the terms of this License. 140 | 141 | c) If the modified program normally reads commands interactively 142 | when run, you must cause it, when started running for such 143 | interactive use in the most ordinary way, to print or display an 144 | announcement including an appropriate copyright notice and a 145 | notice that there is no warranty (or else, saying that you provide 146 | a warranty) and that users may redistribute the program under 147 | these conditions, and telling the user how to view a copy of this 148 | License. (Exception: if the Program itself is interactive but 149 | does not normally print such an announcement, your work based on 150 | the Program is not required to print an announcement.) 151 | 152 | These requirements apply to the modified work as a whole. If 153 | identifiable sections of that work are not derived from the Program, 154 | and can be reasonably considered independent and separate works in 155 | themselves, then this License, and its terms, do not apply to those 156 | sections when you distribute them as separate works. But when you 157 | distribute the same sections as part of a whole which is a work based 158 | on the Program, the distribution of the whole must be on the terms of 159 | this License, whose permissions for other licensees extend to the 160 | entire whole, and thus to each and every part regardless of who wrote it. 161 | 162 | Thus, it is not the intent of this section to claim rights or contest 163 | your rights to work written entirely by you; rather, the intent is to 164 | exercise the right to control the distribution of derivative or 165 | collective works based on the Program. 166 | 167 | In addition, mere aggregation of another work not based on the Program 168 | with the Program (or with a work based on the Program) on a volume of 169 | a storage or distribution medium does not bring the other work under 170 | the scope of this License. 171 | 172 | 3. You may copy and distribute the Program (or a work based on it, 173 | under Section 2) in object code or executable form under the terms of 174 | Sections 1 and 2 above provided that you also do one of the following: 175 | 176 | a) Accompany it with the complete corresponding machine-readable 177 | source code, which must be distributed under the terms of Sections 178 | 1 and 2 above on a medium customarily used for software interchange; or, 179 | 180 | b) Accompany it with a written offer, valid for at least three 181 | years, to give any third party, for a charge no more than your 182 | cost of physically performing source distribution, a complete 183 | machine-readable copy of the corresponding source code, to be 184 | distributed under the terms of Sections 1 and 2 above on a medium 185 | customarily used for software interchange; or, 186 | 187 | c) Accompany it with the information you received as to the offer 188 | to distribute corresponding source code. (This alternative is 189 | allowed only for noncommercial distribution and only if you 190 | received the program in object code or executable form with such 191 | an offer, in accord with Subsection b above.) 192 | 193 | The source code for a work means the preferred form of the work for 194 | making modifications to it. For an executable work, complete source 195 | code means all the source code for all modules it contains, plus any 196 | associated interface definition files, plus the scripts used to 197 | control compilation and installation of the executable. However, as a 198 | special exception, the source code distributed need not include 199 | anything that is normally distributed (in either source or binary 200 | form) with the major components (compiler, kernel, and so on) of the 201 | operating system on which the executable runs, unless that component 202 | itself accompanies the executable. 203 | 204 | If distribution of executable or object code is made by offering 205 | access to copy from a designated place, then offering equivalent 206 | access to copy the source code from the same place counts as 207 | distribution of the source code, even though third parties are not 208 | compelled to copy the source along with the object code. 209 | 210 | 4. You may not copy, modify, sublicense, or distribute the Program 211 | except as expressly provided under this License. Any attempt 212 | otherwise to copy, modify, sublicense or distribute the Program is 213 | void, and will automatically terminate your rights under this License. 214 | However, parties who have received copies, or rights, from you under 215 | this License will not have their licenses terminated so long as such 216 | parties remain in full compliance. 217 | 218 | 5. You are not required to accept this License, since you have not 219 | signed it. However, nothing else grants you permission to modify or 220 | distribute the Program or its derivative works. These actions are 221 | prohibited by law if you do not accept this License. Therefore, by 222 | modifying or distributing the Program (or any work based on the 223 | Program), you indicate your acceptance of this License to do so, and 224 | all its terms and conditions for copying, distributing or modifying 225 | the Program or works based on it. 226 | 227 | 6. Each time you redistribute the Program (or any work based on the 228 | Program), the recipient automatically receives a license from the 229 | original licensor to copy, distribute or modify the Program subject to 230 | these terms and conditions. You may not impose any further 231 | restrictions on the recipients' exercise of the rights granted herein. 232 | You are not responsible for enforcing compliance by third parties to 233 | this License. 234 | 235 | 7. If, as a consequence of a court judgment or allegation of patent 236 | infringement or for any other reason (not limited to patent issues), 237 | conditions are imposed on you (whether by court order, agreement or 238 | otherwise) that contradict the conditions of this License, they do not 239 | excuse you from the conditions of this License. If you cannot 240 | distribute so as to satisfy simultaneously your obligations under this 241 | License and any other pertinent obligations, then as a consequence you 242 | may not distribute the Program at all. For example, if a patent 243 | license would not permit royalty-free redistribution of the Program by 244 | all those who receive copies directly or indirectly through you, then 245 | the only way you could satisfy both it and this License would be to 246 | refrain entirely from distribution of the Program. 247 | 248 | If any portion of this section is held invalid or unenforceable under 249 | any particular circumstance, the balance of the section is intended to 250 | apply and the section as a whole is intended to apply in other 251 | circumstances. 252 | 253 | It is not the purpose of this section to induce you to infringe any 254 | patents or other property right claims or to contest validity of any 255 | such claims; this section has the sole purpose of protecting the 256 | integrity of the free software distribution system, which is 257 | implemented by public license practices. Many people have made 258 | generous contributions to the wide range of software distributed 259 | through that system in reliance on consistent application of that 260 | system; it is up to the author/donor to decide if he or she is willing 261 | to distribute software through any other system and a licensee cannot 262 | impose that choice. 263 | 264 | This section is intended to make thoroughly clear what is believed to 265 | be a consequence of the rest of this License. 266 | 267 | 8. If the distribution and/or use of the Program is restricted in 268 | certain countries either by patents or by copyrighted interfaces, the 269 | original copyright holder who places the Program under this License 270 | may add an explicit geographical distribution limitation excluding 271 | those countries, so that distribution is permitted only in or among 272 | countries not thus excluded. In such case, this License incorporates 273 | the limitation as if written in the body of this License. 274 | 275 | 9. The Free Software Foundation may publish revised and/or new versions 276 | of the General Public License from time to time. Such new versions will 277 | be similar in spirit to the present version, but may differ in detail to 278 | address new problems or concerns. 279 | 280 | Each version is given a distinguishing version number. If the Program 281 | specifies a version number of this License which applies to it and "any 282 | later version", you have the option of following the terms and conditions 283 | either of that version or of any later version published by the Free 284 | Software Foundation. If the Program does not specify a version number of 285 | this License, you may choose any version ever published by the Free Software 286 | Foundation. 287 | 288 | 10. If you wish to incorporate parts of the Program into other free 289 | programs whose distribution conditions are different, write to the author 290 | to ask for permission. For software which is copyrighted by the Free 291 | Software Foundation, write to the Free Software Foundation; we sometimes 292 | make exceptions for this. Our decision will be guided by the two goals 293 | of preserving the free status of all derivatives of our free software and 294 | of promoting the sharing and reuse of software generally. 295 | 296 | NO WARRANTY 297 | 298 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 299 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 300 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 301 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 302 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 303 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 304 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 305 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 306 | REPAIR OR CORRECTION. 307 | 308 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 309 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 310 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 311 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 312 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 313 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 314 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 315 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 316 | POSSIBILITY OF SUCH DAMAGES. 317 | 318 | END OF TERMS AND CONDITIONS 319 | 320 | How to Apply These Terms to Your New Programs 321 | 322 | If you develop a new program, and you want it to be of the greatest 323 | possible use to the public, the best way to achieve this is to make it 324 | free software which everyone can redistribute and change under these terms. 325 | 326 | To do so, attach the following notices to the program. It is safest 327 | to attach them to the start of each source file to most effectively 328 | convey the exclusion of warranty; and each file should have at least 329 | the "copyright" line and a pointer to where the full notice is found. 330 | 331 | 332 | Copyright (C) 333 | 334 | This program is free software; you can redistribute it and/or modify 335 | it under the terms of the GNU General Public License as published by 336 | the Free Software Foundation; either version 2 of the License, or 337 | (at your option) any later version. 338 | 339 | This program is distributed in the hope that it will be useful, 340 | but WITHOUT ANY WARRANTY; without even the implied warranty of 341 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 342 | GNU General Public License for more details. 343 | 344 | You should have received a copy of the GNU General Public License along 345 | with this program; if not, write to the Free Software Foundation, Inc., 346 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 347 | 348 | Also add information on how to contact you by electronic and paper mail. 349 | 350 | If the program is interactive, make it output a short notice like this 351 | when it starts in an interactive mode: 352 | 353 | Gnomovision version 69, Copyright (C) year name of author 354 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 355 | This is free software, and you are welcome to redistribute it 356 | under certain conditions; type `show c' for details. 357 | 358 | The hypothetical commands `show w' and `show c' should show the appropriate 359 | parts of the General Public License. Of course, the commands you use may 360 | be called something other than `show w' and `show c'; they could even be 361 | mouse-clicks or menu items--whatever suits your program. 362 | 363 | You should also get your employer (if you work as a programmer) or your 364 | school, if any, to sign a "copyright disclaimer" for the program, if 365 | necessary. Here is a sample; alter the names: 366 | 367 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 368 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 369 | 370 | , 1 April 1989 371 | Ty Coon, President of Vice 372 | 373 | This General Public License does not permit incorporating your program into 374 | proprietary programs. If your program is a subroutine library, you may 375 | consider it more useful to permit linking proprietary applications with the 376 | library. If this is what you want to do, use the GNU Lesser General 377 | Public License instead of this License. 378 | */ 379 | 380 | module.exports = (() => { 381 | const config = { 382 | info: { 383 | name: 'SendTimestamps', 384 | version: '2.2.2', 385 | description: 'Send timestamps in your messages easily by right clicking the text input.', 386 | author: 'Taimoor', 387 | authorId: '220161488516546561', 388 | authorLink: 'https://github.com/Taimoor-Tariq', 389 | github: 'https://github.com/PseudoResonance/BetterDiscord-Theme/blob/master/SendTimestamps.plugin.js', 390 | github_raw: 'https://raw.githubusercontent.com/PseudoResonance/BetterDiscord-Theme/master/SendTimestamps.plugin.js', 391 | authors: [{ 392 | name: 'Taimoor', 393 | discord_id: '220161488516546561' 394 | }, { 395 | name: 'PseudoResonance' 396 | } 397 | ] 398 | }, 399 | changelog: [{ 400 | title: 'v2.2.2 - Update', 401 | type: 'fixed', 402 | items: ['Updated to BetterDiscord 1.9.3'] 403 | } 404 | ], 405 | main: 'index.js', 406 | }; 407 | 408 | return !global.ZeresPluginLibrary 409 | ? class { 410 | constructor() { 411 | this._config = config; 412 | } 413 | getName() { 414 | return config.info.name; 415 | } 416 | getAuthor() { 417 | return config.info.authors.map((a) => a.name).join(', '); 418 | } 419 | getDescription() { 420 | return config.info.description; 421 | } 422 | getVersion() { 423 | return config.info.version; 424 | } 425 | load() { 426 | BdApi.UI.showConfirmationModal('Library Missing', `The library plugin needed for ${config.info.name} is missing. Please click Download Now to install it.`, { 427 | confirmText: 'Download Now', 428 | cancelText: 'Cancel', 429 | onConfirm: () => { 430 | require('request').get('https://rauenzi.github.io/BDPluginLibrary/release/0PluginLibrary.plugin.js', async(error, response, body) => { 431 | if (error) 432 | return require('electron').shell.openExternal('https://betterdiscord.net/ghdl?url=https://raw.githubusercontent.com/rauenzi/BDPluginLibrary/master/release/0PluginLibrary.plugin.js'); 433 | await new Promise((r) => require('fs').writeFile(require('path').join(BdApi.Plugins.folder, '0PluginLibrary.plugin.js'), body, r)); 434 | }); 435 | }, 436 | }); 437 | } 438 | start() {} 439 | stop() {} 440 | } 441 | : (([Plugin, Api]) => { 442 | const plugin = (Plugin, Api) => { 443 | const { 444 | PluginUtilities, 445 | Patcher, 446 | Modals, 447 | DOMTools, 448 | WebpackModules, 449 | DiscordModules: { 450 | React, 451 | MessageActions, 452 | Slider, 453 | Dropdown, 454 | SwitchRow, 455 | LocaleManager, 456 | DiscordPermissions 457 | }, 458 | } = Api; 459 | let ComponentDispatch = null; 460 | let ComponentActions = null; 461 | const css = `.timestamp-button { 462 | margin-top: 4px; 463 | max-height: 40px; 464 | justify-content: center; 465 | background-color: transparent; 466 | } 467 | 468 | .timestamp-button button { 469 | min-height: 32px; 470 | min-width: 32px; 471 | background-color: transparent; 472 | } 473 | .timestamp-button svg { 474 | width: 20px; 475 | height: 20px; 476 | color: var(--interactive-normal); 477 | } 478 | .timestamp-button svg:hover { 479 | color: var(--interactive-hover); 480 | } 481 | 482 | .channel-attach-button { 483 | display: flex; 484 | margin-right: 8px; 485 | } 486 | 487 | .channel-attach-button .attachButton-_ACFSu { 488 | padding: 10px 4px; 489 | } 490 | 491 | .timestamp-input-label { 492 | font-size: 16px; 493 | color: var(--text-normal); 494 | margin-left: 4px; 495 | } 496 | 497 | .timestamp-input { 498 | font-size: 16px; 499 | box-sizing: border-box; 500 | width: 100%; 501 | border-radius: 3px; 502 | color: var(--text-normal); 503 | background-color: var(--deprecated-text-input-bg); 504 | border: 1px solid var(--deprecated-text-input-border); 505 | transition: border-color 0.2s ease-in-out; 506 | padding: 10px; 507 | margin: 8px 0 12px 0px; 508 | } 509 | 510 | .timestamp-input-dropdown { 511 | font-size: 16px; 512 | border-radius: 3px; 513 | color: var(--text-normal); 514 | background-color: var(--deprecated-text-input-bg); 515 | border: 1px solid var(--deprecated-text-input-border); 516 | transition: border-color 0.2s ease-in-out; 517 | margin: 8px 0 12px 0px; 518 | } 519 | 520 | input::-webkit-calendar-picker-indicator { 521 | width: 16px; 522 | height: 16px; 523 | background-size: contain; 524 | background-position: center; 525 | background-repeat: no-repeat; 526 | filter: invert(80%); 527 | cursor: pointer; 528 | } 529 | 530 | input[type='time']::-webkit-calendar-picker-indicator { 531 | background-image: url("data:image/svg+xml,%3Csvg role='img' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath fill='currentColor' d='M256,8C119,8,8,119,8,256S119,504,256,504,504,393,504,256,393,8,256,8Zm92.49,313h0l-20,25a16,16,0,0,1-22.49,2.5h0l-67-49.72a40,40,0,0,1-15-31.23V112a16,16,0,0,1,16-16h32a16,16,0,0,1,16,16V256l58,42.5A16,16,0,0,1,348.49,321Z'%3E%3C/path%3E%3C/svg%3E"); 532 | } 533 | input[type='date']::-webkit-calendar-picker-indicator { 534 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-1.99.9-1.99 2L3 19a2 2 0 0 0 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v11zM7 10h5v5H7v-5z'/%3E%3C/svg%3E"); 535 | } 536 | 537 | .timestamp-formats-wrapper { 538 | position: absolute; 539 | top: 0; 540 | width: 90%; 541 | } 542 | 543 | .timestamp-formats-selector { 544 | position: absolute; 545 | bottom: 0; 546 | width: 100%; 547 | background-color: var(--background-tertiary); 548 | box-shadow: var(--elevation-high); 549 | color: var(--text-normal); 550 | border-radius: 5px; 551 | padding: 8px 12px; 552 | } 553 | 554 | .timestamp-formats-selects { 555 | display: flex; 556 | flex-direction: row; 557 | padding: 8px 12px; 558 | gap: 8px; 559 | flex-wrap: wrap; 560 | } 561 | 562 | .timestamp-formats-selects select { 563 | padding: 8px 12px; 564 | border-radius: 5px; 565 | border: 1px solid var(--background-secondary); 566 | background-color: var(--channeltextarea-background); 567 | color: var(--text-normal); 568 | font-size: 1rem; 569 | flex-grow: 1; 570 | } 571 | `; 572 | 573 | const Button = WebpackModules.getByProps('Button').Button; 574 | 575 | const canSendMessages = (channelId) => { 576 | return BdApi.findModule(BdApi.Webpack.Filters.byProps('getChannelPermissions')).canWithPartialContext(DiscordPermissions.SEND_MESSAGES, { 577 | channelId 578 | }); 579 | }; 580 | 581 | let unpatchButton = null; 582 | 583 | return class SendTimestamp extends Plugin { 584 | constructor() { 585 | super(); 586 | 587 | this.defaultSettings = { 588 | timestampFormat: 'f', 589 | }; 590 | 591 | this.forceOnRight = false; 592 | this.locale = LocaleManager.getLocale() ?? 'en'; 593 | 594 | this.sendFormatOptions = { 595 | 0: 'F', 596 | }; 597 | 598 | this.replaceTextAreaText = (text) => { 599 | if (!ComponentDispatch) { 600 | this.getDiscordInternals() 601 | } 602 | ComponentDispatch.dispatchToLastSubscribed(ComponentActions.CLEAR_TEXT); 603 | setImmediate(() => { 604 | ComponentDispatch.dispatchToLastSubscribed(ComponentActions.INSERT_TEXT, { 605 | content: text, 606 | plainText: text 607 | }); 608 | }); 609 | }; 610 | } 611 | 612 | onStart() { 613 | this.getDiscordInternals(); 614 | PluginUtilities.addStyle(this.getName(), css); 615 | this.patchButton(); 616 | } 617 | 618 | onStop() { 619 | this.domObserver?.unsubscribeAll(); 620 | PluginUtilities.removeStyle(this.getName()); 621 | Patcher.unpatchAll(); 622 | if (unpatchButton) 623 | unpatchButton(); 624 | } 625 | 626 | load() { 627 | const myAdditions = (e) => { 628 | const pluginCard = e.target.querySelector(`#${this.getName()}-card`); 629 | if (pluginCard) { 630 | const controls = pluginCard.querySelector('.bd-controls'); 631 | const changeLogButton = DOMTools.createElement( 632 | ``); 633 | changeLogButton.addEventListener('click', () => { 634 | Api.Modals.showChangelogModal(this.getName(), this.getVersion(), this._config.changelog); 635 | }); 636 | 637 | if (!controls.querySelector('.bd-changelog-button') && this._config.changelog?.length > 0) 638 | controls.prepend(changeLogButton); 639 | } 640 | }; 641 | 642 | this.domObserver = new Api.DOMTools.DOMObserver(); 643 | this.domObserver.subscribeToQuerySelector(myAdditions, `#${this.getName()}-card`); 644 | 645 | let userSettings = this.loadSettings(); 646 | if (!userSettings.usingVersion || userSettings.usingVersion < this.getVersion()) { 647 | this.saveSettings({ 648 | ...this.loadSettings(), 649 | usingVersion: this.getVersion() 650 | }); 651 | Api.Modals.showChangelogModal(this.getName(), this.getVersion(), this._config.changelog); 652 | } 653 | } 654 | 655 | getDiscordInternals() { 656 | ComponentDispatch = BdApi.Webpack.getModule(m => m.dispatchToLastSubscribed && m.emitter?._events?.INSERT_TEXT, { 657 | searchExports: true 658 | }); 659 | let getComponentActionsKey = null; 660 | const setComponentActionsKey = (val) => { 661 | getComponentActionsKey = val; 662 | }; 663 | const getComponentActionsModule = ZeresPluginLibrary.WebpackModules.getModule((m) => { 664 | for (const k in m) { 665 | for (const j in m[k]) { 666 | if (m[k][j]?.toString().includes("CLEAR_TEXT")) { 667 | setComponentActionsKey(k); 668 | return true; 669 | } 670 | } 671 | } 672 | }); 673 | ComponentActions = getComponentActionsModule[getComponentActionsKey]; 674 | } 675 | 676 | showTimestampModal() { 677 | const inputFormat = this.settings.timestampFormat; 678 | 679 | const getRelativeTime = (timestamp) => { 680 | const timeElapsed = timestamp - new Date(new Date().getTime() - new Date().getTimezoneOffset() * 180000); 681 | const units = { 682 | year: 24 * 60 * 60 * 1000 * 365, 683 | month: (24 * 60 * 60 * 1000 * 365) / 12, 684 | day: 24 * 60 * 60 * 1000, 685 | hour: 60 * 60 * 1000, 686 | minute: 60 * 1000, 687 | second: 1000, 688 | }; 689 | 690 | for (let u in units) 691 | if (Math.abs(timeElapsed) > units[u] || u == 'second') 692 | return new Intl.RelativeTimeFormat('en', { 693 | numeric: 'auto' 694 | }).format(Math.round(timeElapsed / units[u]), u); 695 | }; 696 | 697 | const updateTimeFormat = (format) => { 698 | this.settings.timestampFormat = format; 699 | this.saveSettings(); 700 | }; 701 | 702 | const isValidDate = (d) => { 703 | return d instanceof Date && !isNaN(d); 704 | }; 705 | 706 | let inputTimestamp = new Date(); 707 | 708 | class TimestampModalBody extends React.Component { 709 | constructor(props) { 710 | super(props); 711 | this.state = { 712 | timestamp: new Date(new Date(inputTimestamp).getTime() - new Date().getTimezoneOffset() * 60000), 713 | returnTimestamp: inputTimestamp, 714 | timestampFormat: inputFormat, 715 | 716 | formatOptions: [{ 717 | value: 't', 718 | label: 'Short Time' 719 | }, { 720 | value: 'T', 721 | label: 'Long Time' 722 | }, { 723 | value: 'd', 724 | label: 'Short Date' 725 | }, { 726 | value: 'D', 727 | label: 'Long Date' 728 | }, { 729 | value: 'f', 730 | label: 'Short Date/Time' 731 | }, { 732 | value: 'F', 733 | label: 'Long Date/Time' 734 | }, { 735 | value: 'R', 736 | label: 'Relative Time' 737 | }, 738 | ], 739 | }; 740 | } 741 | 742 | componentDidMount() { 743 | this.updateFormatOptions(); 744 | } 745 | 746 | componentDidUpdate(prevProps, prevState) { 747 | if (prevState.timestamp != this.state.timestamp) { 748 | inputTimestamp = this.state.returnTimestamp; 749 | this.updateFormatOptions(); 750 | } 751 | } 752 | 753 | updateFormatOptions() { 754 | const time = new Date(new Date(this.state.timestamp).getTime() - new Date().getTimezoneOffset() * 2 * 60000); 755 | this.setState({ 756 | formatOptions: [{ 757 | value: 't', 758 | label: time.toLocaleString(undefined, { 759 | hour: '2-digit', 760 | minute: '2-digit' 761 | }).replace(' at', '') 762 | }, { 763 | value: 'T', 764 | label: time.toLocaleString(undefined, { 765 | timeStyle: 'medium' 766 | }).replace(' at', '') 767 | }, { 768 | value: 'd', 769 | label: time.toLocaleString(undefined, { 770 | dateStyle: 'short' 771 | }).replace(' at', '') 772 | }, { 773 | value: 'D', 774 | label: time.toLocaleString(undefined, { 775 | dateStyle: 'long' 776 | }).replace(' at', '') 777 | }, { 778 | value: 'f', 779 | label: time.toLocaleString(undefined, { 780 | dateStyle: 'long', 781 | timeStyle: 'short' 782 | }).replace(' at', '') 783 | }, { 784 | value: 'F', 785 | label: time.toLocaleString(undefined, { 786 | dateStyle: 'full', 787 | timeStyle: 'short' 788 | }).replace(' at', '') 789 | }, { 790 | value: 'R', 791 | label: getRelativeTime(time) 792 | }, 793 | ], 794 | }); 795 | } 796 | 797 | render() { 798 | const FormatDropdown = React.createElement('div', { 799 | className: 'timestamp-input-group', 800 | children: [ 801 | React.createElement('label', { 802 | className: 'timestamp-input-label', 803 | children: 'Format', 804 | }), 805 | React.createElement('div', { 806 | className: 'timestamp-input-dropdown', 807 | children: [ 808 | React.createElement(Dropdown, { 809 | onChange: (format) => { 810 | this.setState({ 811 | timestampFormat: format, 812 | }); 813 | updateTimeFormat(format); 814 | }, 815 | value: this.state.timestampFormat, 816 | options: this.state.formatOptions, 817 | }), 818 | ], 819 | }), 820 | ], 821 | }); 822 | 823 | const DatePicker = React.createElement('div', { 824 | className: 'timestamp-input-group', 825 | children: [ 826 | React.createElement('label', { 827 | className: 'timestamp-input-label', 828 | children: 'Date', 829 | }), 830 | React.createElement('input', { 831 | className: 'timestamp-input', 832 | type: 'date', 833 | value: this.state.timestamp.toISOString().split('T')[0], 834 | onChange: (e) => { 835 | const date = new Date(`${e.target.value}T${this.state.timestamp.toISOString().split('T')[1]}`); 836 | if (isValidDate(date)) 837 | this.setState({ 838 | timestamp: date, 839 | returnTimestamp: new Date(new Date(date).getTime() + new Date().getTimezoneOffset() * 60000), 840 | }); 841 | }, 842 | }), 843 | ], 844 | }); 845 | 846 | const TimePicker = React.createElement('div', { 847 | className: 'timestamp-input-group', 848 | children: [ 849 | React.createElement('label', { 850 | className: 'timestamp-input-label', 851 | children: 'Time', 852 | }), 853 | React.createElement('input', { 854 | className: 'timestamp-input', 855 | type: 'time', 856 | value: this.state.timestamp.toISOString().split('T')[1].split('.')[0].split(':').slice(0, 2).join(':'), 857 | onChange: (e) => { 858 | let date = new Date(`${this.state.timestamp.toISOString().split('T')[0]}T${e.target.value}:00.000Z`); 859 | if (isValidDate(date)) 860 | this.setState({ 861 | timestamp: date, 862 | returnTimestamp: new Date(new Date(date).getTime() + new Date().getTimezoneOffset() * 60000), 863 | }); 864 | }, 865 | }), 866 | ], 867 | }); 868 | 869 | return React.createElement('div', { 870 | children: [DatePicker, TimePicker, FormatDropdown], 871 | }); 872 | } 873 | } 874 | 875 | Modals.showModal('Select Date and Time', [React.createElement(TimestampModalBody)], { 876 | confirmText: 'Enter', 877 | onConfirm: () => { 878 | let ts_msg = ``; 879 | if (!ComponentDispatch) { 880 | this.getDiscordInternals() 881 | } 882 | ComponentDispatch.dispatchToLastSubscribed(ComponentActions.INSERT_TEXT, { 883 | content: ts_msg, 884 | plainText: ts_msg 885 | }); 886 | }, 887 | }); 888 | } 889 | 890 | patchButton() { 891 | unpatchButton = BdApi.ContextMenu.patch('textarea-context', (menu, props) => { 892 | menu.props.children.splice(menu.props.children.length - 1, 0, ZeresPluginLibrary.DiscordModules.React.createElement(BdApi.ContextMenu.Group, null, ZeresPluginLibrary.DiscordModules.React.createElement(BdApi.ContextMenu.Item, { 893 | id: 'timestamp', 894 | label: 'Add Timestamp', 895 | action: () => { 896 | this.showTimestampModal(); 897 | }, 898 | disabled: false 899 | }))); 900 | }) 901 | } 902 | }; 903 | }; 904 | return plugin(Plugin, Api); 905 | })(global.ZeresPluginLibrary.buildPlugin(config)); 906 | })(); 907 | /*@end@*/ 908 | -------------------------------------------------------------------------------- /EditUploads.plugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @name EditUploads 3 | * @authorLink https://github.com/PseudoResonance 4 | * @source https://github.com/PseudoResonance/BetterDiscord-Theme/blob/master/EditUploads.plugin.js 5 | */ 6 | 7 | // !!! Hey there! If you didn't come here from the BetterDiscord Discord server ( https://discord.gg/2HScm8j ) !!! // 8 | // !!! then please do not use whatever you were using that led you here, getting plugins from places other than !!! // 9 | // !!! the #plugin repo channel in the BD server can be dangerous, as they can be malicious and do bad things. !!! // 10 | module.exports = (() => { 11 | 12 | const config = { 13 | info: { 14 | name: "EditUploads", 15 | authors: [ 16 | { 17 | name: "Qwerasd" 18 | }, 19 | { 20 | name: "PseudoResonance", 21 | discord_id: "152927763605618689", 22 | github_username: "PseudoResonance" 23 | } 24 | ], 25 | version: "0.1.2", 26 | description: "Edit image files before uploading. Uses icons from icons8 https://icons8.com/", 27 | github: "https://github.com/PseudoResonance/BetterDiscord-Theme/blob/master/EditUploads.plugin.js", 28 | github_raw: "https://raw.githubusercontent.com/PseudoResonance/BetterDiscord-Theme/master/EditUploads.plugin.js" 29 | }, 30 | changelog: [ 31 | { 32 | title: "Update", 33 | type: "fixed", 34 | items: [ 35 | "Updated to work with new Discord attachments system" 36 | ] 37 | } 38 | ] 39 | }; 40 | 41 | return !global.ZeresPluginLibrary ? class { 42 | constructor() { 43 | this._config = config; 44 | } 45 | 46 | getName = () => config.info.name; 47 | getAuthor = () => config.info.description; 48 | getVersion = () => config.info.version; 49 | 50 | load() { 51 | BdApi.showConfirmationModal("Library Missing", `The library plugin needed for ${config.info.name} is missing. Please click Download Now to install it.`, { 52 | confirmText: "Download Now", 53 | cancelText: "Cancel", 54 | onConfirm: () => { 55 | require("request").get("https://rauenzi.github.io/BDPluginLibrary/release/0PluginLibrary.plugin.js", async (err, res, body) => { 56 | if (err) return require("electron").shell.openExternal("https://betterdiscord.net/ghdl?url=https://raw.githubusercontent.com/rauenzi/BDPluginLibrary/master/release/0PluginLibrary.plugin.js"); 57 | await new Promise(r => require("fs").writeFile(require("path").join(BdApi.Plugins.folder, "0PluginLibrary.plugin.js"), body, r)); 58 | }); 59 | } 60 | }); 61 | } 62 | 63 | start() {} 64 | stop() {} 65 | } : (([Plugin, Api]) => { 66 | 67 | const plugin = (Plugin, Api) => { 68 | const { Settings, DOMTools, PluginUtilities } = Api; 69 | 70 | return class EditUploads extends Plugin { 71 | 72 | // Initialize plugin 73 | constructor() { 74 | super(); 75 | this.colorWhiteClass = BdApi.findModuleByProps('colorWhite').colorWhite.split(' ')[0]; 76 | this.lookLinkClass = BdApi.findModuleByProps('lookLink').lookLink.split(' ')[0]; 77 | this.uploadModalClass = BdApi.findModuleByProps('uploadModal').uploadModal.split(' ')[0]; 78 | this.textAreaClass = BdApi.findModuleByProps('slateTextArea').slateTextArea.split(' ')[0]; 79 | this.descriptionClass = BdApi.findModuleByProps('file', 'filename', 'comment', 'description').description; 80 | this.iconClass = BdApi.findModuleByProps('file', 'filename', 'comment', 'description').icon; 81 | this.icons = { 82 | rectangle: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAB+gAAAfoBF4pEbwAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAABZSURBVFiF7ZYxCoBAEAMT8f8/EkH8V2xOOBThmlssZtotMtkqEkAjyZ46tjvXnUAqC9u2JK1fh1k8iy4zw0ZAAAEEEEAAAQQQeC2i6mnWf+AszD0KswB+zgV/VXy+7ejmKwAAAABJRU5ErkJggg==', 83 | filledRectangle: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAIAAAACAAF+ftPjAAAFFmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDUgNzkuMTYzNDk5LCAyMDE4LzA4LzEzLTE2OjQwOjIyICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDIxLTA3LTI5VDAxOjE3LTA3OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAyMS0wNy0yOVQwMToxOToyMC0wNzowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAyMS0wNy0yOVQwMToxOToyMC0wNzowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpkNjAxZjcyYS0zODNmLWFjNDktYTMwZS0xOTMwNzExMzczZmIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6ZDYwMWY3MmEtMzgzZi1hYzQ5LWEzMGUtMTkzMDcxMTM3M2ZiIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6ZDYwMWY3MmEtMzgzZi1hYzQ5LWEzMGUtMTkzMDcxMTM3M2ZiIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDpkNjAxZjcyYS0zODNmLWFjNDktYTMwZS0xOTMwNzExMzczZmIiIHN0RXZ0OndoZW49IjIwMjEtMDctMjlUMDE6MTctMDc6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE5IChXaW5kb3dzKSIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz6OUQQGAAAAQElEQVRYw+3XIQ4AIAwDwP3/T4QQHjYUDj0mrkl1zzYyM342ADoBZtZlvADVAQAAAAAAAAAA6AfYhePLLwAAuD0/rllvKVK7PgAAAABJRU5ErkJggg==', 84 | crop: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAB+gAAAfoBF4pEbwAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAFkSURBVFiF5ZY/TsMwGMXfBz0CQlyChQkWWOAIWSs6IFDDnx2JkyBK587cgQNwBBhgQoxItD+GuhCKGzuNEwnxpCiJ/OX9nmNbtvTfBKzHFO0AGZAlhveBN2A7VDjCKSE8BybO9hVYay0AcMm3xkBv1tYpFO1KunKvmx6TYnsVdSTtzWwk5WZ260uZ4VGovYLGwOE8d2WJHi2rOzMbRlXWnQNAF/iY+wMjX23yPwB0JQ0krWo65qVKGgDIJQ0dfCKpV/pBiVHlIeDnOv+acKEhSBIAOPXBWwlQBm88QAjeaADgLARvLIAHvnC2Jw8AnMfCkweoCk8aALioCk8WYFl4kgDAcQE+AfrRRlOvfXf9OmPEBijC8yrwGHXCJTJNN5YjMxukDrBoN3z21N2QTlnR2KdrSe8JOhiUdwjM7AHYknQiaaMmY9bbJ0n37vmxpme8QsuwzUOpVzGroK4O3P2lBdYf1CeOTDUFiQFoXAAAAABJRU5ErkJggg==', 85 | blur: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAB+gAAAfoBF4pEbwAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAIQSURBVFiF1ZZPS1RhFMbPa1CtZjJpldIfA2nATRAhbRoNW7gI+gIFQgulIj+EG6EwCsrWkoIDgiD0BVoURGnFlCuFFKKVocxUzs+FL8xT3hv3vXdmpAMXzr3vec7znPP+ua/Z/2xAJ3DyoMjbgU/AR+BYq8kPAYvUbQFoa6WAJ+y38VaR3xLS7/4B2AGGmk1+Aah4wipwGSgCv/y3H0ChWeR5YEWqvy9jY/K9DOSaIWBKSOYAJ2MOKMn400aTDwI1n3w1qkLfoTUfUwOuNor8OLAuia/8I7YoQr8C7Y0Q8EBa+yxB/HOJn8hK3u1Xe2zrIzA5mYoKcCaLgHmp5noA7obgSmnJCzKfr3TVJ8A64LWsm540Ah5JFYMp8EOCfxgKPkL9iC2HVC852oAvPsc34HBUXNwfbMDMOrw/45wjVIBzrmZms/71hJkVQwT0ib8YSh6D7YsKiBPQJf7nDALK4p8KEaAt/51BgGIjpzFOwIb4XTExSUyx6yECPojfn0HAgPjLiVHsXTh/+i30JsM2fOtzVIF8aIIZOUhuphAwLPjpULwB56QLW8DFAOwlYDvzDwm4J1Vs+apir9++7beFHGA0Fbkkfcyf9g64C5wHjvqn4MW+/yt2MhO5iLhD/V6QxCrASEPIRcRZ4IWsiyirAtPA6aR502yvvJldM7Neqx+vq2a2ZGYvnXOboTkP1HYBffUf8YXFeiQAAAAASUVORK5CYII=', 86 | inverseBlur: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAB+gAAAfoBF4pEbwAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAF9SURBVFiF1ZW7SgNBGEa/UbCwMEaxMuIFRRBs7Ky0E7HxDbQMgq9hIwiCgtiLlRcQBF9DiAhWgkkhWHgpNoo5FnFJNDtLdmc24tf+8J2zw8780n8OUACG/wqeB26AEtDfaXg3cEkjF0BXJwX2ac1Wp+DrEXCAT2Ala/gcEFgEAF6BmazgOeAuBh7mFujLQuCwDXiYA9/w1QTwMMu+4ANAJYVAGcj7ENhJAQ+z7QqfAT4cBN6BKReBcwd4mBOXr695EKgB0zZO3PtdlGRS2f+M+e6yDlsC9EoqS/K15Z4kFYwxwe+B7QQWPcIlaVDSQtTAJjDvER7baRMYyUBgNIkAGQhEdtoEKhkIPCQRKGUgENlpu4Y5SY+SejzBA0lDxpi334PIEzDGPEs68wSXpNMoeGyASerLxDUBMJ5KGyh6EFhLBW+S2HOA7zrBmyQ2gWoCcABseIE3SUwAx8T/F1XgCBhrtzfxuqV+RZckzarxvN5LupZ0ZYx5Sdr5p/kC40fFCUHhtX0AAAAASUVORK5CYII=', 87 | brush: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAB+gAAAfoBF4pEbwAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAHUSURBVFiF7dbPi01xGMfxc5oZk8IQm8m9GKMuhkgWdlIsZmEtu/FvGOyt/AkkTUhTQmYh2VKUUhKiMcbPMaaslPuymOfkdOfeaPqeyeJ+6nROz+f0vJ/nfH+dLOuqqwqEEVzHm7iuYddKwQ/hu6Wax86q4QcDBDdQQx23I3a1Svh+zBUg9JS8zRF/XxV8L76UOu9t8WvhvasCvhufAnATfS1+TxQFE6nhDXyI5HfQ3+L3xQooJuH2lPCRv3S+GnfD/4x9KeFDmC4tsYtYU/L7cSu8rziQEr7F4uYC3/Arnh9jbXQ+VVXnNbyK5I8wEPPgecQu4V48f8SelPBBvIjkT7Ch5DVKXwJmpdp6MRrLqNhemxhreWddDAfMoJEKflZnnYt31uNhxKaxIxX8aHT7E+PYFteZiDVxMiYfvMVQEngU8CASn2/jjbd8jdfYmhI+HInnMdDGHyzBX6K+XFZvh/iJuE/meb7Qxm/GfSHLssN5ns8ut4C2wtPo7lgHvxiCyaTgSF6P5HOWHqubcCEmIYxWUcBYuTuswnFM4Ed4TZxODg/glYBMWdyAil+sQvdxpCp47s/5XtazGPfhSsClAorlN4PLOJV0ff9DARtXFNjV/6Df96UwpUt/UBgAAAAASUVORK5CYII=', 88 | undo: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAB+gAAAfoBF4pEbwAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAHESURBVFiF7daxaxRREMfxWSPBSrAIKCpqQIIiWPg/WBgVEbUVLAQbU8Q/w1YEQTAgiFhExcJGggn4B1iIEEknCFoYYxHDxY/FTchx3mXv9vaSQn+wDOzsm/my896biagojOBu1fUDCWOYh51IfgKLUtudfBLLWrSdyW/gl7/1FR/wGrewf1gA02h0AGjXGu7j4DAgJvGjLeE+jOMyHmM136/g0jAgTmGp2x7AUTxL9zqmqiaaw5suvtJjiKks2TrOVwFYwNst/KN4WBLjdv6J7zjQN0QdwtOEeLBTAOOax7eBw+3+XR0WdK19FRVFsRQRLyJiJCIulgJExO586tTztOdqjtubMJH74ONOAexNgOV2X6c98A7zdTOkLcq/5FvS1naXb1WCTpvwfdrTdQFExJm0n3oBWEh7pUaAjab0qvRLHMv7ewVjg2bG8WzRaz1fx5jNms0MmLzAy4x1r5+FR/ATv3FzAIDplmbU36SE6wnQqAIxcDvOIHds6hEO9bCmnoGkJeBVm2PYqubYdS3LNIo9OJl/7IlhjGR5MmYMYSgtvxrbQCLiQkScjYiJiNjo758jYjGabXe2KIov/cT9r39bfwAudYMcsxLSggAAAABJRU5ErkJggg==', 89 | redo: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAB+gAAAfoBF4pEbwAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAHcSURBVFiF7da7ahVhFAXgfYwGG4uACDEJkRALtRGfwUabGBVsRNDKCwh5AN/AB9BCBSUIIipoE0REg72VRSQgtgEhXtAYPZ9F9oFjcpKZYSYK4mo2zL/2Xuu/zf4jagJX0Ve3Th0D8BQDf9MAvMW+v2kAPuDwZooN4iJm8AYL1mIJ58rW3FpSeDgirkTE2YjYVkDvi4jmzgOO43PO7ivuYBJjGFg1+0UcbVJ8Cu0sfg+jPTgdzONAk+IT+IllXN6ABy+xc53x53hWVXwIH7P4pQLuDfRvMD6LF1UN3Ezx6UqJTQCj+IFv2LPZelt6fJuIlav0sNVqvWtKaL2z0Os/cCTjo6bEu7TahSzM5f6PN2ygHLpO/44/odfrDLQyFi9XBeR1fFWG2NmCvQ2Kj2TNhdVjvVZgPuOhpgxExMGMr8sYeJLxWIMGTmacLWTmb3g5+/pYXWXswpfsK2ua2XpJ13LPHjRgYDpr3a+StLvrOk7VEL+QNT5hpGpyqXa8Qf757CltnK6a3ylS+CDpkTOC25nTrrOCnYKrn2R3cQb7sR392UFP5X4vJXcRJ2qJd5kYxnV8V4xl3FKylbeKKb8ZGYyIyVhp2eMRMZRD7yNiLiJmIuJxk238P/59/ALb21MuiNBdkwAAAABJRU5ErkJggg==', 90 | check: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAB+gAAAfoBF4pEbwAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAFMSURBVFiF7ZW/TsJQFIe/gw4+iJuWDsBr+ArGQOL/mMsiGHddpA4ii0ZHQxwdfAcvg+V9ehxaYgNFaSkdtL+tuaf3++7J6S2UKVPmv0eKgLgf7X0VvQFe/LrXiq+trxpeteZI0TvCw+5Mr1dWDQcmcEXpFibgWtMB+hE8UKHpN7zH6bqVzIBrTUfhKnoMVGiNa95TUm3uAmnguQukhecqkAWem0BW+FyBrZHZXFMuUX3zG7evP21Qte1D0Mm0K8ixX+8NFpVP/AwrARfALiLDEJAc15qDZeDzBSq8hxsioP0QNAtXuF8GPlfgs+YNVWgCASAKA8e2z2PwjsKA+CWTAQ6/DKEzMnuiPESiChhFNgS9jkpSDVxqAUi4z7/fy9z2VAIw0wmAQEWa41rveRk4LPgzilp8StgBBTnJA54626OzqmONUzi4TJk/nS/UUpvIjjxSkwAAAABJRU5ErkJggg==', 91 | eraser: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAB+gAAAfoBF4pEbwAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAGcSURBVFiF7ZbLLgNxFMZP7SppVy6phAg7vIDnUCxq0YVWIkqUuBPPw6J7JUTUA9CEBYKQ1EK6d/tZOE3+RnUunZlVTzKbM+f7ft/MnJmMSKt8KGAcuAQqwD7QEyZ8h79VAYbCgC8r8APIAr3AqfYegcEg4bUrfwOSRj8KFI07MRwa3BLiUGdegJGg4GMN5tqBI19DAEvGM085mI8BJdU8A32hwQ1dHDhXbaFZOEDOg75btdVm4QBXQMKlR0a1ZbfwvAo/gVWFuwoBpFVPvTemkXBbRe/ApPY6gQvt3wP9Nh5zwJceGa/wCcs5RyEs8KwvcKchgFxgcLsQFviCG3jSWLi0Q03CWMxrYMVYuDXHcDUrq3Dapc4MUat1V3A1qqq4w4N23oBvuIarSUENDoCoC12Kn88zwKYnuBr1AU9qVHQSApgy4Fue4ZYQt2p4BsQazM7qpgMsNg3/J0QJiNvA877BLSFu6oUIHG6ABoAHBZ0AXcCM8Z7v+sGJ2IUQkWMR8f4nYwVGIr+YbTbDdyIyKiJ7IvLqV4hWtcqsb8bJP3WiOIYMAAAAAElFTkSuQmCC', 92 | save: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAB+gAAAfoBF4pEbwAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAD7SURBVFiF7ZdNbsJADEadwjE4UdmgnACJImVTfs4LQShtbkHzunGkgIIZTwIUNd9mIsf+/DSZjGZEBqmABNgCO+BEN/0ACy/AqmPTNogPD8BeC1NgVLsY+WdqiVcuCC0ASJpGHQCyBsQyBKDVKBZAn+cKUAHrhwO4ILwAoT4ayzRcPQXAio8Nr28RmcRAiMhXaOKb8S4TkTKieam1PsVOeVd/awYeogFgALgJAEyBw+XWaygH3t0kxg5WOJrXOob6hwC49gevz99fA/8KoD7FJH03aXiaa+Cg46xvABFJdcyvZtD/sbxNnxZAfTGJ+e9vqQA23OHzDnp9/QK8BlZMPbVohgAAAABJRU5ErkJggg==' 93 | }; 94 | this.tools = [{ 95 | name: 'rectangle', 96 | icon: this.icons.rectangle, 97 | settings: { 98 | thickness: { 99 | type: 'range', 100 | min: 2, 101 | max: 30, 102 | default: 10 103 | }, 104 | color: { 105 | type: 'color', 106 | value: '#ff0000' 107 | } 108 | }, 109 | initialize: function() { 110 | this.imageCopy = document.createElement('canvas'); 111 | this.imageCopyCtx = this.imageCopy.getContext('2d'); 112 | }, 113 | selected: async function(canvas, ctx, helpers) { 114 | ctx.lineWidth = Math.max(1, this.settings.thickness.value * (canvas.width / parseInt(canvas.style.width))); 115 | ctx.lineJoin = "round"; 116 | ctx.lineCap = "round"; 117 | ctx.strokeStyle = this.settings.color.value; 118 | this.imageCopy.width = canvas.width; 119 | this.imageCopy.height = canvas.height; 120 | this.imageCopyCtx.drawImage(canvas, 0, 0); 121 | }, 122 | mouseDown: function(_, __, location) { 123 | this.startLoc = location; 124 | }, 125 | mouseMove: function(_, ctx, location) { 126 | ctx.drawImage(this.imageCopy, 0, 0); 127 | const [x, y, w, h] = [this.startLoc.x, this.startLoc.y, location.x - this.startLoc.x, location.y - this.startLoc.y]; 128 | ctx.strokeRect(x, y, w, h); 129 | }, 130 | mouseUp: function(canvas, ctx, location) { 131 | ctx.drawImage(this.imageCopy, 0, 0); 132 | const [x, y, w, h] = [this.startLoc.x, this.startLoc.y, location.x - this.startLoc.x, location.y - this.startLoc.y]; 133 | ctx.strokeRect(x, y, w, h); 134 | this.imageCopyCtx.drawImage(canvas, 0, 0); 135 | } 136 | },{ 137 | name: 'filled rectangle', 138 | icon: this.icons.filledRectangle, 139 | settings: { 140 | thickness: { 141 | type: 'range', 142 | min: 2, 143 | max: 30, 144 | default: 10 145 | }, 146 | color: { 147 | type: 'color', 148 | value: '#ff0000' 149 | } 150 | }, 151 | initialize: function() { 152 | this.imageCopy = document.createElement('canvas'); 153 | this.imageCopyCtx = this.imageCopy.getContext('2d'); 154 | }, 155 | selected: async function(canvas, ctx, helpers) { 156 | ctx.lineWidth = Math.max(1, this.settings.thickness.value * (canvas.width / parseInt(canvas.style.width))); 157 | ctx.lineJoin = "round"; 158 | ctx.lineCap = "round"; 159 | ctx.fillStyle = this.settings.color.value; 160 | this.imageCopy.width = canvas.width; 161 | this.imageCopy.height = canvas.height; 162 | this.imageCopyCtx.drawImage(canvas, 0, 0); 163 | }, 164 | mouseDown: function(_, __, location) { 165 | this.startLoc = location; 166 | }, 167 | mouseMove: function(_, ctx, location) { 168 | ctx.drawImage(this.imageCopy, 0, 0); 169 | const [x, y, w, h] = [this.startLoc.x, this.startLoc.y, location.x - this.startLoc.x, location.y - this.startLoc.y]; 170 | ctx.fillRect(x, y, w, h); 171 | }, 172 | mouseUp: function(canvas, ctx, location) { 173 | ctx.drawImage(this.imageCopy, 0, 0); 174 | const [x, y, w, h] = [this.startLoc.x, this.startLoc.y, location.x - this.startLoc.x, location.y - this.startLoc.y]; 175 | ctx.fillRect(x, y, w, h); 176 | this.imageCopyCtx.drawImage(canvas, 0, 0); 177 | } 178 | }, 179 | { 180 | name: 'draw', 181 | icon: this.icons.brush, 182 | settings: { 183 | size: { 184 | type: 'range', 185 | min: 2, 186 | max: 150, 187 | default: 10 188 | }, 189 | color: { 190 | type: 'color', 191 | value: '#ff0000' 192 | } 193 | }, 194 | initialize: () => {}, 195 | selected: function(canvas, ctx) { 196 | ctx.strokeStyle = this.settings.color.value; 197 | ctx.lineWidth = Math.max(1, this.settings.size.value * (canvas.width / parseInt(canvas.style.width))); 198 | ctx.lineJoin = "round"; 199 | ctx.lineCap = "round"; 200 | }, 201 | mouseDown: function(_, ctx, location) { 202 | ctx.beginPath(); 203 | ctx.moveTo(location.x, location.y); 204 | }, 205 | mouseMove: function(_, ctx, location) { 206 | ctx.lineTo(location.x, location.y); 207 | ctx.stroke(); 208 | }, 209 | mouseUp: function(_, ctx, location) { 210 | ctx.lineTo(location.x, location.y); 211 | ctx.stroke(); 212 | } 213 | }, 214 | { 215 | name: 'erase', 216 | icon: this.icons.eraser, 217 | settings: { 218 | size: { 219 | type: 'range', 220 | min: 2, 221 | max: 150, 222 | default: 20 223 | }, 224 | }, 225 | initialize: function(canvas) { 226 | this.imageCopy = document.createElement('canvas'); 227 | this.imageCopy.width = canvas.width; 228 | this.imageCopy.height = canvas.height; 229 | this.imageCopyCtx = this.imageCopy.getContext('2d'); 230 | this.imageCopyCtx.drawImage(canvas, 0, 0); 231 | this.scratch = document.createElement('canvas'); 232 | this.scratchCtx = this.scratch.getContext('2d'); 233 | this.crop = [0, 0, canvas.width, canvas.height]; 234 | }, 235 | selected: function(canvas) { 236 | this.scratch.width = canvas.width; 237 | this.scratch.height = canvas.height; 238 | this.scratchCtx.lineWidth = this.settings.size.value * (canvas.width / parseInt(canvas.style.width)); 239 | this.scratchCtx.lineJoin = "round"; 240 | this.scratchCtx.lineCap = "round"; 241 | this.scratchCtx.strokeStyle = 'white'; 242 | this.scratchCtx.clearRect(0, 0, this.scratch.width, this.scratch.height); 243 | }, 244 | mouseDown: function(_, ctx, location) { 245 | this.scratchCtx.beginPath(); 246 | this.scratchCtx.moveTo(location.x, location.y); 247 | this.scratchCtx.globalCompositeOperation = 'source-in'; 248 | this.scratchCtx.drawImage(this.imageCopy, ...this.crop, 0, 0, this.scratch.width, this.scratch.height); 249 | this.scratchCtx.globalCompositeOperation = 'source-over'; 250 | ctx.drawImage(this.scratch, 0, 0); 251 | }, 252 | mouseMove: function(_, ctx, location) { 253 | this.scratchCtx.lineTo(location.x, location.y); 254 | this.scratchCtx.stroke(); 255 | this.scratchCtx.globalCompositeOperation = 'source-in'; 256 | this.scratchCtx.drawImage(this.imageCopy, ...this.crop, 0, 0, this.scratch.width, this.scratch.height); 257 | this.scratchCtx.globalCompositeOperation = 'source-over'; 258 | ctx.drawImage(this.scratch, 0, 0); 259 | }, 260 | mouseUp: function(_, ctx, location) { 261 | this.scratchCtx.lineTo(location.x, location.y); 262 | this.scratchCtx.stroke(); 263 | const data = this.scratchCtx.getImageData(0, 0, this.scratch.width, this.scratch.height).data; 264 | for (var i = 3; i < data.length; i += 4) { 265 | if (data[i] < 255) 266 | data[i] = 0; 267 | } 268 | this.scratchCtx.putImageData(new ImageData(data, this.scratch.width), 0, 0); 269 | this.scratchCtx.globalCompositeOperation = 'source-in'; 270 | this.scratchCtx.drawImage(this.imageCopy, ...this.crop, 0, 0, this.scratch.width, this.scratch.height); 271 | this.scratchCtx.globalCompositeOperation = 'source-over'; 272 | ctx.drawImage(this.scratch, 0, 0); 273 | } 274 | }, 275 | { 276 | name: 'blur', 277 | icon: this.icons.blur, 278 | settings: { 279 | amount: { 280 | type: 'range', 281 | min: 1, 282 | max: 9, 283 | default: 3 284 | }, 285 | }, 286 | initialize: function() { 287 | this.imageCopy = document.createElement('canvas'); 288 | this.imageCopyCtx = this.imageCopy.getContext('2d'); 289 | this.blurred = document.createElement('canvas'); 290 | this.blurredCtx = this.blurred.getContext('2d'); 291 | }, 292 | selected: function(canvas, ctx, helpers) { 293 | ctx.lineWidth = Math.max(1, 2 * (canvas.width / parseInt(canvas.style.width))); 294 | ctx.lineJoin = "round"; 295 | ctx.lineCap = "round"; 296 | ctx.strokeStyle = 'white'; 297 | this.imageCopy.width = canvas.width; 298 | this.imageCopy.height = canvas.height; 299 | this.imageCopyCtx.drawImage(canvas, 0, 0); 300 | const blurredCanvas = helpers.blurImage(canvas, this.settings.amount.value); 301 | this.blurred.width = canvas.width; 302 | this.blurred.height = canvas.height; 303 | this.blurredCtx.putImageData(blurredCanvas, 0, 0, 0, 0, canvas.width, canvas.height); 304 | }, 305 | mouseDown: function(_, __, location) { 306 | this.startLoc = location; 307 | }, 308 | mouseMove: function(_, ctx, location) { 309 | ctx.drawImage(this.imageCopy, 0, 0); 310 | const [x, y, w, h] = [this.startLoc.x, this.startLoc.y, location.x - this.startLoc.x, location.y - this.startLoc.y]; 311 | ctx.drawImage(this.blurred, x, y, w, h, x, y, w, h); 312 | ctx.strokeRect(x, y, w, h); 313 | }, 314 | mouseUp: function(canvas, ctx, location) { 315 | ctx.drawImage(this.imageCopy, 0, 0); 316 | const [x, y, w, h] = [this.startLoc.x, this.startLoc.y, location.x - this.startLoc.x, location.y - this.startLoc.y]; 317 | ctx.drawImage(this.blurred, x, y, w, h, x, y, w, h); 318 | this.imageCopyCtx.drawImage(canvas, 0, 0); 319 | } 320 | }, 321 | { 322 | name: 'inverse blur', 323 | icon: this.icons.inverseBlur, 324 | settings: { 325 | amount: { 326 | type: 'range', 327 | min: 1, 328 | max: 9, 329 | default: 3 330 | }, 331 | }, 332 | initialize: function() { 333 | this.imageCopy = document.createElement('canvas'); 334 | this.imageCopyCtx = this.imageCopy.getContext('2d'); 335 | this.blurred = document.createElement('canvas'); 336 | this.blurredCtx = this.blurred.getContext('2d'); 337 | }, 338 | selected: async function(canvas, ctx, helpers) { 339 | ctx.lineWidth = Math.max(1, 4 * (canvas.width / parseInt(canvas.style.width))); 340 | ctx.lineJoin = "round"; 341 | ctx.lineCap = "round"; 342 | ctx.strokeStyle = 'white'; 343 | this.imageCopy.width = canvas.width; 344 | this.imageCopy.height = canvas.height; 345 | this.imageCopyCtx.drawImage(canvas, 0, 0); 346 | const blurredCanvas = await helpers.blurImage(canvas, this.settings.amount.value); 347 | this.blurred.width = canvas.width; 348 | this.blurred.height = canvas.height; 349 | this.blurredCtx.putImageData(blurredCanvas, 0, 0, 0, 0, canvas.width, canvas.height); 350 | }, 351 | mouseDown: function(_, __, location) { 352 | this.startLoc = location; 353 | }, 354 | mouseMove: function(_, ctx, location) { 355 | ctx.drawImage(this.blurred, 0, 0); 356 | const [x, y, w, h] = [this.startLoc.x, this.startLoc.y, location.x - this.startLoc.x, location.y - this.startLoc.y]; 357 | ctx.drawImage(this.imageCopy, x, y, w, h, x, y, w, h); 358 | ctx.strokeRect(x, y, w, h); 359 | }, 360 | mouseUp: function(canvas, ctx, location) { 361 | ctx.drawImage(this.blurred, 0, 0); 362 | const [x, y, w, h] = [this.startLoc.x, this.startLoc.y, location.x - this.startLoc.x, location.y - this.startLoc.y]; 363 | ctx.drawImage(this.imageCopy, x, y, w, h, x, y, w, h); 364 | this.blurredCtx.drawImage(canvas, 0, 0); 365 | } 366 | }, 367 | { 368 | name: 'crop', 369 | icon: this.icons.crop, 370 | settings: {}, 371 | initialize: function(_, __, tools) { 372 | this.imageCopy = document.createElement('canvas'); 373 | this.imageCopyCtx = this.imageCopy.getContext('2d'); 374 | this.greyed = document.createElement('canvas'); 375 | this.greyedCtx = this.greyed.getContext('2d'); 376 | this.eraser = tools.find(t => t.name === 'erase'); 377 | }, 378 | selected: async function(canvas, ctx) { 379 | ctx.lineWidth = Math.max(1, 4 * (canvas.width / parseInt(canvas.style.width))); 380 | ctx.lineJoin = "round"; 381 | ctx.lineCap = "round"; 382 | ctx.strokeStyle = 'white'; 383 | this.imageCopy.width = canvas.width; 384 | this.imageCopy.height = canvas.height; 385 | this.imageCopyCtx.drawImage(canvas, 0, 0); 386 | this.greyed.width = canvas.width; 387 | this.greyed.height = canvas.height; 388 | this.greyedCtx.drawImage(canvas, 0, 0); 389 | this.greyedCtx.globalAlpha = 0.6; 390 | this.greyedCtx.fillRect(0, 0, canvas.width, canvas.height); 391 | this.greyedCtx.globalAlpha = 1; 392 | }, 393 | mouseDown: function(_, __, location) { 394 | this.startLoc = location; 395 | }, 396 | mouseMove: function(_, ctx, location) { 397 | ctx.drawImage(this.greyed, 0, 0); 398 | const [x, y, w, h] = [this.startLoc.x, this.startLoc.y, location.x - this.startLoc.x, location.y - this.startLoc.y]; 399 | ctx.drawImage(this.imageCopy, x, y, w, h, x, y, w, h); 400 | ctx.strokeRect(x, y, w, h); 401 | }, 402 | mouseUp: function(canvas, ctx, location, helpers) { 403 | const [x, y, w, h] = [ 404 | this.startLoc.x >= location.x ? location.x : this.startLoc.x, 405 | this.startLoc.y >= location.y ? location.y : this.startLoc.y, 406 | Math.abs(location.x - this.startLoc.x), 407 | Math.abs(location.y - this.startLoc.y) 408 | ]; 409 | if (w < 2 || h < 2) 410 | return; 411 | canvas.width = w; 412 | canvas.height = h; 413 | ctx.drawImage(this.imageCopy, x, y, w, h, 0, 0, w, h); 414 | this.imageCopy.width = w; 415 | this.imageCopy.height = h; 416 | this.imageCopyCtx.drawImage(canvas, 0, 0); 417 | this.greyed.width = canvas.width; 418 | this.greyed.height = canvas.height; 419 | this.greyedCtx.drawImage(canvas, 0, 0); 420 | this.greyedCtx.globalAlpha = 0.6; 421 | this.greyedCtx.fillRect(0, 0, canvas.width, canvas.height); 422 | this.greyedCtx.globalAlpha = 1; 423 | this.eraser.crop = [x, y, w, h]; 424 | helpers.fitCanvas(canvas, canvas.parentNode); 425 | ctx.lineWidth = Math.max(1, 4 * (canvas.width / parseInt(canvas.style.width))); 426 | ctx.lineJoin = "round"; 427 | ctx.lineCap = "round"; 428 | ctx.strokeStyle = 'white'; 429 | } 430 | } 431 | ]; 432 | this.restoreToolDefaults(); 433 | this.mouseCurrentlyDown = false; 434 | this.toolHelpers = { 435 | getDPI: function() { 436 | const div = document.createElement('div'); 437 | div.style.width = '1in'; 438 | div.style.height = '0'; 439 | document.body.appendChild(div); 440 | const dpi = div.offsetWidth * window.devicePixelRatio; 441 | div.remove(); 442 | return dpi; 443 | }, 444 | blurImage: function(image, amount) { 445 | const { 446 | width, 447 | height 448 | } = image; 449 | const scaleFactor = this.getDPI(); 450 | const dpiFactor = scaleFactor / 96; 451 | const canvas = document.createElement('canvas'); 452 | canvas.width = width; 453 | canvas.height = height; 454 | const ctx = canvas.getContext('2d'); 455 | ctx.drawImage(image, 0, 0, width, height, 0, 0, width / dpiFactor, height / dpiFactor); 456 | ctx.filter = `blur(${amount}px)`; 457 | ctx.drawImage(image, 0, 0, width, height, 0, 0, width / dpiFactor, height / dpiFactor); 458 | ctx.filter = ''; 459 | ctx.drawImage(canvas, 0, 0, width / dpiFactor, height / dpiFactor, 0, 0, width, height); 460 | return ctx.getImageData(0, 0, width, height); 461 | }, 462 | fitCanvas: function(canvas, wrapper) { 463 | const { 464 | width, 465 | height 466 | } = wrapper.getBoundingClientRect(); 467 | const canvasAspectRatio = canvas.width / canvas.height; 468 | const wrapperAspectRatio = width / height; 469 | wrapperAspectRatio >= canvasAspectRatio ? 470 | (canvas.style.height = height + 'px') && (canvas.style.width = (height * canvasAspectRatio) + 'px') : 471 | (canvas.style.width = width + 'px') && (canvas.style.height = (width / canvasAspectRatio) + 'px'); 472 | } 473 | }; 474 | } 475 | 476 | // Start plugin 477 | onStart() { 478 | this.cancelPatch = BdApi.monkeyPatch(BdApi.findModule(m => m.displayName === 'UploadModal').prototype, 'render', { 479 | after: _ => { 480 | requestAnimationFrame(this.addButtonToDescription.bind(this)); 481 | } 482 | }); 483 | this.cancelPatchAttachment = BdApi.monkeyPatch(BdApi.findModule(m => m.displayName === 'UploadAttachmentModal').prototype, 'render', { 484 | after: _ => { 485 | requestAnimationFrame(this.addButtonToDescription.bind(this)); 486 | } 487 | }); 488 | PluginUtilities.addStyle('EditUploads-CSS', ` 489 | #EditUploadsEditButton { 490 | padding: 0; 491 | background: none; 492 | background-color: transparent; 493 | } 494 | 495 | #EditUploadsEditButton span { 496 | font-size: 127%; 497 | } 498 | 499 | #EditUploadsEditButton:hover span { 500 | text-decoration: underline; 501 | } 502 | 503 | #EditUploadsModal { 504 | z-index: 9999; 505 | width: 90%; 506 | height: 90%; 507 | position: fixed; 508 | display: block; 509 | top: 5%; 510 | left: 5%; 511 | background-color: rgb(40,42,47); 512 | box-shadow: 0 0 40px rgba(0,0,0,0.75); 513 | border-radius: 1em; 514 | color: white; 515 | overflow: hidden; 516 | } 517 | 518 | #EditUploadsToolbar { 519 | background-color: rgb(20,21,23); 520 | padding: 1em 0 0.5em; 521 | text-align: center; 522 | position: absolute; 523 | bottom: 0; 524 | width: 100%; 525 | } 526 | 527 | #EditUploadsToolbar .tool { 528 | margin-right: 5px; 529 | margin-left: 5px; 530 | cursor: pointer; 531 | padding: 5px 4px 3px; 532 | border-radius: 5px; 533 | } 534 | 535 | #EditUploadsToolbar .tool:hover { 536 | background-color: rgba(255,255,255,0.25); 537 | } 538 | 539 | #EditUploadsToolbar .toolIconWrapper { 540 | position: relative; 541 | } 542 | 543 | #EditUploadsToolbar .toolIconWrapper:hover::after { 544 | content: attr(aria-label); 545 | text-transform: capitalize; 546 | background: black; 547 | border: 2px solid #222; 548 | border-radius: 5px; 549 | padding: 0.25ex 1ch; 550 | position: absolute; 551 | bottom: calc(28px + 0.75ch); left: 0; 552 | transform: translateX(calc(21px - 50%)); 553 | box-shadow: 0 2px 5px rgba(0,0,0,0.35); 554 | z-index: 999999; 555 | white-space: nowrap; 556 | } 557 | 558 | #EditUploadsToolbar .selected .tool { 559 | background-color: rgba(255,255,255,0.25); 560 | } 561 | 562 | #EditUploadsToolbar .tool:active { 563 | background-color: rgba(0,0,0,0.75); 564 | } 565 | 566 | #EditUploadsToolbar .toolSettings { 567 | display: none; 568 | position: relative; 569 | top: -8px; 570 | text-transform: capitalize; 571 | padding-right: 0.5ch; 572 | border-right: 1px solid rgba(255,255,255,0.25); 573 | } 574 | 575 | #EditUploadsModal .toolSettings input { 576 | margin: 0 1ch; 577 | position: relative; 578 | top: 3.5px; 579 | padding: 0; 580 | border: none; 581 | } 582 | 583 | #EditUploadsModal .toolSettings input[type="color"] { 584 | top: 7px; 585 | background: none; 586 | } 587 | 588 | #EditUploadsModal .toolSettings input[type="color"]::-webkit-color-swatch-wrapper { 589 | padding: 0; 590 | } 591 | 592 | #EditUploadsModal .toolSettings input[type="color"]::-webkit-color-swatch { 593 | border: none; 594 | border-radius: 5px; 595 | } 596 | 597 | #EditUploadsModal .selected .toolSettings { 598 | display: inline; 599 | } 600 | 601 | #EditUploadsModal .dividerLeft, 602 | #EditUploadsModal .dividerRight { 603 | position: relative; 604 | } 605 | 606 | #EditUploadsModal .dividerLeft::before { 607 | content: ""; 608 | width: 1px; 609 | height: 100%; 610 | position: absolute; 611 | left: 0; 612 | top: -8px; 613 | background-color: rgba(255,255,255,0.25); 614 | } 615 | 616 | #EditUploadsModal .dividerRight::after { 617 | content: ""; 618 | width: 1px; 619 | height: 100%; 620 | position: absolute; 621 | right: 0; 622 | top: -8px; 623 | background-color: rgba(255,255,255,0.25); 624 | } 625 | 626 | #EditUploadsCanvasWrapper { 627 | position: absolute; 628 | height: calc(100% - 14px - 2.5em); 629 | width: 100%; 630 | left: 0; 631 | top: 0; 632 | text-align: center; 633 | } 634 | 635 | #EditUploadsCanvas { 636 | display: inline; 637 | position: relative; 638 | top: 50%; 639 | transform: translateY(-50%); 640 | } 641 | `); 642 | } 643 | 644 | // Stop plugin 645 | onStop() { 646 | this.cancelPatch(); 647 | this.cancelPatchAttachment(); 648 | PluginUtilities.removeStyle('EditUploads-CSS'); 649 | } 650 | 651 | clamp(num, min, max) { 652 | return Math.min(Math.max(num, min), max); 653 | } 654 | createIcon(icon, label) { 655 | const iconWrapper = document.createElement('span'); 656 | const img = document.createElement('img'); 657 | img.src = icon; 658 | img.className = 'tool'; 659 | img.width = 20; 660 | img.height = 20; 661 | iconWrapper.setAttribute('aria-label', label); 662 | iconWrapper.className = 'toolIconWrapper'; 663 | iconWrapper.appendChild(img); 664 | return iconWrapper; 665 | } 666 | createToolbarButton(icon, label) { 667 | const span = document.createElement('span'); 668 | const iconWrapper = this.createIcon(icon, label); 669 | span.className = 'toolWrapper'; 670 | span.appendChild(iconWrapper); 671 | return span; 672 | } 673 | createUndo(toolbar, canvas, ctx, that) { 674 | const eraser = that.tools.find(t => t.name === 'erase'); 675 | const span = document.createElement('span'); 676 | const undoButton = this.createToolbarButton(this.icons.undo, 'undo'); 677 | const canvasHistory = [ 678 | [ctx.getImageData(0, 0, canvas.width, canvas.height), [0, 0, canvas.width, canvas.height]] 679 | ]; 680 | const undoHistory = []; 681 | const undo = () => { 682 | if (!canvasHistory.length) 683 | return; 684 | const [last, lastCrop] = canvasHistory.pop(); 685 | if (last) { 686 | undoHistory.push([ctx.getImageData(0, 0, canvas.width, canvas.height), eraser.crop]); 687 | [canvas.width, canvas.height] = [last.width, last.height]; 688 | ctx.putImageData(last, 0, 0); 689 | that.fitCanvas(canvas, canvas.parentNode); 690 | eraser.crop = lastCrop; 691 | that.currentTool.selected(canvas, ctx, that.toolHelpers); 692 | } 693 | }; 694 | undoButton.addEventListener('click', undo); 695 | span.appendChild(undoButton); 696 | const redoButton = this.createToolbarButton(this.icons.redo, 'redo'); 697 | const redo = () => { 698 | if (!undoHistory.length) 699 | return; 700 | const [next, nextCrop] = undoHistory.pop(); 701 | if (next) { 702 | canvasHistory.push([ctx.getImageData(0, 0, canvas.width, canvas.height), eraser.crop]); 703 | [canvas.width, canvas.height] = [next.width, next.height]; 704 | ctx.putImageData(next, 0, 0); 705 | that.fitCanvas(canvas, canvas.parentNode); 706 | eraser.crop = nextCrop; 707 | that.currentTool.selected(canvas, ctx, that.toolHelpers); 708 | } 709 | }; 710 | redoButton.addEventListener('click', redo); 711 | span.appendChild(redoButton); 712 | span.style.marginRight = '5px'; 713 | span.classList.add('dividerRight'); 714 | toolbar.appendChild(span); 715 | const undoFunc = () => { 716 | undoHistory.length = 0; 717 | canvasHistory.push([ctx.getImageData(0, 0, canvas.width, canvas.height), eraser.crop]); 718 | if (canvasHistory.length > 20) { 719 | canvasHistory.shift(); 720 | } 721 | }; 722 | return [undoFunc, undo, redo]; 723 | } 724 | createSaveDefaultSettings() { 725 | const span = document.createElement('span'); 726 | span.style.marginLeft = '5px'; 727 | span.style.paddingLeft = '5px'; 728 | span.classList.add('dividerLeft'); 729 | const button = this.createToolbarButton(this.icons.save, 'save tool settings'); 730 | button.addEventListener('click', _ => { 731 | this.tools.forEach((tool) => { 732 | for (var key in tool.settings) { 733 | const setting = tool.settings[key]; 734 | if (setting.value) { 735 | this.setToolDefault(tool.name, key, setting.value); 736 | setting.default = setting.value; 737 | } 738 | } 739 | }); 740 | }); 741 | span.appendChild(button); 742 | return span; 743 | } 744 | restoreToolDefaults() { 745 | this.tools.forEach((tool) => { 746 | for (var key in tool.settings) { 747 | const setting = tool.settings[key]; 748 | setting.default = this.getToolDefault(tool.name, key) || setting.default; 749 | } 750 | }); 751 | } 752 | setToolDefault(tool, setting, value) { 753 | return BdApi.saveData('EditUploads', `${tool}_${setting}_default`, value); 754 | } 755 | getToolDefault(tool, setting) { 756 | return BdApi.loadData('EditUploads', `${tool}_${setting}_default`); 757 | } 758 | createToolSettings(tool, canvas, ctx) { 759 | const span = document.createElement('span'); 760 | span.className = 'toolSettings'; 761 | for (var key in tool.settings) { 762 | const setting = tool.settings[key]; 763 | span.appendChild(document.createTextNode(' ' + key)); 764 | const input = Object.assign(document.createElement('input'), setting); 765 | if (setting.default) { 766 | input.value = setting.default; 767 | setting.value = setting.default; 768 | } 769 | input.addEventListener('input', _ => { 770 | setting.value = input.value; 771 | tool.selected(canvas, ctx, this.toolHelpers); 772 | }); 773 | span.appendChild(input); 774 | } 775 | return span; 776 | } 777 | createTools(toolbar, canvas, ctx) { 778 | const [undoChangeFunction, undoFunc, redoFunc] = this.createUndo(toolbar, canvas, ctx, this); 779 | this.currentTool = null; 780 | this.tools.forEach(function(tool) { 781 | tool.initialize(canvas, ctx, this.tools); 782 | const item = this.createToolbarButton(tool.icon, tool.name); 783 | item.appendChild(this.createToolSettings(tool, canvas, ctx)); 784 | item.firstElementChild.firstElementChild.addEventListener('click', _ => { 785 | if (this.currentTool === tool) { 786 | this.currentTool = null; 787 | const selected = toolbar.querySelector('.toolWrapper.selected'); 788 | if (selected) 789 | selected.classList.remove('selected'); 790 | return; 791 | } 792 | const selected = toolbar.querySelector('.toolWrapper.selected'); 793 | if (selected) 794 | selected.classList.remove('selected'); 795 | tool.selected(canvas, ctx, this.toolHelpers); 796 | this.currentTool = tool; 797 | item.classList.add('selected'); 798 | }); 799 | toolbar.appendChild(item); 800 | }.bind(this)); 801 | canvas.addEventListener('mousedown', e => { 802 | this.mouseCurrentlyDown = true; 803 | if (!this.currentTool) 804 | return; 805 | undoChangeFunction(); 806 | const scaleFactor = canvas.width / parseInt(canvas.style.width); 807 | const { 808 | left: x, 809 | top: y 810 | } = canvas.getBoundingClientRect(); 811 | const canvasX = (e.clientX - x) * scaleFactor; 812 | const canvasY = (e.clientY - y) * scaleFactor; 813 | const boundedX = this.clamp(canvasX, 0, canvas.width); 814 | const boundedY = this.clamp(canvasY, 0, canvas.height); 815 | this.currentTool.mouseDown.bind(this.currentTool)(canvas, ctx, { 816 | x: boundedX, 817 | y: boundedY 818 | }, this.toolHelpers); 819 | }); 820 | const mousemoveHandler = e => { 821 | if (!this.currentTool) 822 | return; 823 | if (this.mouseCurrentlyDown) { 824 | const scaleFactor = canvas.width / parseInt(canvas.style.width); 825 | const { 826 | left: x, 827 | top: y 828 | } = canvas.getBoundingClientRect(); 829 | const canvasX = (e.clientX - x) * scaleFactor; 830 | const canvasY = (e.clientY - y) * scaleFactor; 831 | const boundedX = this.clamp(canvasX, 0, canvas.width); 832 | const boundedY = this.clamp(canvasY, 0, canvas.height); 833 | this.currentTool.mouseMove.bind(this.currentTool)(canvas, ctx, { 834 | x: boundedX, 835 | y: boundedY 836 | }, this.toolHelpers); 837 | } 838 | }; 839 | document.body.addEventListener('mousemove', mousemoveHandler); 840 | const mouseupHandler = e => { 841 | if (!this.mouseCurrentlyDown) 842 | return; 843 | this.mouseCurrentlyDown = false; 844 | if (!this.currentTool) 845 | return; 846 | const scaleFactor = canvas.width / parseInt(canvas.style.width); 847 | const { 848 | left: x, 849 | top: y 850 | } = canvas.getBoundingClientRect(); 851 | const canvasX = (e.clientX - x) * scaleFactor; 852 | const canvasY = (e.clientY - y) * scaleFactor; 853 | const boundedX = this.clamp(canvasX, 0, canvas.width); 854 | const boundedY = this.clamp(canvasY, 0, canvas.height); 855 | this.currentTool.mouseUp.bind(this.currentTool)(canvas, ctx, { 856 | x: boundedX, 857 | y: boundedY 858 | }, this.toolHelpers); 859 | }; 860 | document.body.addEventListener('mouseup', mouseupHandler); 861 | return [{ 862 | up: mouseupHandler, 863 | move: mousemoveHandler 864 | }, undoFunc, redoFunc]; 865 | } 866 | createModal(canvas, ctx, confirm) { 867 | const cleanup = () => { 868 | modal.remove(); 869 | document.removeEventListener('keydown', keyDownHandler); 870 | document.body.removeEventListener('mouseup', handlers.up); 871 | document.body.removeEventListener('mousemove', handlers.move); 872 | //@ts-ignore 873 | const textArea = document.getElementsByClassName(this.uploadModalClass)[0].getElementsByClassName(this.textAreaClass)[0]; 874 | if (textArea) { 875 | textArea.focus(); 876 | } 877 | }; 878 | const modal = document.createElement('div'); 879 | modal.id = 'EditUploadsModal'; 880 | const toolbar = document.createElement('div'); 881 | toolbar.id = 'EditUploadsToolbar'; 882 | const [handlers, undoFunc, redoFunc] = this.createTools(toolbar, canvas, ctx); 883 | 884 | function keyDownHandler(e) { 885 | if (e.keyCode === 13) 886 | e.preventDefault(), e.stopPropagation(); 887 | if (e.keyCode === 27) 888 | e.preventDefault(), e.stopPropagation(), cleanup(); 889 | if (e.keyCode === 90 /*z*/ && (e.ctrlKey || e.metaKey)) { 890 | undoFunc(); 891 | return; 892 | } 893 | if (e.keyCode === 89 /*y*/ && (e.ctrlKey || e.metaKey)) { 894 | redoFunc(); 895 | return; 896 | } 897 | if (e.keyCode === 83 /*s*/ && (e.ctrlKey || e.metaKey)) { 898 | confirm(); 899 | cleanup(); 900 | return; 901 | } 902 | } 903 | document.addEventListener('keydown', keyDownHandler); 904 | toolbar.appendChild(this.createSaveDefaultSettings()); 905 | const confirmButton = this.createToolbarButton(this.icons.check, 'done'); 906 | confirmButton.addEventListener('click', _ => { 907 | confirm(); 908 | cleanup(); 909 | }); 910 | toolbar.appendChild(confirmButton); 911 | modal.appendChild(toolbar); 912 | const canvasWrapper = document.createElement('div'); 913 | canvasWrapper.id = 'EditUploadsCanvasWrapper'; 914 | canvas.id = 'EditUploadsCanvas'; 915 | canvasWrapper.appendChild(canvas); 916 | modal.appendChild(canvasWrapper); 917 | const closeButton = document.createElement('span'); 918 | closeButton.innerText = 'X'; 919 | closeButton.style.position = 'absolute'; 920 | closeButton.style.top = '2ch'; 921 | closeButton.style.right = '3ex'; 922 | closeButton.style.cursor = 'pointer'; 923 | closeButton.style.zIndex = '100'; 924 | closeButton.addEventListener('click', cleanup); 925 | modal.appendChild(closeButton); 926 | return modal; 927 | } 928 | fitCanvas(canvas, wrapper) { 929 | const { 930 | width, 931 | height 932 | } = wrapper.getBoundingClientRect(); 933 | const canvasAspectRatio = canvas.width / canvas.height; 934 | const wrapperAspectRatio = width / height; 935 | wrapperAspectRatio >= canvasAspectRatio ? 936 | (canvas.style.height = height + 'px') && (canvas.style.width = (height * canvasAspectRatio) + 'px') : 937 | (canvas.style.width = width + 'px') && (canvas.style.height = (width / canvasAspectRatio) + 'px'); 938 | } 939 | editFile(file, modalWrapper) { 940 | return new Promise(resolve => { 941 | const reader = new FileReader(); 942 | const canvas = document.createElement('canvas'); 943 | const ctx = canvas.getContext('2d'); 944 | const image = new Image(); 945 | image.onload = _ => { 946 | canvas.width = image.width; 947 | canvas.height = image.height; 948 | ctx.drawImage(image, 0, 0); 949 | modalWrapper.parentNode.appendChild(this.createModal(canvas, ctx, _ => { 950 | canvas.toBlob(blob => { 951 | resolve(new File([blob], file.name, { 952 | type: file.type 953 | })); 954 | }); 955 | })); 956 | const canvasWrapper = document.getElementById('EditUploadsCanvasWrapper'); 957 | this.fitCanvas(canvas, canvasWrapper); 958 | }; 959 | reader.onload = _ => { 960 | image.src = reader.result; 961 | }; 962 | reader.readAsDataURL(file); 963 | }); 964 | } 965 | startEdit() { 966 | const uploadModal = document.getElementsByClassName(this.uploadModalClass)[0]; 967 | const modalWrapper = uploadModal.parentNode.parentNode.parentNode; 968 | const reactInstance = this.getReactInstance(uploadModal); 969 | const stateNode = reactInstance.return.stateNode; 970 | const icon = uploadModal.getElementsByClassName(this.iconClass)[0]; 971 | const newNodeFormat = typeof reactInstance.return.stateNode.props.upload.item.file != "undefined"; 972 | const file = (newNodeFormat ? reactInstance.return.stateNode.props.upload.item.file : reactInstance.return.stateNode.props.upload.file); 973 | this.editFile(file, modalWrapper) 974 | .then((newFile) => { 975 | stateNode.setState({ 976 | upload: Object.assign(newNodeFormat ? stateNode.props.upload.item : stateNode.props.upload, { 977 | file: newFile, 978 | size: newFile.size 979 | }), 980 | file: newFile 981 | }); 982 | if (this.getReactInstance(icon).return.stateNode != null) 983 | this.getReactInstance(icon).return.stateNode.forceUpdate(); 984 | }); 985 | } 986 | createButton() { 987 | const button = document.createElement('button'); 988 | button.id = 'EditUploadsEditButton'; 989 | button.className = 'button'; 990 | button.style.minWidth = '0px'; 991 | const span = document.createElement('span'); 992 | span.innerText = 'Edit'; 993 | span.className = `${this.colorWhiteClass} ${this.lookLinkClass}`; 994 | button.appendChild(span); 995 | button.addEventListener('click', this.startEdit.bind(this)); 996 | return button; 997 | } 998 | addButtonToDescription() { 999 | const reactInstace = this.getReactInstance(document.getElementsByClassName(this.uploadModalClass)[0]); 1000 | if (!(reactInstace && reactInstace.return.stateNode.props.upload && reactInstace.return.stateNode.props.upload.isImage)) 1001 | return; 1002 | if (document.getElementById('EditUploadsEditButton')) 1003 | return; 1004 | const desc = document.getElementsByClassName(this.descriptionClass)[0]; 1005 | desc.appendChild(this.createButton()); 1006 | } 1007 | getReactInstance(element) { 1008 | return BdApi.getInternalInstance(element); 1009 | } 1010 | } 1011 | }; 1012 | 1013 | return plugin(Plugin, Api); 1014 | })(global.ZeresPluginLibrary.buildPlugin(config)); 1015 | })(); 1016 | -------------------------------------------------------------------------------- /XposeRAW.theme.css: -------------------------------------------------------------------------------- 1 | /** 2 | * @name XposeRAW 3 | * @author PseudoResonance 4 | * @version 3.2.2 5 | * @description Custom theme. 6 | * @source https://github.com/PseudoResonance/BetterDiscord-Theme/blob/master/XposeRAW.theme.css 7 | */ 8 | 9 | :root, .theme-dark, .theme-light { 10 | --background-screen-cover:0, 0, 0; /* Color of screen over background */ 11 | --background-screen-opacity:0.45; /* Value from 0-1 - 0 being least opaque, 1 being most opaque */ 12 | --background-url:url("https://i.imgur.com/avzaqfC.jpg"); /* URL for background */ 13 | --message-spacing:1.0625rem; 14 | --header-height:1.375rem; 15 | } 16 | 17 | /* 18 | * Variables 19 | */ 20 | 21 | :root, .theme-dark, .theme-light { 22 | --xposeVersion:"3.2.2"; 23 | --blur-radius:10px; 24 | } 25 | 26 | /* 27 | * Theme Colors 28 | */ 29 | 30 | .theme-dark { 31 | --header-primary:#fff; 32 | --header-secondary:#b9bbbe; 33 | --text-normal:#dcddde; 34 | --text-muted:#72767d; 35 | --text-link:#00b0f4; 36 | --channels-default:#c9cccf; 37 | --interactive-normal:#d5d6d8; 38 | --interactive-hover:#dcddde; 39 | --interactive-active:#fff; 40 | --interactive-muted:#838a95; 41 | --background-primary:transparent; 42 | --background-secondary:transparent; 43 | --background-secondary-alt:transparent; 44 | --background-tertiary:transparent; 45 | --modal-footer-background:transparent; 46 | --home-background:transparent; 47 | --bg-overlay-chat:transparent; 48 | --bg-overlay-2:transparent; 49 | --primary-600:transparent; 50 | --background-accent:rgba(79, 84, 92, 0.32); 51 | --background-floating:rgba(24, 25, 28, 0.4); 52 | --background-mobile-primary:#36393f; 53 | --background-mobile-secondary:#2f3136; 54 | --background-modifier-hover:rgba(79, 84, 92, 0.16); 55 | --background-modifier-active:rgba(79, 84, 92, 0.24); 56 | --background-modifier-selected:rgba(79, 84, 92, 0.32); 57 | --background-modifier-accent:hsla(0, 0%, 100%, 0.06); 58 | --background-mentioned:transparent; 59 | --background-mentioned-hover:transparent; 60 | --background-message-hover:transparent; 61 | --background-help-warning:rgba(250, 166, 26, 0.1); 62 | --background-help-info:rgba(0, 176, 244, 0.1); 63 | --scrollbar-thin-thumb:transparent; 64 | --scrollbar-thin-track:transparent; 65 | --scrollbar-auto-thumb:transparent; 66 | --scrollbar-auto-track:transparent; 67 | --scrollbar-auto-scrollbar-color-thumb:transparent; 68 | --scrollbar-auto-scrollbar-color-track:transparent; 69 | --elevation-stroke:0 0 0 1px rgba(4, 4, 5, 0.15); 70 | --elevation-low:transparent; 71 | --elevation-medium:transparent; 72 | --elevation-high:transparent; 73 | --logo-primary:#fff; 74 | --focus-primary:#00b0f4; 75 | --radio-group-dot-foreground:#8ea1e1; 76 | --guild-header-text-shadow:0 1px 1px rgba(0, 0, 0, 0.4); 77 | --channeltextarea-background:transparent; 78 | --activity-card-background:var(--background-attachment); 79 | --textbox-markdown-syntax:#8e9297; 80 | /* Custom Variables */ 81 | --background-attachment:rgba(47, 49, 54, 0.2); 82 | --code-block-border:rgba(20, 20, 20, 0.8); 83 | --attachment-border:rgba(20, 20, 20, 0.2); 84 | --background-code-block:rgba(0, 0, 0, 0.3); 85 | --background-code-inline:rgba(0, 0, 0, 0.5); 86 | --background-scrollbar-preview:rgba(255,255,255,0.15); 87 | --background-scrollbar-hover:rgba(255,255,255,0.2); 88 | --background-scrollbar-active:rgba(255,255,255,0.25); 89 | --new-message-color:#f04747; 90 | --background-modifier-hover-brand:rgba(92, 111, 177, 0.16); 91 | --background-modifier-hover-danger:rgba(240, 71, 71, 0.16); 92 | --background-modifier-active-brand:rgba(92, 111, 177, 0.24); 93 | --background-modifier-active-danger:rgba(240, 71, 71, 0.24); 94 | --background-floating-hover:rgba(24, 25, 28, 0.5); 95 | --background-notice:rgba(78, 93, 148, 0.4); 96 | --background-notice-info:rgba(74, 143, 225, 0.4); 97 | --background-notice-warning:rgba(250, 168, 26, 0.4); 98 | --background-notice-error:rgba(237, 66, 69, 0.4); 99 | --background-notice-success:rgba(59, 165, 93, 0.4); 100 | --playback-bar:rgba(0, 0, 0, 0.3); 101 | } 102 | 103 | .theme-light { 104 | --header-primary:#060607; 105 | --header-secondary:#4f5660; 106 | --text-normal:#2e3338; 107 | --text-muted:#747f8d; 108 | --text-link:#0067e0; 109 | --channels-default:#3a3f45; 110 | --interactive-normal:#4f5660; 111 | --interactive-hover:#2e3338; 112 | --interactive-active:#060607; 113 | --interactive-muted:#c7ccd1; 114 | --background-primary:transparent; 115 | --background-secondary:transparent; 116 | --background-secondary-alt:transparent; 117 | --background-tertiary:transparent; 118 | --modal-footer-background:transparent; 119 | --home-background:transparent; 120 | --bg-overlay-chat:transparent; 121 | --bg-overlay-2:transparent; 122 | --primary-600:transparent; 123 | --background-accent:rgba(116, 127, 141, 0.24); 124 | --background-floating:rgba(255, 255, 255, 0.4); 125 | --background-mobile-primary:#f8f9f9; 126 | --background-mobile-secondary:#fff; 127 | --background-modifier-hover:rgba(116, 127, 141, 0.08); 128 | --background-modifier-active:rgba(116, 127, 141, 0.16); 129 | --background-modifier-selected:rgba(116, 127, 141, 0.24); 130 | --background-modifier-accent:rgba(6, 6, 7, 0.08); 131 | --background-mentioned:transparent; 132 | --background-mentioned-hover:transparent; 133 | --background-message-hover:transparent; 134 | --background-help-warning:rgba(250, 166, 26, 0.1); 135 | --background-help-info:rgba(0, 103, 224, 0.1); 136 | --scrollbar-thin-thumb:transparent; 137 | --scrollbar-thin-track:transparent; 138 | --scrollbar-auto-thumb:transparent; 139 | --scrollbar-auto-track:transparent; 140 | --scrollbar-auto-scrollbar-color-thumb:transparent; 141 | --scrollbar-auto-scrollbar-color-track:transparent; 142 | --elevation-stroke:0 0 0 1px rgba(6, 6, 7, 0.08); 143 | --elevation-low:transparent; 144 | --elevation-medium:transparent; 145 | --elevation-high:transparent; 146 | --logo-primary:#7289da; 147 | --focus-primary:#00b0f4; 148 | --radio-group-dot-foreground:#4e5d94; 149 | --guild-header-text-shadow:0 1px 1px hsla(0, 0%, 100%, 0.4); 150 | --channeltextarea-background:transparent; 151 | --activity-card-background:var(--background-attachment); 152 | --textbox-markdown-syntax:#6a7480; 153 | /* Custom Variables */ 154 | --background-attachment:rgba(240, 240, 240, 0.2); 155 | --code-block-border:rgba(240, 240, 240, 0.8); 156 | --attachment-border:rgba(240, 240, 240, 0.2); 157 | --background-code-block:rgba(255, 255, 255, 0.3); 158 | --background-code-inline:rgba(255, 255, 255, 0.5); 159 | --background-scrollbar-preview:rgba(0,0,0,0.15); 160 | --background-scrollbar-hover:rgba(0,0,0,0.2); 161 | --background-scrollbar-active:rgba(0,0,0,0.25); 162 | --new-message-color:#f04747; 163 | --background-modifier-hover-brand:rgba(92, 111, 177, 0.16); 164 | --background-modifier-hover-danger:rgba(240, 71, 71, 0.26); 165 | --background-floating-hover:rgba(255, 255, 255, 0.5); 166 | --background-notice:rgba(78, 93, 148, 0.4); 167 | --background-notice-error:rgba(237, 66, 69, 0.4); 168 | --playback-bar:rgba(0, 0, 0, 0.3); 169 | } 170 | 171 | /* 172 | * Discord Styling 173 | */ 174 | 175 | /* Chat */ 176 | 177 | /* Remove Spacer Between Messages and Text Input */ 178 | [class*="messagesWrapper_"] [class*="scrollerSpacer_"] { 179 | height:5px; 180 | } 181 | 182 | [class*="messagesWrapper_"] ~ form [class*="channelTextArea_"] { 183 | margin-bottom:10px; 184 | padding-top:10px; 185 | margin-top:0px; 186 | } 187 | 188 | /* Decrease Height of Special Channel Messages (ex: Announcements) */ 189 | [class*="messagesWrapper_"] ~ form [class*="content_"] { 190 | margin-bottom:-45px; 191 | } 192 | 193 | [class*="messagesWrapper_"] ~ form [class*="content_"] ~ [class*="buttonContainer_"] { 194 | margin-bottom:-35px; 195 | } 196 | 197 | /* Resize/Move Message Buttons */ 198 | [class*="messagesWrapper_"] [class*="buttonContainer_"] [class*="buttons_"] [class*="button_"] { 199 | height:16px; 200 | width:16px; 201 | min-height:16px; 202 | min-width:16px; 203 | color:rgba(185, 187, 190, 0.6); 204 | } 205 | 206 | [class*="messagesWrapper_"] [class*="buttonContainer_"] [class*="buttons_"] [class*="button_"]:hover, [class*="messagesWrapper_"] [class*="buttonContainer_"] [class*="buttons_"] [class*="button_"]:active, [class*="messagesWrapper_"] [class*="buttonContainer_"] [class*="buttons_"] [class*="button_"][class*="selected_"] { 207 | color:rgba(155, 157, 160, 0.6) !important; 208 | background-color:transparent !important; 209 | } 210 | 211 | [class*="messagesWrapper_"] [class*="buttonContainer_"] [class*="buttons_"] { 212 | top:-4px; 213 | padding-right:0.875rem; 214 | } 215 | 216 | [class*="messagesWrapper_"] [class*="groupStart_"]:not([class*="compact_"]) [class*="buttonContainer_"] { 217 | top:var(--header-height); 218 | } 219 | 220 | [class*="messagesWrapper_"] [class*="buttonContainer_"] [class*="buttons_"] [class*="button_"] { 221 | padding:0px; 222 | } 223 | 224 | [class*="messagesWrapper_"] [class*="buttonContainer_"] [class*="buttons_"] div { 225 | box-shadow:none !important; 226 | border:0px !important; 227 | } 228 | 229 | /* Rotate Settings Button */ 230 | [class*="messagesWrapper_"] [class*="buttonContainer_"] [class*="buttons_"] [class*="button_"]:last-child:not([class*="dangerous_"]) { 231 | transform:rotate(90deg); 232 | padding-right:0px; 233 | padding-left:0px; 234 | margin-right:0px; 235 | margin-left:0px; 236 | } 237 | 238 | /* Margin on Text to Accomodate Buttons */ 239 | [class*="messagesWrapper_"] [class*="message_"] [class*="markup_"]:not([contenteditable="true"])::before { 240 | content:''; 241 | width:6.75rem; 242 | height:16px; 243 | display:inline-flex; 244 | float:right; 245 | } 246 | 247 | /* Make Text Area Wider */ 248 | [class*="messagesWrapper_"] [class*="message_"] { 249 | padding-right:0.875rem !important; 250 | } 251 | 252 | /* Spacer Between Message Groups */ 253 | [class*="messagesWrapper_"] [class*="groupStart_"]::after { 254 | content:''; 255 | border-top:thin solid var(--background-modifier-accent); 256 | top:calc(0px - var(--message-spacing)); 257 | left:1rem; 258 | right:0.875rem; 259 | display:block; 260 | position:absolute; 261 | } 262 | 263 | [class*="messagesWrapper_"] [class*="groupStart_"]:not([class*="compact_"]) { 264 | margin-top:calc(var(--message-spacing) * 2); 265 | } 266 | 267 | [class*="messagesWrapper_"] [class*="divider_"] + [class*="groupStart_"]::after { 268 | display:none; 269 | } 270 | 271 | [class*="messagesWrapper_"] [class*="divider_"] + [class*="groupStart_"]:not([class*="compact_"]) { 272 | margin-top:calc(var(--message-spacing) * 0.5); 273 | } 274 | 275 | [class*="messagesWrapper_"] [class*="divider_"] + [class*="groupStart_"][class*="compact_"] { 276 | margin-top:0; 277 | } 278 | 279 | /* Spacing Between Messages Within a Group */ 280 | [class*="messagesWrapper_"] [class*="message_"] { 281 | padding-top:0px; 282 | padding-bottom:0px; 283 | } 284 | 285 | /* Message Divider */ 286 | [class*="messagesWrapper_"] [class*="divider_"] { 287 | width:100%; 288 | margin-left:0; 289 | border:none; 290 | height:calc(var(--message-spacing) * 1.5) !important; 291 | } 292 | 293 | [class*="messagesWrapper_"] [class*="divider_"] span { 294 | background-color:transparent; 295 | padding:0; 296 | display:flex; 297 | align-items:center; 298 | text-align:center; 299 | font-size:14px; 300 | line-height:22px; 301 | height:calc(var(--message-spacing) * 2) !important; 302 | left:1rem; 303 | right:0.875rem; 304 | position:absolute; 305 | } 306 | 307 | [class*="messagesWrapper_"] [class*="divider_"] span::before { 308 | margin-right:8px; 309 | } 310 | 311 | [class*="messagesWrapper_"] [class*="divider_"] span::after { 312 | margin-left:8px; 313 | } 314 | 315 | [class*="messagesWrapper_"] [class*="divider_"] span::before, [class*="messagesWrapper_"] [class*="divider_"] span::after { 316 | content:''; 317 | flex:1; 318 | } 319 | 320 | [class*="messagesWrapper_"] [class*="divider_"][class*="isUnread_"] span:nth-child(2) { 321 | display:none; 322 | } 323 | 324 | /* Spacing Correction */ 325 | .group-spacing-0 [class*="divider_"][class*="hasContent_"] { 326 | margin-top:4px; 327 | margin-bottom:4px; 328 | } 329 | 330 | .group-spacing-4 [class*="divider_"][class*="hasContent_"] { 331 | margin-top:8px; 332 | margin-bottom:8px; 333 | } 334 | 335 | .group-spacing-8 [class*="divider_"][class*="hasContent_"] { 336 | margin-top:12px; 337 | margin-bottom:12px; 338 | } 339 | 340 | .group-spacing-16 [class*="divider_"][class*="hasContent_"] { 341 | margin-top:20px; 342 | margin-bottom:20px; 343 | } 344 | 345 | .group-spacing-24 [class*="divider_"][class*="hasContent_"] { 346 | margin-top:28px; 347 | margin-bottom:28px; 348 | } 349 | 350 | /* New Message Color */ 351 | [class*="messagesWrapper_"] [class*="divider_"][class*="isUnread_"] span { 352 | color:var(--new-message-color); 353 | } 354 | 355 | [class*="messagesWrapper_"] [class*="divider_"][class*="isUnread_"] span::before, [class*="messagesWrapper_"] [class*="divider_"][class*="isUnread_"] span::after { 356 | border-bottom:thin solid var(--new-message-color); 357 | } 358 | 359 | [class*="messagesWrapper_"] [class*="divider_"][class*="isUnread_"] span svg { 360 | display:none; 361 | } 362 | 363 | /* Timestamp Divider Color */ 364 | [class*="messagesWrapper_"] [class*="divider_"]:not([class*="isUnread_"]) span::before, [class*="messagesWrapper_"] [class*="divider_"]:not([class*="isUnread_"]) span::after { 365 | border-bottom:thin solid var(--text-muted); 366 | } 367 | 368 | /* Mention Background */ 369 | [class*="mention"] { 370 | border-radius:0; 371 | transition:background-color 50ms ease-out,color 50ms ease-out; 372 | -webkit-transition:background-color 50ms ease-out,color 50ms ease-out; 373 | } 374 | 375 | [class*="mention"][class*="interactive"], [class*="mention"][class*="roleMention"] { 376 | background-color:rgba(114,137,218,.1); 377 | color:#7289da; 378 | } 379 | 380 | [class*="mention"][class*="interactive"]:hover, [class*="mention"][class*="roleMention"]:hover { 381 | background-color:rgba(114,137,218,.7); 382 | color:#fff; 383 | } 384 | 385 | [class*="messagesWrapper_"] [class*="message_"][class*="mentioned_"] [class*="mention"][class*="interactive"]:hover { 386 | color:#7289da; 387 | } 388 | 389 | [class*="iconMention_"]:not(#foo) { 390 | padding-left:20px; 391 | } 392 | 393 | /* Reactions */ 394 | [class*="reaction_"] { 395 | background:var(--background-modifier-accent); 396 | border-radius:.25rem; 397 | border:none; 398 | margin:0 .125rem .125rem 0; 399 | padding:0; 400 | -webkit-transition:background-color .1s ease; 401 | transition:background-color .1s ease; 402 | } 403 | 404 | [class*="reaction_"]:hover, [class*="reaction_"]:active { 405 | background-color:rgba(252, 245, 255, 0.05); 406 | } 407 | 408 | [class*="reaction_"][class*="reactionMe_"] { 409 | background-color:rgba(114,137,218,.3); 410 | } 411 | 412 | [class*="reaction_"] [class*="reactionInner_"] { 413 | padding:0 .375rem; 414 | } 415 | 416 | [class*="reaction_"] [class*="reactionCount_"]:not(#foo) { 417 | color:var(--text-muted); 418 | } 419 | 420 | [class*="reaction_"]:hover [class*="reactionCount_"]:not(#foo) { 421 | color:var(--interactive-hover); 422 | } 423 | 424 | [class*="reaction_"][class*="reactionMe_"] [class*="reactionCount_"]:not(#foo), [class*="reaction_"][class*="reactionMe_"]:hover [class*="reactionCount_"]:not(#foo) { 425 | color:#7289da; 426 | } 427 | 428 | /* Transparent New Messages / Jump to Bottom */ 429 | [class*="messagesWrapper_"] [class*="barBase_"], [class*="sidebar_"] [class*="unreadBar_"]:not([aria-hidden="true"]) { 430 | background-color:var(--background-floating) !important; 431 | left:1rem; 432 | right:1.375rem; 433 | padding-bottom:0px; 434 | backdrop-filter:blur(var(--blur-radius)); 435 | border-radius:4px !important; 436 | } 437 | 438 | [class*="messagesWrapper_"] [class*="barBase_"]:hover, [class*="messagesWrapper_"] [class*="barBase_"]:active { 439 | background-color:var(--background-floating-hover) !important; 440 | } 441 | 442 | [data-list-id="guildsnav"] [class*="unreadMentionsBar_"] { 443 | backdrop-filter:blur(var(--blur-radius)); 444 | } 445 | 446 | /* Transparent Notice */ 447 | 448 | [class*="notice_"]:not([class*="-message"]) { 449 | background-color:var(--background-notice) !important; 450 | backdrop-filter:blur(var(--blur-radius)); 451 | } 452 | 453 | [class*="notice-info"]:not([class*="-message"]) { 454 | background-color:var(--background-notice-info) !important; 455 | backdrop-filter:blur(var(--blur-radius)); 456 | } 457 | 458 | [class*="notice-warning"]:not([class*="-message"]) { 459 | background-color:var(--background-notice-warning) !important; 460 | backdrop-filter:blur(var(--blur-radius)); 461 | } 462 | 463 | [class*="notice-error"]:not([class*="-message"]) { 464 | background-color:var(--background-notice-error) !important; 465 | backdrop-filter:blur(var(--blur-radius)); 466 | } 467 | 468 | [class*="notice-success"]:not([class*="-message"]) { 469 | background-color:var(--background-notice-success) !important; 470 | backdrop-filter:blur(var(--blur-radius)); 471 | } 472 | 473 | [class*="notice_"] [class*="notice-close"], [class*="notice_"] [class*="notice-content"], [class*="notice_"] [class*="notice-button"] { 474 | background-color:transparent !important; 475 | backdrop-filter:none; 476 | } 477 | 478 | /* Scrollbars */ 479 | .scroller-2FKFPG::-webkit-scrollbar, .scrollerBase-289Jih::-webkit-scrollbar, .textContainer-C0szpm::-webkit-scrollbar, .scrollbarGhostHairline-1mSOM1::-webkit-scrollbar { 480 | width:8px; 481 | height:8px; 482 | } 483 | 484 | .scroller-2FKFPG::-webkit-scrollbar-track-piece, .scroller-2FKFPG::-webkit-scrollbar-track, .scrollerBase-289Jih::-webkit-scrollbar-track-piece, .scrollerBase-289Jih::-webkit-scrollbar-track, .textContainer-C0szpm::-webkit-scrollbar-track-piece, .textContainer-C0szpm::-webkit-scrollbar-track, .scrollbarGhostHairline-1mSOM1::-webkit-scrollbar-track { 485 | background:transparent !important; 486 | border:none !important; 487 | } 488 | 489 | .scroller-2FKFPG::-webkit-scrollbar-thumb, .scrollerBase-289Jih::-webkit-scrollbar-thumb, .textContainer-C0szpm::-webkit-scrollbar-thumb, .scrollbarGhostHairline-1mSOM1::-webkit-scrollbar-thumb { 490 | background:var(--scrollbar-auto-thumb)!important; 491 | border:none !important; 492 | border-radius:0!important; 493 | width:4px; 494 | border-left:6px solid var(--lvl3)!important; 495 | } 496 | 497 | .scroller-2FKFPG:hover::-webkit-scrollbar-thumb, .scrollerBase-289Jih:hover::-webkit-scrollbar-thumb, .textContainer-C0szpm:hover::-webkit-scrollbar-thumb, .scrollbarGhostHairline-1mSOM1:hover::-webkit-scrollbar-thumb { 498 | background:var(--background-scrollbar-preview)!important; 499 | border-radius:0!important; 500 | } 501 | 502 | .scroller-2FKFPG::-webkit-scrollbar-thumb:hover, .scrollerBase-289Jih::-webkit-scrollbar-thumb:hover, .textContainer-C0szpm::-webkit-scrollbar-thumb:hover, .scrollbarGhostHairline-1mSOM1::-webkit-scrollbar-thumb:hover { 503 | background:var(--background-scrollbar-hover) !important; 504 | } 505 | 506 | .scroller-2FKFPG::-webkit-scrollbar-thumb:active, .scrollerBase-289Jih::-webkit-scrollbar-thumb:active, .textContainer-C0szpm::-webkit-scrollbar-thumb:active, .scrollbarGhostHairline-1mSOM1::-webkit-scrollbar-thumb:active { 507 | background:var(--background-scrollbar-active) !important; 508 | } 509 | 510 | /* Code Blocks, Embeds, Attachments */ 511 | [class*="messagesWrapper_"] [class*="attachment_"]:not([class*="embedFull_"]), [class*="messagesWrapper_"] [class*="wrapper_"][class*="userSelectNone_"], [class*="messagesWrapper_"] [class*="giftCodeContainer_"], [class*="messagesWrapper_"] [class*="wrapperAudio_"], [class*="messagesWrapper_"] [class*="invite_"] { 512 | background:var(--background-attachment) !important; 513 | backdrop-filter:blur(var(--blur-radius)); 514 | border:1px solid var(--attachment-border) !important; 515 | } 516 | 517 | [class*="messagesWrapper_"] [class*="giftCodeContainer_"] [class*="tile_"], [class*="messagesWrapper_"] [class*="giftCodeContainer_"] [class*="tile_"] [class*="invalidPoop_"] { 518 | background-color:transparent !important; 519 | } 520 | 521 | [class*="messagesWrapper_"] [class*="giftCodeContainer_"] { 522 | width:auto; 523 | } 524 | 525 | [class*="embedFull_"] { 526 | background:var(--background-attachment) !important; 527 | backdrop-filter:blur(var(--blur-radius)); 528 | border-left:4px solid rgb(32, 34, 37); 529 | border-right:1px solid var(--attachment-border) !important; 530 | border-top:1px solid var(--attachment-border) !important; 531 | border-bottom:1px solid var(--attachment-border) !important; 532 | } 533 | 534 | [class*="chatContainer_"] code { 535 | border:1px solid var(--code-block-border) !important; 536 | background:var(--background-code-block) !important; 537 | backdrop-filter:blur(var(--blur-radius)); 538 | border-radius:0 !important; 539 | } 540 | 541 | code.inline { 542 | background:var(--background-code-inline) !important; 543 | border-radius:0 !important; 544 | } 545 | 546 | /* TODO Bugfix for Misaligned Blur in Pinned Messages */ 547 | .layerContainer-2v_Sit [class*="messagesPopoutWrap_"] code.inline{ 548 | backdrop-filter:none !important; 549 | } 550 | 551 | [class*="textContainer_"] > pre > code, [class*="modalTextContainer_"] > pre > code { 552 | overflow:visible !important; 553 | } 554 | 555 | [class*="modalTextContainer_"] { 556 | overflow:scroll visible !important; 557 | } 558 | 559 | [class*="messagesWrapper_"] [class*="wrapperAudio_"] [class*="audioControls_"], [class*="messagesWrapper_"] [class*="volumeButtonSlider_"] [class*="mediaBarInteraction_"], [class*="messagesWrapper_"] [class*="volumeButtonSlider_"] [class*="mediaBarInteractionDragging_"], [class*="messagesWrapper_"] [class*="videoControls_"] { 560 | background-color:var(--playback-bar); 561 | } 562 | 563 | /* Tooltips */ 564 | [class*="tooltipRight_"] { 565 | backdrop-filter:blur(var(--blur-radius)); 566 | background-color:var(--background-floating); 567 | border-radius:0; 568 | padding:0px 0px 0px 8px; 569 | margin-left:-8px; 570 | -webkit-clip-path:polygon(8px 4px, 9px 2px, 10px 1px, 12px 0px, calc(100% - 4px) 0%, calc(100% - 2px) 1px, calc(100% - 1px) 2px, 100% 4px, 100% calc(100% - 4px), calc(100% - 1px) calc(100% - 2px), calc(100% - 2px) calc(100% - 1px), calc(100% - 4px) 100%, 12px 100%, 10px calc(100% - 1px), 9px calc(100% - 2px), 8px calc(100% - 4px), 8px calc(50% + 5px), 3px 50%, 8px calc(50% - 5px)) !important; 571 | } 572 | 573 | [class*="tooltipBottom_"] { 574 | backdrop-filter:blur(var(--blur-radius)); 575 | background-color:var(--background-floating); 576 | border-radius:0; 577 | padding:8px 0px 0px 0px; 578 | margin-top:-8px; 579 | -webkit-clip-path:polygon(0 12px, 1px 10px, 2px 9px, 4px 8px, calc(50% - 5px) 8px, 50% 3px, calc(50% + 5px) 8px, calc(100% - 4px) 8px, calc(100% - 2px) 9px, calc(100% - 1px) 10px, 100% 12px, 100% calc(100% - 4px), calc(100% - 1px) calc(100% - 2px), calc(100% - 2px) calc(100% - 1px), calc(100% - 4px) 100%, 4px 100%, 2px calc(100% - 1px), 1px calc(100% - 2px), 0 calc(100% - 4px)) !important; 580 | } 581 | 582 | [class*="tooltipLeft_"] { 583 | backdrop-filter:blur(var(--blur-radius)); 584 | background-color:var(--background-floating); 585 | border-radius:0; 586 | padding:0px 0px 0px 8px; 587 | margin-left:-8px; 588 | -webkit-clip-path:polygon(0px 4px, 1px 2px, 2px 1px, 4px 0px, calc(100% - 12px) 0%, calc(100% - 10px) 1px, calc(100% - 9px) 2px, calc(100% - 8px) 4px, calc(100% - 8px) calc(50% - 5px), 100% 50%, calc(100% - 8px) calc(50% + 5px), calc(100% - 8px) calc(100% - 4px), calc(100% - 9px) calc(100% - 2px), calc(100% - 10px) calc(100% - 1px), calc(100% - 12px) 100%, 4px 100%, 2px calc(100% - 1px), 1px calc(100% - 2px), 0px calc(100% - 4px)) !important; 589 | } 590 | 591 | [class*="tooltipTop_"], .layerContainer-2v_Sit .toolbar-2bjZV7, #EditUploadsToolbar .toolIconWrapper:not(#foo):hover::after /* EditUploads Plugin Tooltips */ { 592 | backdrop-filter:blur(var(--blur-radius)); 593 | background-color:var(--background-floating); 594 | border-radius:0; 595 | padding:0px 0px 8px 0px; 596 | margin-bottom:-8px; 597 | -webkit-clip-path:polygon(0 4px, 1px 2px, 2px 1px, 4px 0px, calc(100% - 4px) 0px, calc(100% - 2px) 1px, calc(100% - 1px) 2px, 100% 4px, 100% calc(100% - 12px), calc(100% - 1px) calc(100% - 10px), calc(100% - 2px) calc(100% - 9px), calc(100% - 4px) calc(100% - 8px), calc(50% + 5px) calc(100% - 8px), 50% calc(100% - 3px), calc(50% - 5px) calc(100% - 8px), 4px calc(100% - 8px), 2px calc(100% - 9px), 1px calc(100% - 10px), 0 calc(100% - 12px)) !important; 598 | } 599 | 600 | [class*="tooltipPointer_"], .layerContainer-2v_Sit .toolbar-2bjZV7::before, [class*="mediaBarWrapper_"] [class*="bubble_"]::before { 601 | display:none !important; 602 | } 603 | 604 | /* Small Media Player Time Tooltip */ 605 | [class*="mediaBarWrapper_"] [class*="bubble_"] { 606 | line-height:12px; 607 | background-color:var(--background-floating); 608 | border-radius:0; 609 | padding:8px 12px; 610 | margin-bottom:-8px; 611 | -webkit-clip-path:polygon(0 4px, 1px 2px, 2px 1px, 4px 0px, calc(100% - 4px) 0px, calc(100% - 2px) 1px, calc(100% - 1px) 2px, 100% 4px, 100% calc(100% - 12px), calc(100% - 1px) calc(100% - 10px), calc(100% - 2px) calc(100% - 9px), calc(100% - 4px) calc(100% - 8px), calc(50% + 5px) calc(100% - 8px), 50% calc(100% - 3px), calc(50% - 5px) calc(100% - 8px), 4px calc(100% - 8px), 2px calc(100% - 9px), 1px calc(100% - 10px), 0 calc(100% - 12px)) !important; 612 | 613 | } 614 | 615 | /* Popouts */ 616 | 617 | [id*="popout_"]::before, [id*="popout_"] [class*="submenu_"]::before, .theme-dark:not([id*="popout_"]) > [role="menu"]::before, .theme-dark:not([id*="popout_"]) > [role="menu"] [class*="submenu_"]::before, [class*="profileCustomizationPreview_"]::before, [role*="dialog"] > div::before { 618 | content:''; 619 | position:absolute; 620 | top:0; 621 | bottom:0; 622 | left:0; 623 | right:0; 624 | z-index:-1; 625 | backdrop-filter:blur(var(--blur-radius)); 626 | } 627 | 628 | [id*="popout_"], [id*="popout_"] [class*="submenu_"], .theme-dark:not([id*="popout_"]) > [role="menu"], .theme-dark:not([id*="popout_"]) > [role="menu"] [class*="submenu_"], [class*="profileCustomizationPreview_"], [role*="dialog"] > div { 629 | box-shadow:none !important; 630 | background-color:var(--background-floating) !important; 631 | } 632 | 633 | [role="menu"] { 634 | box-shadow:none !important; 635 | background:transparent; 636 | } 637 | 638 | /* Transparent Message Preview */ 639 | .layerContainer-2lfOPe [class*="content_"] .message-2qRu38 { 640 | background-color:var(--background-primary) !important; 641 | border:none !important; 642 | box-shadow:none !important; 643 | } 644 | 645 | /* Fix Misaligned Blur on Livestream Watchers Popout */ 646 | .layerContainer-2lfOPe [class*="translate_"] > .popoutWrapper-3TSaaB { 647 | left:0; 648 | } 649 | 650 | .layerContainer-2lfOPe [class*="translate_"] > .container-2dqNWc { 651 | margin-left:0; 652 | } 653 | 654 | .layerContainer-2lfOPe [class*="translate_"] > [class*="streamPreviewWrapper_"] { 655 | padding-left:0; 656 | } 657 | 658 | /* Autocomplete */ 659 | [class*="autocomplete_"] { 660 | box-shadow:none !important; 661 | background-color:var(--background-floating) !important; 662 | backdrop-filter:blur(var(--blur-radius)); 663 | } 664 | 665 | [class*="autocomplete_"] [class*="categoryHeader_"] { 666 | background-color:transparent !important; 667 | } 668 | 669 | [class*="autocomplete_"] [class*="autocompleteRow_"] [class*="selected_"] { 670 | background-color:var(--background-modifier-hover) !important; 671 | } 672 | 673 | /* Emoji Information */ 674 | .popoutContainer-1MXdqN { 675 | box-shadow:none !important; 676 | background-color:var(--background-floating) !important; 677 | } 678 | 679 | .layerContainer-2lfOPe [role="menu"]::before { 680 | content:''; 681 | position:absolute; 682 | top:0; 683 | bottom:0; 684 | left:0; 685 | right:0; 686 | backdrop-filter:blur(var(--blur-radius)); 687 | box-shadow:none !important; 688 | } 689 | 690 | /* User Popouts */ 691 | [class*="userPopoutOverlayBackground_"], [class*="userProfileModalOverlayBackground_"], [class*="userPopoutInner_"] [class*="bodyInnerWrapper_"], [class*="tabBarContainer_"] { 692 | background:transparent !important; 693 | box-shadow:none !important; 694 | } 695 | 696 | [class*="userPopoutInner_"] [class*="headerPlaying_"]:not(#foo):not(#bar), [class*="userProfileModalOverlayBackground_"] [class*="topSectionPlaying_"] { 697 | background-color:rgba(114, 137, 218, 0.35) !important; 698 | } 699 | 700 | [class*="userPopoutInner_"] [class*="headerSpotify_"]:not(#foo):not(#bar), [class*="userProfileModalOverlayBackground_"] [class*="topSectionSpotify_"] { 701 | background-color:rgba(29, 185, 84, 0.4) !important; 702 | } 703 | 704 | [class*="userPopoutInner_"] [class*="headerXbox_"]:not(#foo):not(#bar), [class*="userProfileModalOverlayBackground_"] [class*="topSectionXbox_"] { 705 | background-color:rgba(16, 124, 16, 0.4) !important; 706 | } 707 | 708 | [class*="userPopoutInner_"] [class*="headerStreaming_"]:not(#foo):not(#bar), [class*="userProfileModalOverlayBackground_"] [class*="topSectionStreaming_"] { 709 | background-color:rgba(89, 54, 149, 0.45) !important; 710 | } 711 | 712 | [class*="userPopoutInner_"] [class*="aboutMeSection_"] { 713 | background:transparent !important; 714 | } 715 | 716 | .activityUserPopout-2yItg2, .headerFill-adLl4x { 717 | background-color:transparent !important; 718 | } 719 | 720 | [class*="accountProfilePopoutWrapper_"] { 721 | left:0 !important; 722 | } 723 | 724 | /* Profile Banner */ 725 | [class*="userPopoutInner_"] [class*="popoutBanner_"]:not(#foo):not(#bar), [role="dialog"] [class*="profileBanner_"] { 726 | opacity:0.7 !important; 727 | } 728 | 729 | /* Mask Out Profile Picture from Banner for Transparent Profile Pictures */ 730 | [class*="userPopoutInner_"] [class*="popoutBanner_"]:not(#foo):not(#bar) { 731 | mask:radial-gradient(circle 46px at 62px 56px,transparent 97%,#fff 100%); 732 | -webkit-mask:radial-gradient(circle 46px at 62px 56px,transparent 97%,#fff 100%); 733 | } 734 | 735 | [role="dialog"] [class*="profileBanner_"] { 736 | mask:radial-gradient(circle 68px at 82px 101px,transparent 97%,#fff 100%); 737 | -webkit-mask:radial-gradient(circle 68px at 82px 101px,transparent 97%,#fff 100%); 738 | } 739 | 740 | /* Premium Profile Banners */ 741 | [class*="userPopoutInner_"] [class*="popoutBannerPremium_"]:not(#foo):not(#bar), [class*="profileBannerPreview_"] [class*="banner_"] { 742 | mask:radial-gradient(circle 46px at 62px 116px,transparent 97%,#fff 100%); 743 | -webkit-mask:radial-gradient(circle 46px at 62px 116px,transparent 97%,#fff 100%); 744 | } 745 | 746 | [role="dialog"] [class*="profileBannerPremium_"] { 747 | mask:radial-gradient(circle 68px at 82px 207px,transparent 97%,#fff 100%); 748 | -webkit-mask:radial-gradient(circle 68px at 82px 207px,transparent 97%,#fff 100%); 749 | } 750 | 751 | /* Image Preview */ 752 | .layerContainer-2lfOPe [role="dialog"] [class*="downloadLink_"], .layerContainer-2lfOPe [role="dialog"] [class*="operations_"] { 753 | position:relative; 754 | top:0; 755 | } 756 | 757 | .layerContainer-2lfOPe [role="dialog"] [class*="imageWrapper_"] { 758 | margin-left:auto; 759 | margin-right:auto; 760 | min-width:0; 761 | } 762 | 763 | .layerContainer-2lfOPe [role="dialog"] [class*="gallery_"] { 764 | pointer-events:none; 765 | } 766 | 767 | .layerContainer-2lfOPe [role="dialog"] [class*="imageWrapper_"] ~ *, .layerContainer-2lfOPe [role="dialog"] [class*="imageWrapper_"] { 768 | pointer-events:auto; 769 | } 770 | 771 | .layerContainer-2lfOPe [role="dialog"]:not(#emoji-picker-tab-panel) [class*="gallery_"]:not(#foo) { 772 | background-color:transparent !important; 773 | } 774 | 775 | /* Search */ 776 | [role="listbox"] [class*="resultsGroup_"] [class*="option"], [role="listbox"] [class*="queryContainer_"] { 777 | background:transparent; 778 | } 779 | 780 | [role="listbox"] [class*="resultsGroup_"] [class*="option"]:hover { 781 | background:var(--background-modifier-hover); 782 | } 783 | 784 | [role="listbox"] [class*="resultsGroup_"] [class*="option"]:active { 785 | background:var(--background-modifier-active); 786 | } 787 | 788 | [role="listbox"] [class*="resultsGroup_"] [class*="option"]::before, [role="listbox"] [class*="resultsGroup_"] [role="option"]::after { 789 | display:none !important; 790 | } 791 | 792 | [id="search-results"] [class*="searchResultGroup_"] [class*="buttonsContainer_"] [class*="button_"], .card-3x20HF { 793 | background-color:var(--background-floating) !important; 794 | backdrop-filter:blur(var(--blur-radius)); 795 | } 796 | 797 | [class*="calendarPicker_"], .react-datepicker, .react-datepicker__header { 798 | background:transparent !important; 799 | } 800 | 801 | .react-datepicker__day--outside-month:not(#foo) { 802 | background:var(--background-accent) !important; 803 | } 804 | 805 | .react-datepicker__day:hover { 806 | background:var(--background-modifier-hover) !important; 807 | } 808 | 809 | .react-datepicker__day--outside-month:hover { 810 | background:var(--background-floating) !important; 811 | } 812 | 813 | .react-datepicker__day--disabled, .react-datepicker__day--disabled:hover { 814 | background:var(--background-floating-hover) !important; 815 | } 816 | 817 | .react-datepicker__navigation { 818 | border-color:transparent !important; 819 | } 820 | 821 | /* Settings/Inputs */ 822 | *:not([class*="copyInput_"]) > * > [class*="inputWrapper_"]:not([class*="copyInput_"]) *, [class*="copyInput_"], [class*="emojiInput_"], [class*="emojiRow_"]:hover [class*="emojiInput_"], [class*="emojiRow_"]:focus-within [class*="emojiInput_"], .container-CpszHS, [class*="peopleColumn_"] [class*="addInputWrapper_"] form, [class*="quickswitcher_"] input { 823 | backdrop-filter:blur(var(--blur-radius)); 824 | background-color:var(--background-attachment) !important; 825 | } 826 | 827 | *:not([class*="copyInput_"]) > * > [class*="inputWrapper_"]:not([class*="copyInput_"]) *:hover, [class*="copyInput_"]:hover, .container-CpszHS:hover, [class*="peopleColumn_"] [class*="addInputWrapper_"] form:hover, [class*="quickswitcher_"] input:hover { 828 | background-color:var(--background-modifier-hover) !important; 829 | } 830 | 831 | *:not([class*="copyInput_"]) > * > [class*="inputWrapper_"]:not([class*="copyInput_"]) *:active, [class*="copyInput_"]:active, .container-CpszHS:active, [class*="peopleColumn_"] [class*="addInputWrapper_"] form:active, [class*="quickswitcher_"] input:active { 832 | background-color:var(--background-modifier-active) !important; 833 | } 834 | 835 | [class*="select_"] > div > [class*="-control"], .bd-select { 836 | backdrop-filter:blur(var(--blur-radius)); 837 | background-color:var(--background-attachment); 838 | border-color:rgba(0, 0, 0, 0.6); 839 | } 840 | 841 | [class*="select_"] > div > [class*="-control"]:hover, .bd-select:hover { 842 | background-color:var(--background-modifier-hover); 843 | border-color:rgba(0, 0, 0, 0.6); 844 | } 845 | 846 | [class*="select_"] > div > [class*="-control"]:active, .bd-select:active, .bd-select.menu-open { 847 | background-color:var(--background-modifier-active); 848 | border-color:rgba(0, 0, 0, 0.6); 849 | } 850 | 851 | [class*="select_"] > div > [class*="-menu"], .bd-select .bd-select-options { 852 | border-top:none; 853 | border-color:rgba(0, 0, 0, 0.6); 854 | backdrop-filter:blur(var(--blur-radius)); 855 | box-shadow:none !important; 856 | background-color:var(--background-floating) !important; 857 | } 858 | 859 | [class*="select_"] > div:hover { 860 | background-color:var(--background-modifier-hover); 861 | } 862 | 863 | [class*="select_"] > div:active { 864 | background-color:var(--background-modifier-active); 865 | } 866 | 867 | [class*="radioBar_"] { 868 | backdrop-filter:blur(var(--blur-radius)); 869 | background-color:var(--background-attachment); 870 | } 871 | 872 | [tabindex="0"] [class*="radioBar_"] { 873 | background-color:var(--background-modifier-selected); 874 | } 875 | 876 | [class*="auditLog_"], .card-3Qj_Yx { 877 | backdrop-filter:blur(var(--blur-radius)); 878 | background-color:var(--background-attachment); 879 | } 880 | 881 | .card-FDVird::before { 882 | background-color:var(--background-modifier-hover) !important; 883 | border:none !important; 884 | } 885 | 886 | [class*="tiers_"] [class*="tier_"], [class*="connectionList_"] [class*="connection_"], [class*="guildSubscriptionSlots_"], [class*="codeRedemptionRedirect_"]:not(#foo) { 887 | backdrop-filter:blur(var(--blur-radius)); 888 | background-color:var(--background-attachment); 889 | } 890 | 891 | .layerContainer-2lfOPe [class*="selectGuild_"]:hover { 892 | background-color:var(--background-modifier-hover); 893 | } 894 | 895 | [class*="tiers_"] [class*="tierBody_"] { 896 | background-color:transparent; 897 | } 898 | 899 | [class*="tiers_"] [class*="tierHeader_"]:not([class*="tierHeaderUnlocked_"]) { 900 | background-color:var(--background-accent); 901 | } 902 | 903 | /* Other Pages */ 904 | [class*="applicationStore_"], [class*="guilds_"] ~ * [class*="sidebar_"] ~ [class*="container_"], [class*="guilds_"] ~ * [class*="sidebar_"] ~ [class*="pageWrapper_"], .container-3wLKDe, .themed-Hp1KC_, .container-2IKOsH, .container-1um7CU { 905 | background-color:transparent !important; 906 | } 907 | 908 | [class*="nowPlayingColumn_"] [class*="itemCard_"], [class*="peopleListItem_"] [class*="actions_"] [class*="actionButton_"], [class*="pageWrapper_"] [class*="guildListSection_"] [class*="guildList_"] [class*="loaded_"], [class*="pageWrapper_"] [class*="guildList_"] [class*="card_"], [class*="nowPlayingColumn_"] [class*="card_"] { 909 | backdrop-filter:blur(var(--blur-radius)); 910 | background-color:var(--activity-card-background); 911 | } 912 | 913 | [class*="nowPlayingColumn_"] [class*="itemCard_"] [class*="inset_"] { 914 | background-color:var(--background-attachment); 915 | } 916 | 917 | [class*="nowPlayingColumn_"] [class*="itemCard_"]:not(#foo):hover, [class*="peopleListItem_"] [class*="actions_"] [class*="actionButton_"]:hover, [class*="pageWrapper_"] [class*="guildListSection_"] [class*="guildList_"] [class*="loaded_"]:hover, [class*="pageWrapper_"] [class*="guildList_"] [class*="card_"]:hover { 918 | background-color:var(--background-modifier-hover); 919 | } 920 | 921 | [class*="nowPlayingColumn_"] [class*="itemCard_"]:not(#foo):active, [class*="nowPlayingColumn_"] [class*="itemCard_"][class*=" active_"], [class*="peopleListItem_"] [class*="actions_"] [class*="actionButton_"]:active, [class*="pageWrapper_"] [class*="guildListSection_"] [class*="guildList_"] [class*="loaded_"]:active, [class*="pageWrapper_"] [class*="guildList_"] [class*="card_"]:active { 922 | background-color:var(--background-modifier-active); 923 | } 924 | 925 | .layerContainer-2lfOPe [class*="perksModal_"] { 926 | background-color:transparent; 927 | backdrop-filter:blur(var(--blur-radius)); 928 | } 929 | 930 | .layerContainer-2lfOPe [class*="perksModal_"] [class*="perks_"] [class*="perk_"], .layerContainer-2lfOPe [class*="perksModal_"] [class*="tier_"] { 931 | backdrop-filter:blur(var(--blur-radius)); 932 | background-color:var(--background-attachment); 933 | } 934 | 935 | .layerContainer-2lfOPe [class*="perksModal_"] [class*="tier_"] [class*="tierBody_"] { 936 | background-color:transparent; 937 | } 938 | 939 | .layerContainer-2lfOPe [class*="perksModal_"] [class*="tier_"] [class*="tierHeader_"] { 940 | background-color:var(--background-accent); 941 | } 942 | 943 | /* Preset Colors */ 944 | .theme-dark [class*="lookFilled_"][class*="colorGrey_"] { 945 | background-color:rgba(79, 84, 95, 0.6); 946 | } 947 | 948 | .theme-light [class*="lookFilled_"][class*="colorGrey_"] { 949 | background-color:rgba(116, 127, 141, 0.6); 950 | } 951 | 952 | [class*="colorDefault_"][class*="focused_"] { 953 | background-color:var(--background-modifier-hover); 954 | } 955 | 956 | [class*="colorDefault_"][class*="colorBrand_"][class*="focused_"] { 957 | background-color:var(--background-modifier-hover-brand); 958 | } 959 | 960 | [class*="colorDefault_"][class*="colorDanger_"][class*="focused_"] { 961 | background-color:var(--background-modifier-hover-danger); 962 | } 963 | 964 | [class*="colorDefault_"][class*="focused_"]:active:not(#foo), [class*="colorDefault_"]:active:not(#foo) { 965 | background-color:var(--background-modifier-active); 966 | } 967 | 968 | [class*="colorDefault_"][class*="colorBrand_"][class*="focused_"]:active:not(#foo), [class*="colorDefault_"][class*="colorBrand_"]:active:not(#foo) { 969 | background-color:var(--background-modifier-active-brand); 970 | } 971 | 972 | [class*="colorDefault_"][class*="colorDanger_"][class*="focused_"]:active:not(#foo), [class*="colorDefault_"][class*="colorDanger_"]:active:not(#foo) { 973 | background-color:var(--background-modifier-active-danger); 974 | } 975 | 976 | /* Emoji Picker */ 977 | #emoji-picker-tab-panel [class*="categoryItemDefaultCategorySelected_"], [class*="categoryItemDefaultCategorySelected_"]:hover { 978 | background-color:var(--background-modifier-selected); 979 | } 980 | 981 | #emoji-picker-tab-panel [class*="diversitySelector_"] [class*="diversitySelectorOptions_"] { 982 | z-index:10; 983 | background-color:var(--background-floating); 984 | backdrop-filter:blur(var(--blur-radius)); 985 | } 986 | 987 | /* Emoji Picker Unicode Shortcut */ 988 | .layerContainer-2lfOPe [class*="drawerSizingWrapper_"] > [class*="contentWrapper_"] [class*="categoryList_"] { 989 | display:flex; 990 | flex-direction:column; 991 | flex-wrap:nowrap; 992 | align-items:stretch; 993 | } 994 | 995 | .layerContainer-2lfOPe [class*="drawerSizingWrapper_"] > [class*="contentWrapper_"] [class*="categoryList_"] [class*="scroller_"] { 996 | height:auto !important; 997 | flex-grow:1; 998 | align-self:flex-start; 999 | position:relative !important; 1000 | width:100%; 1001 | } 1002 | 1003 | #emoji-picker-tab-panel [class*="unicodeShortcut_"], .layerContainer-2lfOPe [class*="drawerSizingWrapper_"] > [class*="contentWrapper_"] [class*="standardStickerShortcut_"] { 1004 | flex-shrink:0; 1005 | flex-grow:0; 1006 | align-self:flex-end; 1007 | position:relative !important; 1008 | width:100%; 1009 | } 1010 | 1011 | #emoji-picker-tab-panel [class*="unicodeShortcut_"][class*="unicodeShortcutInvisible_"], .layerContainer-2lfOPe [class*="drawerSizingWrapper_"] > [class*="contentWrapper_"] [class*="standardStickerShortcut_"][class*="invisibleShortcut_"] { 1012 | display:none; 1013 | } 1014 | 1015 | #gif-picker-tab-panel [class*="header_"] { 1016 | box-shadow:none !important; 1017 | } 1018 | 1019 | /* Status Emoji Picker */ 1020 | .layerContainer-2lfOPe [class*="emojiButtonContainer_"] { 1021 | z-index:1000000 !important; 1022 | } 1023 | 1024 | 1025 | /* Upload Modal */ 1026 | [class*="uploadModal_"] { 1027 | position:fixed !important; 1028 | top:0px !important; 1029 | left:0px !important; 1030 | background:transparent !important; 1031 | box-shadow:none !important; 1032 | } 1033 | 1034 | [class*="uploadModal_"] [class*="file_"] [class*="icon_"] { 1035 | border:0 !important; 1036 | box-shadow:none !important; 1037 | margin:0 !important; 1038 | background-position:50% !important; 1039 | position:fixed !important; 1040 | width:80vw !important; 1041 | left:10vw !important; 1042 | top:70px !important; 1043 | background-size:contain !important; 1044 | background-color:transparent !important; 1045 | background-repeat:no-repeat !important; 1046 | background-image:none; 1047 | } 1048 | 1049 | [class*="uploadModal_"] [class*="file_"] [class*="description_"] { 1050 | display:none; 1051 | } 1052 | 1053 | [class*="uploadModal_"] [class*="comment_"]{ 1054 | position:fixed !important; 1055 | width:80vw !important; 1056 | left:10vw !important; 1057 | height:calc(100vh - 50px) !important; 1058 | top:0vh !important; 1059 | margin:0 !important; 1060 | padding:0 !important; 1061 | } 1062 | 1063 | [class*="uploadModal_"] [class*="comment_"] [class*="label_"] { 1064 | display:none; 1065 | } 1066 | 1067 | [class*="uploadModal_"] [class*="comment_"] [class*="inputWrapper_"] { 1068 | margin:0; 1069 | padding:0; 1070 | } 1071 | 1072 | [class*="uploadModal_"] [class*="comment_"] [class*="inputWrapper_"] input { 1073 | backdrop-filter:none !important; 1074 | background-color:transparent !important; 1075 | border:none !important; 1076 | position:fixed !important; 1077 | left:0 !important; 1078 | width:100vw !important; 1079 | height:20px !important; 1080 | padding:0 !important; 1081 | text-align:center !important; 1082 | color:var(--header-primary) !important; 1083 | font-size:18px !important; 1084 | font-weight:700 !important; 1085 | font-style:normal !important; 1086 | line-height:20px !important; 1087 | vertical-align:baseline !important; 1088 | } 1089 | 1090 | [class*="uploadModal_"] [class*="comment_"] [class*="inputWrapper_"]:nth-of-type(2) input { 1091 | top:16px !important; 1092 | } 1093 | 1094 | [class*="uploadModal_"] [class*="comment_"] [class*="inputWrapper_"]:nth-of-type(4) input { 1095 | top:44px !important; 1096 | } 1097 | 1098 | [class*="uploadModal_"] [class*="comment_"] [class*="inputWrapper_"] input:hover, [class*="uploadModal_"] [class*="comment_"] [class*="inputWrapper_"] input:active { 1099 | background-color:transparent !important; 1100 | } 1101 | 1102 | [class*="uploadModal_"] [class*="footer_"] { 1103 | position:fixed !important; 1104 | background:transparent !important; 1105 | width:80vw !important; 1106 | left:10vw !important; 1107 | height:calc(100vh - 10px) !important; 1108 | top:0vh !important; 1109 | margin:0 !important; 1110 | padding:0 !important; 1111 | margin-bottom:5px !important; 1112 | align-items:flex-end !important; 1113 | z-index:-1; 1114 | box-shadow:none !important; 1115 | } 1116 | 1117 | [class*="uploadModal_"] [class*="comment_"] [class*="checkboxWrapper_"] { 1118 | position:fixed !important; 1119 | background:transparent !important; 1120 | width:100vw !important; 1121 | left:10vw !important; 1122 | height:24px !important; 1123 | top:calc(100vh - 36px) !important; 1124 | margin:0 !important; 1125 | padding:0 !important; 1126 | margin-bottom:5px !important; 1127 | align-items:flex-end !important; 1128 | z-index:-2; 1129 | } 1130 | 1131 | [class*="uploadModal_"] [class*="comment_"] [class*="checkboxWrapper_"] [class*="label_"] { 1132 | display:block !important; 1133 | } 1134 | 1135 | /* Upload Modal Sizing */ 1136 | @media only screen and (max-height: 300px) { 1137 | [class*="uploadModal_"] [class*="file_"] [class*="icon_"] { 1138 | height:50vh !important; 1139 | } 1140 | 1141 | [class*="uploadModal_"] [class*="comment_"] [class*="channelTextArea_"], [class*="uploadModal_"] [class*="comment_"] [class*="channelTextArea_"] [class*="scrollableContainer_"] { 1142 | max-height:calc(100vh - (50vh + 22px + 20px)); 1143 | } 1144 | } 1145 | 1146 | @media only screen and (min-height: 301px) { 1147 | [class*="uploadModal_"] [class*="file_"] [class*="icon_"] { 1148 | height:50vh !important; 1149 | } 1150 | 1151 | [class*="uploadModal_"] [class*="comment_"] [class*="channelTextArea_"], [class*="uploadModal_"] [class*="comment_"] [class*="channelTextArea_"] [class*="scrollableContainer_"] { 1152 | max-height:calc(100vh - (50vh + 22px + 105px)); 1153 | } 1154 | } 1155 | 1156 | @media only screen and (min-height: 500px) { 1157 | [class*="uploadModal_"] [class*="file_"] [class*="icon_"] { 1158 | height:65vh !important; 1159 | } 1160 | 1161 | [class*="uploadModal_"] [class*="comment_"] [class*="channelTextArea_"], [class*="uploadModal_"] [class*="comment_"] [class*="channelTextArea_"] [class*="scrollableContainer_"] { 1162 | max-height:calc(100vh - (65vh + 22px + 105px)); 1163 | } 1164 | } 1165 | 1166 | @media only screen and (min-height: 700px) { 1167 | [class*="uploadModal_"] [class*="file_"] [class*="icon_"] { 1168 | height:75vh !important; 1169 | } 1170 | 1171 | [class*="uploadModal_"] [class*="comment_"] [class*="channelTextArea_"], [class*="uploadModal_"] [class*="comment_"] [class*="channelTextArea_"] [class*="scrollableContainer_"] { 1172 | max-height:calc(100vh - (75vh + 22px + 105px)); 1173 | } 1174 | } 1175 | 1176 | /* Shrink Guilds Inside Expanded Folder */ 1177 | [data-list-id="guildsnav"] [role="group"][id|="folder-items"] [class*="blobContainer_"] > [class*="wrapper_"], [data-list-id="guildsnav"] [role="group"][id|="folder-items"] [class*="blobContainer_"] > [class*="wrapper_"] > svg, [data-list-id="guildsnav"] [role="group"][id|="folder-items"] [class*="pill_"] { 1178 | width:38px; 1179 | height:38px; 1180 | } 1181 | 1182 | [data-list-id="guildsnav"] [role="group"][id|="folder-items"] { 1183 | height:auto !important; 1184 | } 1185 | 1186 | [data-list-id="guildsnav"] [class|="expandedFolderBackground"] { 1187 | bottom:auto; 1188 | top:auto; 1189 | height:calc(100% + 5px); 1190 | clip-path:inset(0px 0px 0px 0px); 1191 | transition:clip-path 250ms ease, background-color 250ms ease; 1192 | } 1193 | 1194 | [data-list-id="guildsnav"] [class|="expandedFolderBackground"][class*="collapsed_"] { 1195 | background-color:transparent !important; 1196 | clip-path:inset(0px 0px 100% 0px); 1197 | } 1198 | 1199 | @keyframes guildFadeIn { 1200 | 0% { 1201 | opacity:0; 1202 | clip-path:inset(0px 0px 100% 0px); 1203 | } 1204 | 100% { 1205 | opacity:1; 1206 | clip-path:inset(0px 0px 0px 0px); 1207 | } 1208 | } 1209 | 1210 | [data-list-id="guildsnav"] [id|="folder-items"] { 1211 | opacity:1; 1212 | clip-path:inset(0px 0px 0px 0px); 1213 | transition:clip-path 250ms ease, opacity 250ms ease; 1214 | animation:guildFadeIn 250ms ease; 1215 | } 1216 | 1217 | [data-list-id="guildsnav"] [class|="expandedFolderBackground"][class*="collapsed_"] ~ [id|="folder-items"] { 1218 | opacity:0; 1219 | clip-path:inset(0px 0px 100% 0px); 1220 | } 1221 | 1222 | /* Transparent Guilds with No Image */ 1223 | [data-list-id="guildsnav"] [class*="listItem_"] [class*="childWrapper_"], [data-list-id="guildsnav"] [class*="listItem_"] [class*="circleIconButton_"] { 1224 | background-color:rgba(54,57,63,0.85); 1225 | } 1226 | 1227 | [data-list-id="guildsnav"] [class*="listItem_"] [class*="circleIconButton_"][class*="selected_"] { 1228 | background-color:rgba(67,181,129,0.85); 1229 | } 1230 | 1231 | [data-list-id="guildsnav"] [class*="listItem_"] [data-list-item-id*="_home"][class*="selected_"] > div { 1232 | background-color:rgba(114,137,218,0.85); 1233 | } 1234 | 1235 | [class|="expandedFolderBackground"][class*="collapsed_"] ~ [class|="listItem"] .wrapper-28eC3z::before { 1236 | content:''; 1237 | position:absolute; 1238 | top:0; 1239 | bottom:0; 1240 | left:0; 1241 | right:0; 1242 | backdrop-filter:blur(var(--blur-radius)); 1243 | border-radius:16px; 1244 | } 1245 | 1246 | [class|="expandedFolderBackground"] { 1247 | backdrop-filter:blur(var(--blur-radius)); 1248 | } 1249 | 1250 | /* Fix Server List Outlined when Clicking */ 1251 | [data-list-id="guildsnav"] { 1252 | outline:none !important; 1253 | } 1254 | 1255 | /* Background Image */ 1256 | #app-mount::before { 1257 | content:''; 1258 | width:100%; 1259 | height:100%; 1260 | left:0; 1261 | top:0; 1262 | position:absolute; 1263 | background-image:linear-gradient(to right,rgba(var(--background-screen-cover), var(--background-screen-opacity)) 0%,rgba(var(--background-screen-cover), var(--background-screen-opacity)) 100%),var(--background-url); /* Please use this to change background image */ 1264 | background-size:cover; 1265 | z-index:-5; 1266 | } 1267 | 1268 | /* 1269 | * Plugin Specific Styling 1270 | */ 1271 | 1272 | /* BetterDiscord Plugin Settings */ 1273 | .bd-addon-settings-wrap .plugin-inputs { 1274 | transition:all 300ms cubic-bezier(0.47, 0, 0.745, 0.715) !important; 1275 | transform:scaleY(1) !important; 1276 | overflow:inherit !important; 1277 | } 1278 | 1279 | .bd-addon-settings-wrap .plugin-inputs.collapsed { 1280 | transform:scaleY(0) !important; 1281 | } 1282 | 1283 | .bd-addon-settings-wrap .plugin-input-group > [class*="title"] { 1284 | cursor:pointer; 1285 | } 1286 | 1287 | .bd-addon-card { 1288 | backdrop-filter:blur(var(--blur-radius)); 1289 | box-shadow:none !important; 1290 | background-color:var(--background-floating) !important; 1291 | } 1292 | 1293 | /* BetterDiscord/ZeresPluginLibrary Toasts */ 1294 | .bd-toasts .bd-toast:not([class*="toast_"]), #app-mount .toasts .toast:not([class*="toast_"]) { 1295 | background-color:var(--background-floating) !important; 1296 | } 1297 | 1298 | #app-mount .toasts .toast { 1299 | box-shadow:none !important; 1300 | } 1301 | 1302 | /* EditUploads Plugin */ 1303 | #EditUploadsEditButton { 1304 | top:calc(100vh - 1em - 25px) !important; 1305 | position:fixed !important; 1306 | z-index:10000 !important; 1307 | } 1308 | 1309 | #EditUploadsModal { 1310 | border-radius:0; 1311 | box-shadow:none !important; 1312 | background:none !important; 1313 | border:none !important; 1314 | } 1315 | 1316 | #EditUploadsCanvasWrapper, #EditUploadsToolbar { 1317 | box-shadow:none !important; 1318 | background:none !important; 1319 | border:none !important; 1320 | } 1321 | 1322 | #EditUploadsModal::before { 1323 | content:''; 1324 | position:fixed; 1325 | left:0; 1326 | top:0; 1327 | right:0; 1328 | bottom:0; 1329 | backdrop-filter:blur(var(--blur-radius)); 1330 | } 1331 | 1332 | #EditUploadsToolbar .toolIconWrapper:not(#foo):hover::after { 1333 | backdrop-filter:blur(var(--blur-radius)); 1334 | box-shadow:none !important; 1335 | background-color:var(--background-floating) !important; 1336 | border:none; 1337 | margin-bottom:-2px; 1338 | padding:8px 12px 16px 12px; 1339 | } 1340 | 1341 | /* ImageUtilities Plugin */ 1342 | [class*="lensBackdrop_"] { 1343 | backdrop-filter:blur(var(--blur-radius)); 1344 | } 1345 | 1346 | /* CharCounter Plugin */ 1347 | [class*="messagesWrapper_"] ~ form [class*="charCounter_"] { 1348 | bottom:-0.5em; 1349 | right:10px; 1350 | } 1351 | 1352 | [class*="uploadModal_"] [class*="comment_"] [class*="charCounterAdded_"] { 1353 | position:static !important; 1354 | } 1355 | 1356 | [class*="uploadModal_"] [class*="comment_"] [class*="charCounterAdded_"] [class*="charCounter_"] { 1357 | bottom:0; 1358 | right:-1em; 1359 | } 1360 | 1361 | /* GuildProfile Plugin */ 1362 | .guild-profile [class*="body_"] { 1363 | background-color:var(--background-floating) !important; 1364 | } 1365 | 1366 | /* Display Version Info */ 1367 | [class*="sidebar_"] [role="tablist"] [class*="info_"]::after { 1368 | content:"Xpose Theme Version: v" var(--xposeVersion); 1369 | color:var(--text-muted); 1370 | font-size:12px; 1371 | line-height:16px; 1372 | } 1373 | 1374 | .bd-addon-list [id*="XposeLoader"] .bd-addon-header .bd-title .bd-meta::after { 1375 | content:"Theme Version: v" var(--xposeVersion); 1376 | padding-left:1em; 1377 | } 1378 | --------------------------------------------------------------------------------