diff --git a/.config/BetterDiscord/plugins/BetterFormattingRedux.config.json b/.config/BetterDiscord/plugins/BetterFormattingRedux.config.json
new file mode 100755
index 0000000..0b55ec2
--- /dev/null
+++ b/.config/BetterDiscord/plugins/BetterFormattingRedux.config.json
@@ -0,0 +1,74 @@
+{
+ "currentVersionInfo": {
+ "version": "2.3.11",
+ "hasShownChangelog": true
+ },
+ "settings": {
+ "toolbar": {
+ "bold": true,
+ "italic": true,
+ "underline": true,
+ "strikethrough": true,
+ "spoiler": true,
+ "code": true,
+ "codeblock": true,
+ "superscript": true,
+ "smallcaps": true,
+ "fullwidth": true,
+ "upsidedown": true,
+ "varied": true,
+ "leet": false,
+ "thicc": false
+ },
+ "formats": {
+ "superscript": true,
+ "smallcaps": true,
+ "fullwidth": true,
+ "upsidedown": true,
+ "varied": true,
+ "leet": false,
+ "thicc": false
+ },
+ "wrappers": {
+ "superscript": "^^",
+ "smallcaps": "%%",
+ "fullwidth": "##",
+ "upsidedown": "&&",
+ "varied": "==",
+ "leet": "++",
+ "thicc": "$$"
+ },
+ "formatting": {
+ "fullWidthMap": true,
+ "reorderUpsidedown": true,
+ "fullwidth": true
+ },
+ "plugin": {
+ "hoverOpen": true,
+ "chainFormats": true,
+ "closeOnSend": true
+ },
+ "style": {
+ "icons": true,
+ "rightSide": true,
+ "toolbarOpacity": 1,
+ "fontSize": 85
+ }
+ },
+ "buttonOrder": [
+ "bold",
+ "italic",
+ "underline",
+ "strikethrough",
+ "spoiler",
+ "codeblock",
+ "code",
+ "superscript",
+ "smallcaps",
+ "fullwidth",
+ "upsidedown",
+ "varied",
+ "leet",
+ "thicc"
+ ]
+}
\ No newline at end of file
diff --git a/.config/BetterDiscord/plugins/BlurNSFW.config.json b/.config/BetterDiscord/plugins/BlurNSFW.config.json
new file mode 100755
index 0000000..a9d7282
--- /dev/null
+++ b/.config/BetterDiscord/plugins/BlurNSFW.config.json
@@ -0,0 +1,6 @@
+{
+ "currentVersionInfo": {
+ "version": "0.2.5",
+ "hasShownChangelog": true
+ }
+}
\ No newline at end of file
diff --git a/.config/BetterDiscord/plugins/BlurNSFW.plugin.js b/.config/BetterDiscord/plugins/BlurNSFW.plugin.js
new file mode 100755
index 0000000..3ce00aa
--- /dev/null
+++ b/.config/BetterDiscord/plugins/BlurNSFW.plugin.js
@@ -0,0 +1,144 @@
+/**
+ * @name BlurNSFW
+ * @version 0.2.5
+ * @authorLink https://twitter.com/IAmZerebos
+ * @donate https://paypal.me/ZackRauen
+ * @patreon https://patreon.com/Zerebos
+ * @website https://github.com/rauenzi/BetterDiscordAddons/tree/master/Plugins/BlurNSFW
+ * @source https://raw.githubusercontent.com/rauenzi/BetterDiscordAddons/master/Plugins/BlurNSFW/BlurNSFW.plugin.js
+ * @updateUrl https://raw.githubusercontent.com/rauenzi/BetterDiscordAddons/master/Plugins/BlurNSFW/BlurNSFW.plugin.js
+ */
+/*@cc_on
+@if (@_jscript)
+
+ // Offer to self-install for clueless users that try to run this directly.
+ var shell = WScript.CreateObject("WScript.Shell");
+ var fs = new ActiveXObject("Scripting.FileSystemObject");
+ var pathPlugins = shell.ExpandEnvironmentStrings("%APPDATA%\\BetterDiscord\\plugins");
+ var pathSelf = WScript.ScriptFullName;
+ // Put the user at ease by addressing them in the first person
+ 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);
+ if (fs.GetParentFolderName(pathSelf) === fs.GetAbsolutePathName(pathPlugins)) {
+ shell.Popup("I'm in the correct folder already.", 0, "I'm already installed", 0x40);
+ } else if (!fs.FolderExists(pathPlugins)) {
+ shell.Popup("I can't find the BetterDiscord plugins folder.\nAre you sure it's even installed?", 0, "Can't install myself", 0x10);
+ } else if (shell.Popup("Should I copy myself to BetterDiscord's plugins folder for you?", 0, "Do you need some help?", 0x34) === 6) {
+ fs.CopyFile(pathSelf, fs.BuildPath(pathPlugins, fs.GetFileName(pathSelf)), true);
+ // Show the user where to put plugins in the future
+ shell.Exec("explorer " + pathPlugins);
+ shell.Popup("I'm installed!", 0, "Successfully installed", 0x40);
+ }
+ WScript.Quit();
+
+@else@*/
+
+module.exports = (() => {
+ const config = {info:{name:"BlurNSFW",authors:[{name:"Zerebos",discord_id:"249746236008169473",github_username:"rauenzi",twitter_username:"ZackRauen"}],version:"0.2.5",description:"Blurs images in NSFW channels until you hover over it.",github:"https://github.com/rauenzi/BetterDiscordAddons/tree/master/Plugins/BlurNSFW",github_raw:"https://raw.githubusercontent.com/rauenzi/BetterDiscordAddons/master/Plugins/BlurNSFW/BlurNSFW.plugin.js"},changelog:[{title:"New Option",items:["An option to stop blurring a picture when clicking on it/expanding it was added to settings."]}],defaultConfig:[{type:"slider",id:"blurSize",name:"Blur Size",note:"The size (in px) of the blurred pixels.",value:10,min:0,max:50,units:"px"},{type:"slider",id:"blurTime",name:"Blur Time",note:"The time (in ms) it takes for the blur to disappear and reappear.",value:200,min:0,max:5000,units:"ms"},{type:"switch",id:"blurOnFocus",name:"Blur When Focused",note:"This setting keeps the blur when clicking on/expanding an image.",value:true}],main:"index.js"};
+
+ return !global.ZeresPluginLibrary ? class {
+ constructor() {this._config = config;}
+ getName() {return config.info.name;}
+ getAuthor() {return config.info.authors.map(a => a.name).join(", ");}
+ getDescription() {return config.info.description;}
+ getVersion() {return config.info.version;}
+ load() {
+ BdApi.showConfirmationModal("Library Missing", `The library plugin needed for ${config.info.name} is missing. Please click Download Now to install it.`, {
+ confirmText: "Download Now",
+ cancelText: "Cancel",
+ onConfirm: () => {
+ require("request").get("https://rauenzi.github.io/BDPluginLibrary/release/0PluginLibrary.plugin.js", async (error, response, body) => {
+ if (error) return require("electron").shell.openExternal("https://betterdiscord.net/ghdl?url=https://raw.githubusercontent.com/rauenzi/BDPluginLibrary/master/release/0PluginLibrary.plugin.js");
+ await new Promise(r => require("fs").writeFile(require("path").join(BdApi.Plugins.folder, "0PluginLibrary.plugin.js"), body, r));
+ });
+ }
+ });
+ }
+ start() {}
+ stop() {}
+ } : (([Plugin, Api]) => {
+ const plugin = (Plugin, Api) => {
+ const {Patcher, WebpackModules, DiscordModules, PluginUtilities, Utilities} = Api;
+
+ const SelectedChannelStore = DiscordModules.SelectedChannelStore;
+ const ChannelStore = DiscordModules.ChannelStore;
+ const ReactDOM = DiscordModules.ReactDOM;
+ const InlineMediaWrapper = WebpackModules.getByProps("ImageReadyStates").default;
+
+ return class BlurNSFW extends Plugin {
+ constructor() {
+ super();
+ this.styleTemplate = `
+ {{blurOnFocus}}
+ img.blur:hover,
+ video.blur:hover {
+ transition: {{time}}ms cubic-bezier(.2, .11, 0, 1) !important;
+ filter: blur(0px) !important;
+ }
+
+ img.blur,
+ video.blur {
+ filter: blur({{size}}px) !important;
+ transition: {{time}}ms cubic-bezier(.2, .11, 0, 1) !important;
+ }`;
+ }
+
+ onStart() {
+ const blurAccessory = (thisObject) => {
+ const channel = ChannelStore.getChannel(SelectedChannelStore.getChannelId());
+ if (!channel || !channel.isNSFW || !channel.isNSFW()) return;
+ const element = ReactDOM.findDOMNode(thisObject);
+ const mediaElement = element.querySelector("img") || element.querySelector("video");
+ if (!mediaElement) return;
+
+ mediaElement.classList.add("blur");
+
+ if (mediaElement.tagName !== "VIDEO") return;
+ mediaElement.addEventListener("play", () => {
+ if (mediaElement.autoplay) return;
+ mediaElement.classList.remove("blur");
+ });
+ mediaElement.addEventListener("pause", () => {
+ if (mediaElement.autoplay) return;
+ mediaElement.classList.add("blur");
+ });
+ };
+
+ Patcher.after(InlineMediaWrapper.prototype, "componentDidMount", blurAccessory);
+ Patcher.after(InlineMediaWrapper.prototype, "componentDidUpdate", blurAccessory);
+
+ this.addStyle();
+ }
+
+ onStop() {
+ Patcher.unpatchAll();
+ this.removeStyle();
+ }
+
+ addStyle() {
+ const styleString = Utilities.formatString(this.styleTemplate, {
+ size: Math.round(this.settings.blurSize),
+ time: Math.round(this.settings.blurTime),
+ blurOnFocus: this.settings.blurOnFocus ? "" : ".layer-2KE1M9 img.blur,"
+ });
+ PluginUtilities.addStyle(this.getName(), styleString);
+ }
+
+ removeStyle() {
+ PluginUtilities.removeStyle(this.getName());
+ }
+
+ getSettingsPanel() {
+ const panel = this.buildSettingsPanel();
+ panel.addListener(() => {
+ this.removeStyle();
+ this.addStyle();
+ });
+ return panel.getElement();
+ }
+
+ };
+};
+ return plugin(Plugin, Api);
+ })(global.ZeresPluginLibrary.buildPlugin(config));
+})();
+/*@end@*/
\ No newline at end of file
diff --git a/.config/BetterDiscord/plugins/CreationDate.config.json b/.config/BetterDiscord/plugins/CreationDate.config.json
new file mode 100755
index 0000000..86a7c0d
--- /dev/null
+++ b/.config/BetterDiscord/plugins/CreationDate.config.json
@@ -0,0 +1,14 @@
+{
+ "all": {
+ "dates": {
+ "creationDate": {}
+ },
+ "general": {
+ "displayText": true
+ },
+ "places": {
+ "userPopout": true,
+ "userProfile": true
+ }
+ }
+}
\ No newline at end of file
diff --git a/.config/BetterDiscord/plugins/CreationDate.plugin.js b/.config/BetterDiscord/plugins/CreationDate.plugin.js
new file mode 100755
index 0000000..5c1f5d0
--- /dev/null
+++ b/.config/BetterDiscord/plugins/CreationDate.plugin.js
@@ -0,0 +1,309 @@
+/**
+ * @name CreationDate
+ * @author DevilBro
+ * @authorId 278543574059057154
+ * @version 1.4.6
+ * @description Displays the Creation Date of an Account in the UserPopout and UserModal
+ * @invite Jx3TjNS
+ * @donate https://www.paypal.me/MircoWittrien
+ * @patreon https://www.patreon.com/MircoWittrien
+ * @website https://mwittrien.github.io/
+ * @source https://github.com/mwittrien/BetterDiscordAddons/tree/master/Plugins/CreationDate/
+ * @updateUrl https://mwittrien.github.io/BetterDiscordAddons/Plugins/CreationDate/CreationDate.plugin.js
+ */
+
+module.exports = (_ => {
+ const config = {
+ "info": {
+ "name": "CreationDate",
+ "author": "DevilBro",
+ "version": "1.4.6",
+ "description": "Displays the Creation Date of an Account in the UserPopout and UserModal"
+ },
+ "changeLog": {
+ "fixed": {
+ "User Popout": "Fixing Stuff for the User Popout Update, thanks Discord"
+ }
+ }
+ };
+
+ return (window.Lightcord && !Node.prototype.isPrototypeOf(window.Lightcord) || window.LightCord && !Node.prototype.isPrototypeOf(window.LightCord) || window.Astra && !Node.prototype.isPrototypeOf(window.Astra)) ? class {
+ getName () {return config.info.name;}
+ getAuthor () {return config.info.author;}
+ getVersion () {return config.info.version;}
+ getDescription () {return "Do not use LightCord!";}
+ load () {BdApi.alert("Attention!", "By using LightCord you are risking your Discord Account, due to using a 3rd Party Client. Switch to an official Discord Client (https://discord.com/) with the proper BD Injection (https://betterdiscord.app/)");}
+ start() {}
+ stop() {}
+ } : !window.BDFDB_Global || (!window.BDFDB_Global.loaded && !window.BDFDB_Global.started) ? class {
+ getName () {return config.info.name;}
+ getAuthor () {return config.info.author;}
+ getVersion () {return config.info.version;}
+ getDescription () {return `The Library Plugin needed for ${config.info.name} is missing. Open the Plugin Settings to download it. \n\n${config.info.description}`;}
+
+ downloadLibrary () {
+ require("request").get("https://mwittrien.github.io/BetterDiscordAddons/Library/0BDFDB.plugin.js", (e, r, b) => {
+ if (!e && b && r.statusCode == 200) require("fs").writeFile(require("path").join(BdApi.Plugins.folder, "0BDFDB.plugin.js"), b, _ => BdApi.showToast("Finished downloading BDFDB Library", {type: "success"}));
+ else BdApi.alert("Error", "Could not download BDFDB Library Plugin. Try again later or download it manually from GitHub: https://mwittrien.github.io/downloader/?library");
+ });
+ }
+
+ load () {
+ if (!window.BDFDB_Global || !Array.isArray(window.BDFDB_Global.pluginQueue)) window.BDFDB_Global = Object.assign({}, window.BDFDB_Global, {pluginQueue: []});
+ if (!window.BDFDB_Global.downloadModal) {
+ window.BDFDB_Global.downloadModal = true;
+ BdApi.showConfirmationModal("Library Missing", `The Library Plugin needed for ${config.info.name} is missing. Please click "Download Now" to install it.`, {
+ confirmText: "Download Now",
+ cancelText: "Cancel",
+ onCancel: _ => {delete window.BDFDB_Global.downloadModal;},
+ onConfirm: _ => {
+ delete window.BDFDB_Global.downloadModal;
+ this.downloadLibrary();
+ }
+ });
+ }
+ if (!window.BDFDB_Global.pluginQueue.includes(config.info.name)) window.BDFDB_Global.pluginQueue.push(config.info.name);
+ }
+ start () {this.load();}
+ stop () {}
+ getSettingsPanel () {
+ let template = document.createElement("template");
+ template.innerHTML = `
The Library Plugin needed for ${config.info.name} is missing.\nPlease click
Download Now to install it.
`;
+ template.content.firstElementChild.querySelector("a").addEventListener("click", this.downloadLibrary);
+ return template.content.firstElementChild;
+ }
+ } : (([Plugin, BDFDB]) => {
+ return class CreationDate extends Plugin {
+ onLoad () {
+ this.defaults = {
+ general: {
+ displayText: {value: true, description: "Display '{{presuffix}}' in the Date"}
+ },
+ places: {
+ userPopout: {value: true, description: "User Popouts"},
+ userProfile: {value: true, description: "User Profile Modal"}
+ },
+ dates: {
+ creationDate: {value: {}, description: "Creation Date"},
+ }
+ };
+
+ this.patchedModules = {
+ after: {
+ UserPopoutInfo: "UserPopoutInfo",
+ UserProfileModalHeader: "default"
+ }
+ };
+
+ }
+
+ onStart () {
+ BDFDB.PatchUtils.forceAllUpdates(this);
+ }
+
+ onStop () {
+ BDFDB.PatchUtils.forceAllUpdates(this);
+ }
+
+ getSettingsPanel (collapseStates = {}) {
+ let settingsPanel;
+ return settingsPanel = BDFDB.PluginUtils.createSettingsPanel(this, {
+ collapseStates: collapseStates,
+ children: _ => {
+ let settingsItems = [];
+
+ settingsItems.push(Object.keys(this.defaults.general).map(key => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsSaveItem, {
+ type: "Switch",
+ plugin: this,
+ keys: ["general", key],
+ label: key == "displayText" ? this.defaults.general[key].description.replace("{{presuffix}}", this.labels.created_at.replace("{{time}}", "").trim()) : this.defaults.general[key].description,
+ value: this.settings.general[key]
+ })));
+
+ settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormDivider, {
+ className: BDFDB.disCN.marginbottom8
+ }));
+
+ settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsPanelList, {
+ title: "Add Date in:",
+ children: Object.keys(this.defaults.places).map(key => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsSaveItem, {
+ type: "Switch",
+ plugin: this,
+ keys: ["places", key],
+ label: this.defaults.places[key].description,
+ value: this.settings.places[key]
+ }))
+ }));
+
+ settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormDivider, {
+ className: BDFDB.disCN.marginbottom8
+ }));
+
+ settingsItems.push(Object.keys(this.defaults.dates).map(key => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.DateInput, Object.assign({}, this.settings.dates[key], {
+ label: this.defaults.dates[key].description,
+ prefix: _ => (this.settings.general.displayText && this.labels.created_at.split("{{time}}")[0] || "").trim(),
+ suffix: _ => (this.settings.general.displayText && this.labels.created_at.split("{{time}}")[1] || "").trim(),
+ onChange: valueObj => {
+ this.SettingsUpdated = true;
+ this.settings.dates[key] = valueObj;
+ BDFDB.DataUtils.save(this.settings.dates, this, "dates");
+ }
+ }))));
+
+ return settingsItems.flat(10);
+ }
+ });
+ }
+
+ onSettingsClosed () {
+ if (this.SettingsUpdated) {
+ delete this.SettingsUpdated;
+ BDFDB.PatchUtils.forceAllUpdates(this);
+ }
+ }
+
+ processUserPopoutInfo (e) {
+ if (e.instance.props.user && this.settings.places.userPopout) {
+ let [children, index] = BDFDB.ReactUtils.findParent(e.returnvalue, {name: ["DiscordTag", "ColoredFluxTag"]});
+ if (index > -1) this.injectDate(children, index + 1, e.instance.props.user);
+ }
+ }
+
+ processUserProfileModalHeader (e) {
+ if (e.instance.props.user && this.settings.places.userProfile) {
+ let [children, index] = BDFDB.ReactUtils.findParent(e.returnvalue, {name: ["DiscordTag", "ColoredFluxTag"]});
+ if (index > -1) this.injectDate(children, index + 1, e.instance.props.user);
+ }
+ }
+
+ injectDate (children, index, user) {
+ let timestamp = BDFDB.LibraryComponents.DateInput.format(this.settings.dates.creationDate, user.createdAt);
+ children.splice(index, 0, BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TextScroller, {
+ className: BDFDB.disCNS._creationdatedate + BDFDB.disCNS.userinfodate + BDFDB.disCN.textrow,
+ children: this.settings.general.displayText ? this.labels.created_at.replace("{{time}}", timestamp) : timestamp
+ }));
+ }
+
+ setLabelsByLanguage () {
+ switch (BDFDB.LanguageUtils.getLanguage().id) {
+ case "bg": // Bulgarian
+ return {
+ created_at: "Създадено на {{time}}"
+ };
+ case "cs": // Czech
+ return {
+ created_at: "Vytvořeno {{time}}"
+ };
+ case "da": // Danish
+ return {
+ created_at: "Oprettet den {{time}}"
+ };
+ case "de": // German
+ return {
+ created_at: "Erstellt am {{time}}"
+ };
+ case "el": // Greek
+ return {
+ created_at: "Δημιουργήθηκε στις {{time}}"
+ };
+ case "es": // Spanish
+ return {
+ created_at: "Creado el {{time}}"
+ };
+ case "fi": // Finnish
+ return {
+ created_at: "Luotu {{time}}"
+ };
+ case "fr": // French
+ return {
+ created_at: "Créé le {{time}}"
+ };
+ case "hi": // Hindi
+ return {
+ created_at: "{{time}} को बनाया गया"
+ };
+ case "hr": // Croatian
+ return {
+ created_at: "Izrađeno {{time}}"
+ };
+ case "hu": // Hungarian
+ return {
+ created_at: "Létrehozva: {{time}}"
+ };
+ case "it": // Italian
+ return {
+ created_at: "Creato il {{time}}"
+ };
+ case "ja": // Japanese
+ return {
+ created_at: "{{time}}に作成"
+ };
+ case "ko": // Korean
+ return {
+ created_at: "{{time}}에 생성됨"
+ };
+ case "lt": // Lithuanian
+ return {
+ created_at: "Sukurta {{time}}"
+ };
+ case "nl": // Dutch
+ return {
+ created_at: "Gemaakt op {{time}}"
+ };
+ case "no": // Norwegian
+ return {
+ created_at: "Opprettet {{time}}"
+ };
+ case "pl": // Polish
+ return {
+ created_at: "Utworzono {{time}}"
+ };
+ case "pt-BR": // Portuguese (Brazil)
+ return {
+ created_at: "Criado em {{time}}"
+ };
+ case "ro": // Romanian
+ return {
+ created_at: "Creat la {{time}}"
+ };
+ case "ru": // Russian
+ return {
+ created_at: "Создано {{time}}"
+ };
+ case "sv": // Swedish
+ return {
+ created_at: "Skapad {{time}}"
+ };
+ case "th": // Thai
+ return {
+ created_at: "สร้างเมื่อ {{time}}"
+ };
+ case "tr": // Turkish
+ return {
+ created_at: "{{time}} tarihinde oluşturuldu"
+ };
+ case "uk": // Ukrainian
+ return {
+ created_at: "Створено {{time}}"
+ };
+ case "vi": // Vietnamese
+ return {
+ created_at: "Được tạo vào {{time}}"
+ };
+ case "zh-CN": // Chinese (China)
+ return {
+ created_at: "创建于{{time}}"
+ };
+ case "zh-TW": // Chinese (Taiwan)
+ return {
+ created_at: "創建於{{time}}"
+ };
+ default: // English
+ return {
+ created_at: "Created on {{time}}"
+ };
+ }
+ }
+ };
+ })(window.BDFDB_Global.PluginUtils.buildPlugin(config));
+})();
\ No newline at end of file
diff --git a/.config/BetterDiscord/plugins/EditRoles.plugin.js b/.config/BetterDiscord/plugins/EditRoles.plugin.js
new file mode 100755
index 0000000..640c1b7
--- /dev/null
+++ b/.config/BetterDiscord/plugins/EditRoles.plugin.js
@@ -0,0 +1,681 @@
+/**
+ * @name EditRoles
+ * @author DevilBro
+ * @authorId 278543574059057154
+ * @version 1.0.9
+ * @description Allows you to locally edit Roles
+ * @invite Jx3TjNS
+ * @donate https://www.paypal.me/MircoWittrien
+ * @patreon https://www.patreon.com/MircoWittrien
+ * @website https://mwittrien.github.io/
+ * @source https://github.com/mwittrien/BetterDiscordAddons/tree/master/Plugins/EditRoles/
+ * @updateUrl https://mwittrien.github.io/BetterDiscordAddons/Plugins/EditRoles/EditRoles.plugin.js
+ */
+
+module.exports = (_ => {
+ const config = {
+ "info": {
+ "name": "EditRoles",
+ "author": "DevilBro",
+ "version": "1.0.9",
+ "description": "Allows you to locally edit Roles"
+ },
+ "changeLog": {
+ "fixed": {
+ "Remove Icon": "Now works again"
+ }
+ }
+ };
+
+ return !window.BDFDB_Global || (!window.BDFDB_Global.loaded && !window.BDFDB_Global.started) ? class {
+ getName () {return config.info.name;}
+ getAuthor () {return config.info.author;}
+ getVersion () {return config.info.version;}
+ getDescription () {return `The Library Plugin needed for ${config.info.name} is missing. Open the Plugin Settings to download it. \n\n${config.info.description}`;}
+
+ downloadLibrary () {
+ require("request").get("https://mwittrien.github.io/BetterDiscordAddons/Library/0BDFDB.plugin.js", (e, r, b) => {
+ if (!e && b && r.statusCode == 200) require("fs").writeFile(require("path").join(BdApi.Plugins.folder, "0BDFDB.plugin.js"), b, _ => BdApi.showToast("Finished downloading BDFDB Library", {type: "success"}));
+ else BdApi.alert("Error", "Could not download BDFDB Library Plugin. Try again later or download it manually from GitHub: https://mwittrien.github.io/downloader/?library");
+ });
+ }
+
+ load () {
+ if (!window.BDFDB_Global || !Array.isArray(window.BDFDB_Global.pluginQueue)) window.BDFDB_Global = Object.assign({}, window.BDFDB_Global, {pluginQueue: []});
+ if (!window.BDFDB_Global.downloadModal) {
+ window.BDFDB_Global.downloadModal = true;
+ BdApi.showConfirmationModal("Library Missing", `The Library Plugin needed for ${config.info.name} is missing. Please click "Download Now" to install it.`, {
+ confirmText: "Download Now",
+ cancelText: "Cancel",
+ onCancel: _ => {delete window.BDFDB_Global.downloadModal;},
+ onConfirm: _ => {
+ delete window.BDFDB_Global.downloadModal;
+ this.downloadLibrary();
+ }
+ });
+ }
+ if (!window.BDFDB_Global.pluginQueue.includes(config.info.name)) window.BDFDB_Global.pluginQueue.push(config.info.name);
+ }
+ start () {this.load();}
+ stop () {}
+ getSettingsPanel () {
+ let template = document.createElement("template");
+ template.innerHTML = `The Library Plugin needed for ${config.info.name} is missing.\nPlease click
Download Now to install it.
`;
+ template.content.firstElementChild.querySelector("a").addEventListener("click", this.downloadLibrary);
+ return template.content.firstElementChild;
+ }
+ } : (([Plugin, BDFDB]) => {
+ var changedRoles = {}, cachedRoles = {};
+
+ return class EditRoles extends Plugin {
+ onLoad () {
+ this.patchedModules = {
+ before: {
+ MessageHeader: "default",
+ ChannelMembers: "render",
+ MemberListItem: "render",
+ UserPopoutBody: "default"
+ }
+ };
+ }
+
+ onStart () {
+ BDFDB.PatchUtils.patch(this, BDFDB.LibraryModules.GuildStore, "getGuild", {after: e => {
+ if (e.returnValue) e.returnValue = this.changeRolesInGuild(e.returnValue, true);
+ }});
+ BDFDB.PatchUtils.patch(this, BDFDB.LibraryModules.PermissionRoleUtils, "getHighestRole", {after: e => {
+ if (e.returnValue && changedRoles[e.returnValue.id]) {
+ let data = changedRoles[e.returnValue.id];
+ e.returnValue = Object.assign({}, e.returnValue, {
+ name: data.name || e.returnValue.name,
+ color: data.color ? BDFDB.ColorUtils.convert(data.color, "INT") : e.returnValue.color,
+ colorString: data.color ? BDFDB.ColorUtils.convert(data.color, "HEX") : e.returnValue.colorString
+ });
+ }
+ }});
+ BDFDB.PatchUtils.patch(this, BDFDB.LibraryModules.MemberStore, "getMember", {after: e => {
+ if (e.returnValue) {
+ let guild = BDFDB.LibraryModules.GuildStore.getGuild(e.methodArguments[0]);
+ if (guild) {
+ let colorRole, iconRole;
+ for (let id of e.returnValue.roles) {
+ if (guild.roles[id] && guild.roles[id].colorString && (!colorRole || colorRole.position < guild.roles[id].position)) colorRole = guild.roles[id];
+ if (guild.roles[id] && guild.roles[id].icon && (!iconRole || iconRole.position < guild.roles[id].position)) iconRole = guild.roles[id];
+ }
+ let color = colorRole && changedRoles[colorRole.id] && changedRoles[colorRole.id].color;
+ if (color) e.returnValue = Object.assign({}, e.returnValue, {colorString: BDFDB.ColorUtils.convert(color, "HEX")});
+ if (iconRole && changedRoles[iconRole.id] && changedRoles[iconRole.id].icon) e.returnValue = Object.assign({}, e.returnValue, {iconRoleId: iconRole.id});
+ }
+ }
+ }});
+ BDFDB.PatchUtils.patch(this, BDFDB.LibraryModules.RoleIconUtils, "getRoleIconData", {after: e => {
+ if (e.returnValue && e.methodArguments[0].id && changedRoles[e.methodArguments[0].id]) {
+ if (changedRoles[e.methodArguments[0].id].icon) return {customIconSrc: changedRoles[e.methodArguments[0].id].icon};
+ else if (changedRoles[e.methodArguments[0].id].removeIcon) return {customIconSrc: null};
+ }
+ }});
+ BDFDB.PatchUtils.patch(this, BDFDB.LibraryModules.RoleIconUtils, "canGuildUseRoleIcons", {after: e => {
+ if (e.returnValue === false && Object.keys(e.methodArguments[0].roles).some(roleId => changedRoles[roleId] && changedRoles[roleId].icon)) return true;
+ }});
+
+ this.forceUpdateAll();
+ }
+
+ onStop () {
+ this.forceUpdateAll();
+ }
+
+ getSettingsPanel (collapseStates = {}) {
+ let settingsPanel;
+ return settingsPanel = BDFDB.PluginUtils.createSettingsPanel(this, {
+ collapseStates: collapseStates,
+ children: _ => {
+ let settingsItems = [];
+
+ settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsItem, {
+ type: "Button",
+ color: BDFDB.LibraryComponents.Button.Colors.RED,
+ label: "Reset all Roles",
+ onClick: _ => {
+ BDFDB.ModalUtils.confirm(this, this.labels.confirm_resetall, _ => {
+ this.resetRoles();
+ this.forceUpdateAll();
+ });
+ },
+ children: BDFDB.LanguageUtils.LanguageStrings.RESET
+ }));
+
+ return settingsItems.flat(10);
+ }
+ });
+ }
+
+ onSettingsClosed () {
+ if (this.SettingsUpdated) {
+ delete this.SettingsUpdated;
+ this.forceUpdateAll();
+ }
+ }
+
+ forceUpdateAll () {
+ changedRoles = BDFDB.DataUtils.load(this, "roles");
+
+ BDFDB.PatchUtils.forceAllUpdates(this);
+ BDFDB.MessageUtils.rerenderAll();
+ }
+
+ onUserContextMenu (e) {
+ if (e.subType == "useUserRolesItems") {
+ let [children, index] = BDFDB.ContextMenuUtils.findItem(e.returnvalue, {id: "roles"});
+ if (index > -1 && children[index].props && BDFDB.ArrayUtils.is(children[index].props.children)) for (let child of children[index].props.children) {
+ if (child && child.props && typeof child.props.label == "function") {
+ let renderLabel = child.props.label;
+ child.props.label = (...args) => {
+ let label = renderLabel(...args);
+ let onContextMenu = typeof label.props.onContextMenu == "function" ? label.props.onContextMenu : (_ => {});
+ label.props.onContextMenu = event => BDFDB.LibraryModules.ContextMenuUtils.openContextMenu(event, e => BDFDB.ReactUtils.createElement(BDFDB.ModuleUtils.findByName("DeveloperContextMenu"), Object.assign({}, e2, {id: child.props.id})));
+ return label;
+ };
+ }
+ }
+ }
+ }
+
+ onGuildContextMenu (e) {
+ if (e.instance.props.guild) e.instance.props.guild = this.changeRolesInGuild(e.instance.props.guild);
+ }
+
+ onDeveloperContextMenu (e) {
+ let guild = this.getGuildFromRoleId(e.instance.props.id);
+ if (guild) e.returnvalue.props.children = [
+ BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuGroup, {
+ children: BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, {
+ label: this.labels.context_localrolesettings,
+ id: BDFDB.ContextMenuUtils.createItemId(this.name, "settings-submenu"),
+ children: BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuGroup, {
+ children: [
+ BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, {
+ label: this.labels.submenu_rolesettings,
+ id: BDFDB.ContextMenuUtils.createItemId(this.name, "settings-change"),
+ action: _ => {
+ this.openRoleSettingsModal(guild.roles[e.instance.props.id]);
+ }
+ }),
+ BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, {
+ label: this.labels.submenu_resetsettings,
+ id: BDFDB.ContextMenuUtils.createItemId(this.name, "settings-reset"),
+ color: BDFDB.LibraryComponents.MenuItems.Colors.DANGER,
+ disabled: !changedRoles[e.instance.props.id],
+ action: event => {
+ let remove = _ => {
+ this.resetRoles(e.instance.props.id);
+ this.forceUpdateAll(true);
+ };
+ if (event.shiftKey) remove();
+ else BDFDB.ModalUtils.confirm(this, this.labels.confirm_reset, remove);
+ }
+ })
+ ]
+ })
+ })
+ }),
+ e.returnvalue.props.children
+ ].flat(10).filter(n => n);
+ }
+
+ processChannelMembers (e) {
+ e.instance.props.groups = [].concat(e.instance.props.groups);
+ for (let i in e.instance.props.groups) if (e.instance.props.groups[i].type == "GROUP") {
+ let data = changedRoles[e.instance.props.groups[i].id];
+ if (data && data.name) e.instance.props.groups[i] = Object.assign({}, e.instance.props.groups[i], {title: data.name});
+ }
+ e.instance.props.rows = [].concat(e.instance.props.rows);
+ for (let i in e.instance.props.rows) if (e.instance.props.rows[i].type == "GROUP") {
+ let data = changedRoles[e.instance.props.rows[i].id];
+ if (data && data.name) e.instance.props.rows[i] = Object.assign({}, e.instance.props.rows[i], {title: data.name});
+ }
+ }
+
+ processMemberListItem (e) {
+ if (e.instance.props.user) {
+ let member = BDFDB.LibraryModules.MemberStore.getMember(e.instance.props.guildId, e.instance.props.user.id);
+ if (member) e.instance.props.colorString = member.colorString;
+ }
+ }
+
+ processUserPopoutBody (e) {
+ if (e.instance.props.guild) e.instance.props.guild = this.changeRolesInGuild(e.instance.props.guild);
+ }
+
+ getGuildFromRoleId (roleId) {
+ return BDFDB.LibraryModules.FolderStore.getFlattenedGuilds().find(g => g.roles[roleId]);
+ }
+
+ changeRolesInGuild (guild, useNative) {
+ let changed = false, roles = Object.assign({}, guild.roles);
+ for (let id in guild.roles) {
+ let data = changedRoles[id];
+ if (data) {
+ changed = true;
+ roles[id] = Object.assign({}, roles[id], {
+ name: data.name || roles[id].name,
+ icon: data.icon || roles[id].icon,
+ color: data.color ? BDFDB.ColorUtils.convert(data.color, "INT") : roles[id].color,
+ colorString: data.color ? BDFDB.ColorUtils.convert(data.color, "HEX") : roles[id].colorString
+ });
+ }
+ }
+ if (useNative && changed && !cachedRoles[guild.id]) cachedRoles[guild.id] = guild.roles;
+ if (useNative) guild.roles = roles;
+ return !changed || useNative ? guild : (new BDFDB.DiscordObjects.Guild(Object.assign({}, guild, {roles})));
+ }
+
+ resetRoles (id) {
+ if (id) {
+ let guild = this.getGuildFromRoleId(id);
+ if (guild && cachedRoles[guild.id]) guild.roles = Object.assign({}, guild.roles, {[id]: cachedRoles[guild.id][id]});
+ BDFDB.DataUtils.remove(this, "roles", id);
+ }
+ else {
+ for (let guild of BDFDB.LibraryModules.FolderStore.getFlattenedGuilds()) if (cachedRoles[guild.id]) guild.roles = cachedRoles[guild.id];
+ cachedRoles = {};
+ BDFDB.DataUtils.remove(this, "roles");
+ }
+ }
+
+ openRoleSettingsModal (role) {
+ let data = changedRoles[role.id] || {};
+ let newData = Object.assign({}, data);
+
+ let iconInput;
+
+ BDFDB.ModalUtils.open(this, {
+ size: "MEDIUM",
+ header: this.labels.modal_header,
+ subHeader: role.name,
+ children: [
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormItem, {
+ title: BDFDB.LanguageUtils.LanguageStrings.FORM_LABEL_ROLE_NAME,
+ className: BDFDB.disCN.marginbottom20,
+ children: [
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TextInput, {
+ value: data.name,
+ placeholder: role.name,
+ autoFocus: true,
+ onChange: value => newData.name = value
+ }),
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormDivider, {
+ className: BDFDB.disCN.dividerdefault
+ })
+ ]
+ }),
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormItem, {
+ title: BDFDB.LanguageUtils.LanguageStrings.FORM_LABEL_ROLE_COLOR,
+ className: BDFDB.disCN.marginbottom20,
+ children: [
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.ColorSwatches, {
+ color: data.color,
+ defaultCustomColor: role.colorString,
+ pickerConfig: {
+ alpha: false,
+ gradient: false
+ },
+ onColorChange: value => newData.color = value
+ })
+ ]
+ }),
+ BDFDB.ReactUtils.createElement("div", {
+ className: BDFDB.disCN.marginbottom20,
+ children: [
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormDivider, {
+ className: BDFDB.disCNS.dividerdefault + BDFDB.disCN.marginbottom20
+ }),
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex, {
+ className: BDFDB.disCN.marginbottom8,
+ align: BDFDB.LibraryComponents.Flex.Align.CENTER,
+ direction: BDFDB.LibraryComponents.Flex.Direction.HORIZONTAL,
+ children: [
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormTitle, {
+ className: BDFDB.disCN.marginreset,
+ tag: BDFDB.LibraryComponents.FormComponents.FormTitle.Tags.H5,
+ children: BDFDB.LibraryModules.LanguageStore.Messages.FORM_LABEL_ROLE_ICON
+ }),
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsItem, {
+ type: "Switch",
+ margin: 0,
+ grow: 0,
+ label: BDFDB.LanguageUtils.LanguageStrings.REMOVE,
+ tag: BDFDB.LibraryComponents.FormComponents.FormTitle.Tags.H5,
+ value: data.removeIcon,
+ onChange: value => {
+ newData.removeIcon = value;
+ if (value) {
+ delete iconInput.props.success;
+ delete iconInput.props.errorMessage;
+ iconInput.props.disabled = true;
+ BDFDB.ReactUtils.forceUpdate(iconInput);
+ }
+ else {
+ iconInput.props.disabled = false;
+ this.checkUrl(iconInput.props.value, iconInput).then(returnValue => newData.icon = returnValue);
+ }
+ }
+ })
+ ]
+ }),
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TextInput, {
+ success: !data.removeIcon && data.icon,
+ maxLength: 100000000000000000000,
+ value: data.icon,
+ placeholder: role.icon,
+ disabled: data.removeIcon,
+ ref: instance => {if (instance) iconInput = instance;},
+ onChange: (value, instance) => {
+ this.checkUrl(value, instance).then(returnValue => newData.icon = returnValue);
+ }
+ })
+ ]
+ })
+ ],
+ buttons: [{
+ contents: BDFDB.LanguageUtils.LanguageStrings.SAVE,
+ color: "BRAND",
+ close: true,
+ onClick: _ => {
+ let changed = false;
+ if (Object.keys(newData).every(key => newData[key] == null || newData[key] == false) && (changed = true)) {
+ this.resetRoles(role.id);
+ }
+ else if (!BDFDB.equals(newData, data) && (changed = true)) {
+ BDFDB.DataUtils.save(newData, this, "roles", role.id);
+ }
+ if (changed) this.forceUpdateAll();
+ }
+ }]
+ });
+ }
+
+ checkUrl (url, instance) {
+ return new Promise(callback => {
+ BDFDB.TimeUtils.clear(instance.checkTimeout);
+ url = url && url.trim();
+ if (!url || instance.props.disabled) {
+ delete instance.props.success;
+ delete instance.props.errorMessage;
+ callback("");
+ BDFDB.ReactUtils.forceUpdate(instance);
+ }
+ else instance.checkTimeout = BDFDB.TimeUtils.timeout(_ => {
+ BDFDB.LibraryRequires.request(url, {agentOptions: {rejectUnauthorized: false}}, (error, response, result) => {
+ delete instance.checkTimeout;
+ if (instance.props.disabled) {
+ delete instance.props.success;
+ delete instance.props.errorMessage;
+ callback("");
+ }
+ else if (response && response.headers["content-type"] && response.headers["content-type"].indexOf("image") != -1) {
+ instance.props.success = true;
+ delete instance.props.errorMessage;
+ callback(url);
+ }
+ else {
+ delete instance.props.success;
+ instance.props.errorMessage = this.labels.modal_invalidurl;
+ callback("");
+ }
+ BDFDB.ReactUtils.forceUpdate(instance);
+ });
+ }, 1000);
+ });
+ }
+
+ setLabelsByLanguage () {
+ switch (BDFDB.LanguageUtils.getLanguage().id) {
+ case "bg": // Bulgarian
+ return {
+ confirm_reset: "Наистина ли искате да нулирате тази роля?",
+ confirm_resetall: "Наистина ли искате да нулирате всички роли?",
+ context_localrolesettings: "Настройки на местната роля",
+ modal_header: "Настройки на местната роля",
+ submenu_resetsettings: "Нулиране на ролята",
+ submenu_rolesettings: "Промяна на настройките"
+ };
+ case "da": // Danish
+ return {
+ confirm_reset: "Er du sikker på, at du vil nulstille denne rolle?",
+ confirm_resetall: "Er du sikker på, at du vil nulstille alle roller?",
+ context_localrolesettings: "Lokale rolleindstillinger",
+ modal_header: "Lokale rolleindstillinger",
+ submenu_resetsettings: "Nulstil rolle",
+ submenu_rolesettings: "Ændre indstillinger"
+ };
+ case "de": // German
+ return {
+ confirm_reset: "Möchtest du diese Rolle wirklich zurücksetzen?",
+ confirm_resetall: "Möchtest du wirklich alle Rollen zurücksetzen?",
+ context_localrolesettings: "Lokale Rolleneinstellungen",
+ modal_header: "Lokale Rolleneinstellungen",
+ submenu_resetsettings: "Rolle zurücksetzen",
+ submenu_rolesettings: "Einstellungen ändern"
+ };
+ case "el": // Greek
+ return {
+ confirm_reset: "Είστε βέβαιοι ότι θέλετε να επαναφέρετε αυτόν τον ρόλο;",
+ confirm_resetall: "Είστε βέβαιοι ότι θέλετε να επαναφέρετε όλους τους ρόλους;",
+ context_localrolesettings: "Ρυθμίσεις τοπικού ρόλου",
+ modal_header: "Ρυθμίσεις τοπικού ρόλου",
+ submenu_resetsettings: "Επαναφορά ρόλου",
+ submenu_rolesettings: "Αλλαξε ρυθμίσεις"
+ };
+ case "es": // Spanish
+ return {
+ confirm_reset: "¿Está seguro de que desea restablecer este rol?",
+ confirm_resetall: "¿Está seguro de que desea restablecer todos los roles?",
+ context_localrolesettings: "Configuración de roles locales",
+ modal_header: "Configuración de roles locales",
+ submenu_resetsettings: "Restablecer rol",
+ submenu_rolesettings: "Cambiar ajustes"
+ };
+ case "fi": // Finnish
+ return {
+ confirm_reset: "Haluatko varmasti nollata tämän roolin?",
+ confirm_resetall: "Haluatko varmasti nollata kaikki roolit?",
+ context_localrolesettings: "Paikalliset rooliasetukset",
+ modal_header: "Paikalliset rooliasetukset",
+ submenu_resetsettings: "Nollaa rooli",
+ submenu_rolesettings: "Vaihda asetuksia"
+ };
+ case "fr": // French
+ return {
+ confirm_reset: "Voulez-vous vraiment réinitialiser ce rôle?",
+ confirm_resetall: "Voulez-vous vraiment réinitialiser tous les rôles?",
+ context_localrolesettings: "Paramètres de rôle locaux",
+ modal_header: "Paramètres de rôle locaux",
+ submenu_resetsettings: "Réinitialiser le rôle",
+ submenu_rolesettings: "Modifier les paramètres"
+ };
+ case "hr": // Croatian
+ return {
+ confirm_reset: "Jeste li sigurni da želite resetirati ovu ulogu?",
+ confirm_resetall: "Jeste li sigurni da želite resetirati sve uloge?",
+ context_localrolesettings: "Postavke lokalne uloge",
+ modal_header: "Postavke lokalne uloge",
+ submenu_resetsettings: "Resetiraj ulogu",
+ submenu_rolesettings: "Promijeniti postavke"
+ };
+ case "hu": // Hungarian
+ return {
+ confirm_reset: "Biztosan vissza akarja állítani ezt a szerepet?",
+ confirm_resetall: "Biztosan vissza akarja állítani az összes szerepet?",
+ context_localrolesettings: "Helyi szerepbeállítások",
+ modal_header: "Helyi szerepbeállítások",
+ submenu_resetsettings: "A szerepkör visszaállítása",
+ submenu_rolesettings: "Beállítások megváltoztatása"
+ };
+ case "it": // Italian
+ return {
+ confirm_reset: "Sei sicuro di voler reimpostare questo ruolo?",
+ confirm_resetall: "Sei sicuro di voler reimpostare tutti i ruoli?",
+ context_localrolesettings: "Impostazioni ruolo locale",
+ modal_header: "Impostazioni ruolo locale",
+ submenu_resetsettings: "Reimposta ruolo",
+ submenu_rolesettings: "Cambia impostazioni"
+ };
+ case "ja": // Japanese
+ return {
+ confirm_reset: "この役割をリセットしてもよろしいですか?",
+ confirm_resetall: "すべての役割をリセットしてもよろしいですか?",
+ context_localrolesettings: "ローカルロール設定",
+ modal_header: "ローカルロール設定",
+ submenu_resetsettings: "役割をリセット",
+ submenu_rolesettings: "設定を変更する"
+ };
+ case "ko": // Korean
+ return {
+ confirm_reset: "이 역할을 재설정 하시겠습니까?",
+ confirm_resetall: "모든 역할을 재설정 하시겠습니까?",
+ context_localrolesettings: "로컬 역할 설정",
+ modal_header: "로컬 역할 설정",
+ submenu_resetsettings: "역할 재설정",
+ submenu_rolesettings: "설정 변경"
+ };
+ case "lt": // Lithuanian
+ return {
+ confirm_reset: "Ar tikrai norite iš naujo nustatyti šį vaidmenį?",
+ confirm_resetall: "Ar tikrai norite iš naujo nustatyti visus vaidmenis?",
+ context_localrolesettings: "Vietos vaidmens nustatymai",
+ modal_header: "Vietos vaidmens nustatymai",
+ submenu_resetsettings: "Iš naujo nustatyti vaidmenį",
+ submenu_rolesettings: "Pakeisti nustatymus"
+ };
+ case "nl": // Dutch
+ return {
+ confirm_reset: "Weet u zeker dat u deze rol wilt resetten?",
+ confirm_resetall: "Weet u zeker dat u alle rollen opnieuw wilt instellen?",
+ context_localrolesettings: "Lokale rolinstellingen",
+ modal_header: "Lokale rolinstellingen",
+ submenu_resetsettings: "Rol opnieuw instellen",
+ submenu_rolesettings: "Instellingen veranderen"
+ };
+ case "no": // Norwegian
+ return {
+ confirm_reset: "Er du sikker på at du vil tilbakestille denne rollen?",
+ confirm_resetall: "Er du sikker på at du vil tilbakestille alle rollene?",
+ context_localrolesettings: "Lokale rolleinnstillinger",
+ modal_header: "Lokale rolleinnstillinger",
+ submenu_resetsettings: "Tilbakestill rolle",
+ submenu_rolesettings: "Endre innstillinger"
+ };
+ case "pl": // Polish
+ return {
+ confirm_reset: "Czy na pewno chcesz zresetować tę rolę?",
+ confirm_resetall: "Czy na pewno chcesz zresetować wszystkie role?",
+ context_localrolesettings: "Ustawienia roli lokalnej",
+ modal_header: "Ustawienia roli lokalnej",
+ submenu_resetsettings: "Zresetuj rolę",
+ submenu_rolesettings: "Zmień ustawienia"
+ };
+ case "pt-BR": // Portuguese (Brazil)
+ return {
+ confirm_reset: "Tem certeza de que deseja redefinir esta função?",
+ confirm_resetall: "Tem certeza de que deseja redefinir todas as funções?",
+ context_localrolesettings: "Configurações de função local",
+ modal_header: "Configurações de função local",
+ submenu_resetsettings: "Redefinir função",
+ submenu_rolesettings: "Mudar configurações"
+ };
+ case "ro": // Romanian
+ return {
+ confirm_reset: "Sigur doriți să resetați acest rol?",
+ confirm_resetall: "Sigur doriți să resetați toate rolurile?",
+ context_localrolesettings: "Setări rol local",
+ modal_header: "Setări rol local",
+ submenu_resetsettings: "Resetați rolul",
+ submenu_rolesettings: "Schimbă setările"
+ };
+ case "ru": // Russian
+ return {
+ confirm_reset: "Вы уверены, что хотите сбросить эту роль?",
+ confirm_resetall: "Вы уверены, что хотите сбросить все роли?",
+ context_localrolesettings: "Настройки локальной роли",
+ modal_header: "Настройки локальной роли",
+ submenu_resetsettings: "Сбросить роль",
+ submenu_rolesettings: "Изменить настройки"
+ };
+ case "sv": // Swedish
+ return {
+ confirm_reset: "Är du säker på att du vill återställa denna roll?",
+ confirm_resetall: "Är du säker på att du vill återställa alla roller?",
+ context_localrolesettings: "Lokala rollinställningar",
+ modal_header: "Lokala rollinställningar",
+ submenu_resetsettings: "Återställ roll",
+ submenu_rolesettings: "Ändra inställningar"
+ };
+ case "th": // Thai
+ return {
+ confirm_reset: "แน่ใจไหมว่าต้องการรีเซ็ตบทบาทนี้",
+ confirm_resetall: "แน่ใจไหมว่าต้องการรีเซ็ตบทบาททั้งหมด",
+ context_localrolesettings: "การตั้งค่าบทบาทท้องถิ่น",
+ modal_header: "การตั้งค่าบทบาทท้องถิ่น",
+ submenu_resetsettings: "รีเซ็ตบทบาท",
+ submenu_rolesettings: "เปลี่ยนการตั้งค่า"
+ };
+ case "tr": // Turkish
+ return {
+ confirm_reset: "Bu Rolü sıfırlamak istediğinizden emin misiniz?",
+ confirm_resetall: "Tüm Rolleri sıfırlamak istediğinizden emin misiniz?",
+ context_localrolesettings: "Yerel Rol Ayarları",
+ modal_header: "Yerel Rol Ayarları",
+ submenu_resetsettings: "Rolü Sıfırla",
+ submenu_rolesettings: "Ayarları değiştir"
+ };
+ case "uk": // Ukrainian
+ return {
+ confirm_reset: "Ви впевнені, що хочете скинути цю роль?",
+ confirm_resetall: "Ви впевнені, що хочете скинути всі ролі?",
+ context_localrolesettings: "Налаштування локальної ролі",
+ modal_header: "Налаштування локальної ролі",
+ submenu_resetsettings: "Скинути роль",
+ submenu_rolesettings: "Змінити налаштування"
+ };
+ case "vi": // Vietnamese
+ return {
+ confirm_reset: "Bạn có chắc chắn muốn đặt lại Vai trò này không?",
+ confirm_resetall: "Bạn có chắc chắn muốn đặt lại tất cả các Vai trò không?",
+ context_localrolesettings: "Cài đặt vai trò cục bộ",
+ modal_header: "Cài đặt vai trò cục bộ",
+ submenu_resetsettings: "Đặt lại vai trò",
+ submenu_rolesettings: "Thay đổi cài đặt"
+ };
+ case "zh-CN": // Chinese (China)
+ return {
+ confirm_reset: "您确定要重置此角色吗?",
+ confirm_resetall: "您确定要重置所有角色吗?",
+ context_localrolesettings: "本地角色设置",
+ modal_header: "本地角色设置",
+ submenu_resetsettings: "重置角色",
+ submenu_rolesettings: "更改设置"
+ };
+ case "zh-TW": // Chinese (Taiwan)
+ return {
+ confirm_reset: "您確定要重置此角色嗎?",
+ confirm_resetall: "您確定要重置所有角色嗎?",
+ context_localrolesettings: "本地角色設置",
+ modal_header: "本地角色設置",
+ submenu_resetsettings: "重置角色",
+ submenu_rolesettings: "更改設置"
+ };
+ default: // English
+ return {
+ confirm_reset: "Are you sure you want to reset this Role?",
+ confirm_resetall: "Are you sure you want to reset all Roles?",
+ context_localrolesettings: "Local Role Settings",
+ modal_header: "Local Role Settings",
+ submenu_resetsettings: "Reset Role",
+ submenu_rolesettings: "Change Settings"
+ };
+ }
+ }
+ };
+ })(window.BDFDB_Global.PluginUtils.buildPlugin(config));
+})();
\ No newline at end of file
diff --git a/.config/BetterDiscord/plugins/Freemoji.config.json b/.config/BetterDiscord/plugins/Freemoji.config.json
new file mode 100755
index 0000000..82df47a
--- /dev/null
+++ b/.config/BetterDiscord/plugins/Freemoji.config.json
@@ -0,0 +1,6 @@
+{
+ "currentVersionInfo": {
+ "version": "1.7.3",
+ "hasShownChangelog": true
+ }
+}
\ No newline at end of file
diff --git a/.config/BetterDiscord/plugins/Freemoji.plugin.js b/.config/BetterDiscord/plugins/Freemoji.plugin.js
new file mode 100755
index 0000000..330b2ab
--- /dev/null
+++ b/.config/BetterDiscord/plugins/Freemoji.plugin.js
@@ -0,0 +1,374 @@
+/**
+* @name Freemoji
+* @displayName Freemoji
+* @description Send emoji external emoji and animated emoji without Nitro.
+* @author Qb, An0
+* @authorId 133659541198864384
+* @license LGPLv3 - https://www.gnu.org/licenses/lgpl-3.0.txt
+* @version 1.7.3
+* @invite gj7JFa6mF8
+* @source https://github.com/QbDesu/BetterDiscordAddons/blob/potato/Plugins/Freemoji
+* @updateUrl https://raw.githubusercontent.com/QbDesu/BetterDiscordAddons/potato/Plugins/Freemoji/Freemoji.plugin.js
+*/
+/*@cc_on
+@if (@_jscript)
+
+var shell = WScript.CreateObject("WScript.Shell");
+shell.Popup("It looks like you've mistakenly tried to run me directly. That's not how you install plugins. \n(So don't do that!)", 0, "I'm a plugin for BetterDiscord", 0x30);
+
+@else@*/
+
+module.exports = (() => {
+ const config = {
+ info: {
+ name: 'Freemoji',
+ authors: [
+ {
+ name: 'Qb',
+ discord_id: '133659541198864384',
+ github_username: 'QbDesu'
+ },
+ {
+ name: 'An0',
+ github_username: 'An00nymushun'
+ }
+ ],
+ version: '1.7.3',
+ description: 'Send emoji external emoji and animated emoji without Nitro.',
+ github: 'https://github.com/QbDesu/BetterDiscordAddons/blob/potato/Plugins/Freemoji',
+ github_raw: 'https://raw.githubusercontent.com/QbDesu/BetterDiscordAddons/potato/Plugins/Freemoji/Freemoji.plugin.js'
+ },
+ changelog: [
+ { title: 'Bug Fixes', types: 'fixed', items: ['Stopped clyde from warining your about using unavailable emoji.'] }
+ ],
+ defaultConfig: [
+ {
+ type: 'switch',
+ id: 'sendDirectly',
+ name: 'Send Directly',
+ note: 'Send the emoji link in a message directly instead of putting it in the chat box.',
+ value: false
+ },
+ {
+ type: 'switch',
+ id: 'split',
+ name: 'Automatically Split Emoji Messages',
+ note: 'Automatically splits messages containing emoji links so there won\'t be links in the middle of your messages.',
+ value: false
+ },
+ {
+ type: 'slider',
+ id: 'emojiSize',
+ name: 'Emoji Size',
+ note: 'The size of the emoji in pixels. 48 is recommended because it is the size of regular Discord emoji.',
+ value: 48,
+ markers: [32, 40, 48, 60, 64, 80, 96],
+ stickToMarkers: true
+ },
+ {
+ type: 'dropdown',
+ id: 'removeGrayscale',
+ name: 'Remove Grayscale Filter',
+ note: 'Remove the grayscale filter on emoji that would normally not be usable.',
+ value: 'embedPerms',
+ options: [
+ {
+ label: 'Always',
+ value: 'always'
+ },
+ {
+ label: 'With Embed Perms',
+ value: 'embedPerms'
+ },
+ {
+ label: 'Never',
+ value: 'never'
+ }
+ ]
+ },
+ {
+ type: 'dropdown',
+ id: 'missingEmbedPerms',
+ name: 'Missing Embed Perms Behaviour',
+ note: 'What should happen if you select an emoji even though you have no embed permissions.',
+ value: 'showDialog',
+ options: [
+ {
+ label: 'Show Confirmation Dialog',
+ value: 'showDialog'
+ },
+ {
+ label: 'Insert Anyway',
+ value: 'insert'
+ },
+ {
+ label: 'Nothing',
+ value: 'nothing'
+ }
+ ]
+ },
+ {
+ type: 'dropdown',
+ id: 'external',
+ name: 'Allow External Emoji',
+ note: 'Allow External Emoji for servers that have them disabled.',
+ value: 'showDialog',
+ options: [
+ {
+ label: 'Don\'t Allow',
+ value: 'off'
+ },
+ {
+ label: 'Show Confirmation Dialog',
+ value: 'showDialog'
+ },
+ {
+ label: 'Allow',
+ value: 'allow'
+ }
+ ]
+ }
+ ]
+ };
+ return !global.ZeresPluginLibrary ? class {
+ constructor() { this._config = config; }
+ load() {
+ BdApi.showConfirmationModal('Library plugin is needed',
+ [`The library plugin needed for ${config.info.name} is missing. Please click Download Now to install it.`], {
+ confirmText: 'Download',
+ cancelText: 'Cancel',
+ onConfirm: () => {
+ require('request').get('https://rauenzi.github.io/BDPluginLibrary/release/0PluginLibrary.plugin.js', async (error, response, body) => {
+ if (error) return require('electron').shell.openExternal('https://betterdiscord.app/Download?id=9');
+ await new Promise(r => require('fs').writeFile(require('path').join(BdApi.Plugins.folder, '0PluginLibrary.plugin.js'), body, r));
+ window.location.reload();
+ });
+ }
+ });
+ }
+ start() { }
+ stop() { }
+ }
+ : (([Plugin, Api]) => {
+ const plugin = (Plugin, Api) => {
+ const {
+ Patcher,
+ WebpackModules,
+ Toasts,
+ Logger,
+ DiscordModules: {
+ Permissions,
+ DiscordPermissions,
+ UserStore,
+ SelectedChannelStore,
+ ChannelStore,
+ DiscordConstants: {
+ EmojiDisabledReasons,
+ EmojiIntention
+ }
+ }
+ } = Api;
+
+ const Emojis = WebpackModules.findByUniqueProperties(['getDisambiguatedEmojiContext', 'searchWithoutFetchingLatest']);
+ const EmojiParser = WebpackModules.findByUniqueProperties(['parse', 'parsePreprocessor', 'unparse']);
+ const EmojiPicker = WebpackModules.findByUniqueProperties(['useEmojiSelectHandler']);
+ const MessageUtilities = WebpackModules.getByProps("sendMessage");
+ const EmojiFilter = WebpackModules.getByProps('getEmojiUnavailableReason');
+
+ const EmojiPickerListRow = WebpackModules.find(m => m?.default?.displayName == 'EmojiPickerListRow');
+
+ const SIZE_REGEX = /([?&]size=)(\d+)/;
+ const EMOJI_SPLIT_LINK_REGEX = /(https:\/\/cdn\.discordapp\.com\/emojis\/\d+\.(?:png|gif|webp)(?:\?size\=\d+&quality=\w*)?)/
+
+ return class Freemoji extends Plugin {
+ currentUser = null;
+
+ replaceEmoji(text, emoji) {
+ const emojiString = `<${emoji.animated ? "a" : ""}:${emoji.originalName || emoji.name}:${emoji.id}>`;
+ const emojiURL = this.getEmojiUrl(emoji);
+ return text.replace(emojiString, emojiURL + " ");
+ }
+
+ patch() {
+ // make emote pretend locked emoji are unlocked
+ Patcher.after(Emojis, 'searchWithoutFetchingLatest', (_, args, ret) => {
+ ret.unlocked = ret.unlocked.concat(ret.locked);
+ ret.locked.length = [];
+ return ret;
+ });
+
+ // replace emoji with links in messages
+ Patcher.after(EmojiParser, 'parse', (_, args, ret) => {
+ for (const emoji of ret.invalidEmojis) {
+ ret.content = this.replaceEmoji(ret.content, emoji);
+ }
+ ret.invalidEmojis = [];
+
+ for (const emoji of ret.validNonShortcutEmojis) {
+ if (!emoji.available) {
+ ret.content = this.replaceEmoji(ret.content, emoji);
+ }
+ }
+ if (this.settings.external) {
+ for (const emoji of ret.validNonShortcutEmojis) {
+ if (this.getEmojiUnavailableReason(emoji) === EmojiDisabledReasons.DISALLOW_EXTERNAL) {
+ ret.content = this.replaceEmoji(ret.content, emoji);
+ }
+ }
+ }
+ return ret;
+ });
+
+ // override emoji picker to allow selecting emotes
+ Patcher.after(EmojiPicker, 'useEmojiSelectHandler', (_, args, ret) => {
+ const { onSelectEmoji, closePopout, selectedChannel } = args[0];
+ const self = this;
+
+ return function (data, state) {
+ if (state.toggleFavorite) return ret.apply(this, arguments);
+
+ const emoji = data.emoji;
+ const isFinalSelection = state.isFinalSelection;
+
+ if (self.getEmojiUnavailableReason(emoji, selectedChannel) === EmojiDisabledReasons.DISALLOW_EXTERNAL) {
+ if (self.settings.external == 'off') return;
+
+ if (self.settings.external == 'showDialog') {
+ BdApi.showConfirmationModal(
+ "Sending External Emoji",
+ [`It looks like you are trying to send an an External Emoji in a server that would normally allow it. Do you still want to send it?`], {
+ confirmText: "Send External Emoji",
+ cancelText: "Cancel",
+ onConfirm: () => {
+ self.selectEmoji({ emoji, isFinalSelection, onSelectEmoji, selectedChannel, closePopout, disabled: true });
+ }
+ });
+ return;
+ }
+ self.selectEmoji({ emoji, isFinalSelection, onSelectEmoji, closePopout, selectedChannel, disabled: true });
+ } else if (!emoji.available) {
+ self.selectEmoji({ emoji, isFinalSelection, onSelectEmoji, closePopout, selectedChannel, disabled: true });
+ } else {
+ self.selectEmoji({ emoji, isFinalSelection, onSelectEmoji, closePopout, selectedChannel, disabled: data.isDisabled });
+ }
+ }
+ });
+
+ Patcher.after(EmojiFilter, 'getEmojiUnavailableReason', (_, [{ intention, bypassPatch }], ret) => {
+ if (intention !== EmojiIntention.CHAT || bypassPatch || !this.settings.external) return;
+ return ret === EmojiDisabledReasons.DISALLOW_EXTERNAL ? null : ret;
+ });
+
+ Patcher.before(EmojiPickerListRow, 'default', (_, [{ emojiDescriptors }]) => {
+ if (this.settings.removeGrayscale == 'never') return;
+ if (this.settings.removeGrayscale != 'always' && !this.hasEmbedPerms()) return;
+ emojiDescriptors.filter(e => e.isDisabled).forEach(e => { e.isDisabled = false; e.wasDisabled = true; });
+ });
+ Patcher.after(EmojiPickerListRow, 'default', (_, [{ emojiDescriptors }]) => {
+ emojiDescriptors.filter(e => e.wasDisabled).forEach(e => { e.isDisabled = true; delete e.wasDisabled; });
+ });
+
+ BdApi.Plugins.isEnabled("EmoteReplacer") || Patcher.instead(MessageUtilities, 'sendMessage', (thisObj, args, originalFn) => {
+ if (!this.settings.split || BdApi.Plugins.isEnabled("EmoteReplacer")) return originalFn.apply(thisObj, args);
+ const [channel, message] = args;
+ const split = message.content.split(EMOJI_SPLIT_LINK_REGEX).map(s => s.trim()).filter(s => s.length);
+ if (split.length <= 1) return originalFn.apply(thisObj, args);
+
+
+ const promises = [];
+ for (let i = 0; i < split.length; i++) {
+ const text = split[i];
+ promises.push(new Promise((resolve, reject) => {
+ window.setTimeout(() => {
+ originalFn.call(thisObj, channel, { content: text, validNonShortcutEmojis: [] }).then(resolve).catch(reject);
+ }, i * 100);
+ }));
+ }
+ return Promise.all(promises).then(ret => ret[ret.length - 1]);
+ });
+ }
+
+ selectEmoji({ emoji, isFinalSelection, onSelectEmoji, closePopout, selectedChannel, disabled }) {
+ if (disabled) {
+ const perms = this.hasEmbedPerms(selectedChannel);
+ if (!perms && this.settings.missingEmbedPerms == 'nothing') return;
+ if (!perms && this.settings.missingEmbedPerms == 'showDialog') {
+ BdApi.showConfirmationModal(
+ "Missing Image Embed Permissions",
+ [`It looks like you are trying to send an Emoji using Freemoji but you dont have the permissions to send embeded images in this channel. You can choose to send it anyway but it will only show as a link.`], {
+ confirmText: "Send Anyway",
+ cancelText: "Cancel",
+ onConfirm: () => {
+ if (this.settings.sendDirectly) {
+ MessageUtilities.sendMessage(selectedChannel.id, { content: this.getEmojiUrl(emoji) });
+ } else {
+ onSelectEmoji(emoji, isFinalSelection);
+ }
+ }
+ });
+ return;
+ }
+ if (this.settings.sendDirectly) {
+ MessageUtilities.sendMessage(SelectedChannelStore.getChannelId(), { content: this.getEmojiUrl(emoji) });
+ } else {
+ onSelectEmoji(emoji, isFinalSelection);
+ }
+ } else {
+ onSelectEmoji(emoji, isFinalSelection);
+ }
+
+ if (isFinalSelection) closePopout();
+ }
+
+ getEmojiUnavailableReason(emoji, channel, intention) {
+ return EmojiFilter.getEmojiUnavailableReason({
+ channel: channel || ChannelStore.getChannel(SelectedChannelStore.getChannelId()),
+ emoji,
+ intention: EmojiIntention.CHAT || intention,
+ bypassPatch: true
+ })
+ }
+
+ getEmojiUrl(emoji) {
+ return emoji.url.includes("size=") ?
+ emoji.url.replace(SIZE_REGEX, `$1${this.settings.emojiSize}`) :
+ `${emoji.url}&size=${this.settings.emojiSize}`;
+ }
+
+ hasEmbedPerms(channelParam) {
+ try {
+ if (!this.currentUser) this.currentUser = UserStore.getCurrentUser();
+ const channel = channelParam || ChannelStore.getChannel(SelectedChannelStore.getChannelId());
+ if (!channel.guild_id) return true;
+ return Permissions.can({permission: DiscordPermissions.EMBED_LINKS, user: this.currentUser.id, context: channel});
+ } catch (e) {
+ Logger.error("Error while detecting embed permissions", e);
+ return true;
+ }
+ }
+
+ cleanup() {
+ Patcher.unpatchAll();
+ }
+
+ onStart() {
+ try {
+ this.patch();
+ } catch (e) {
+ Toasts.error(`${config.info.name}: An error occured during intialiation: ${e}`);
+ Logger.error(`Error while patching: ${e}`);
+ console.error(e);
+ }
+ }
+
+ onStop() {
+ this.cleanup();
+ }
+
+ getSettingsPanel() { return this.buildSettingsPanel().getElement(); }
+ };
+ };
+ return plugin(Plugin, Api);
+ })(global.ZeresPluginLibrary.buildPlugin(config));
+})();
+/*@end@*/
diff --git a/.config/BetterDiscord/plugins/ImageUtilities.config.json b/.config/BetterDiscord/plugins/ImageUtilities.config.json
new file mode 100755
index 0000000..1581d7a
--- /dev/null
+++ b/.config/BetterDiscord/plugins/ImageUtilities.config.json
@@ -0,0 +1,59 @@
+{
+ "all": {
+ "amounts": {
+ "hoverDelay": 0
+ },
+ "detailsSettings": {
+ "footnote": true,
+ "tooltip": false,
+ "tooltipDelay": 0
+ },
+ "engines": {
+ "_all": true,
+ "Baidu": true,
+ "Bing": true,
+ "Google": true,
+ "ImgOps": true,
+ "IQDB": true,
+ "Reddit": true,
+ "SauceNAO": true,
+ "Sogou": true,
+ "TinEye": true,
+ "WhatAnime": true,
+ "Yandex": true
+ },
+ "general": {
+ "nsfwMode": true
+ },
+ "places": {
+ "userAvatars": true,
+ "groupIcons": true,
+ "guildIcons": true,
+ "emojis": true
+ },
+ "rescaleSettings": {
+ "messages": "NONE",
+ "imageViewer": "NONE"
+ },
+ "resizeSettings": {
+ "messages": false,
+ "imageViewer": false
+ },
+ "scaleSettings": {
+ "messages": "NONE",
+ "imageViewer": "NONE"
+ },
+ "viewerSettings": {
+ "zoomMode": true,
+ "galleryMode": true,
+ "details": true,
+ "copyImage": true,
+ "saveImage": true
+ },
+ "zoomSettings": {
+ "lensSize": 1459,
+ "pixelMode": false,
+ "zoomLevel": 1.8000000000000016
+ }
+ }
+}
\ No newline at end of file
diff --git a/.config/BetterDiscord/plugins/ImageUtilities.plugin.js b/.config/BetterDiscord/plugins/ImageUtilities.plugin.js
new file mode 100755
index 0000000..2d69237
--- /dev/null
+++ b/.config/BetterDiscord/plugins/ImageUtilities.plugin.js
@@ -0,0 +1,2005 @@
+/**
+ * @name ImageUtilities
+ * @author DevilBro
+ * @authorId 278543574059057154
+ * @version 4.8.2
+ * @description Adds several Utilities for Images/Videos (Gallery, Download, Reverse Search, Zoom, Copy, etc.)
+ * @invite Jx3TjNS
+ * @donate https://www.paypal.me/MircoWittrien
+ * @patreon https://www.patreon.com/MircoWittrien
+ * @website https://mwittrien.github.io/
+ * @source https://github.com/mwittrien/BetterDiscordAddons/tree/master/Plugins/ImageUtilities/
+ * @updateUrl https://mwittrien.github.io/BetterDiscordAddons/Plugins/ImageUtilities/ImageUtilities.plugin.js
+ */
+
+module.exports = (_ => {
+ const config = {
+ "info": {
+ "name": "ImageUtilities",
+ "author": "DevilBro",
+ "version": "4.8.2",
+ "description": "Adds several Utilities for Images/Videos (Gallery, Download, Reverse Search, Zoom, Copy, etc.)"
+ }
+ };
+
+ return !window.BDFDB_Global || (!window.BDFDB_Global.loaded && !window.BDFDB_Global.started) ? class {
+ getName () {return config.info.name;}
+ getAuthor () {return config.info.author;}
+ getVersion () {return config.info.version;}
+ getDescription () {return `The Library Plugin needed for ${config.info.name} is missing. Open the Plugin Settings to download it. \n\n${config.info.description}`;}
+
+ downloadLibrary () {
+ require("request").get("https://mwittrien.github.io/BetterDiscordAddons/Library/0BDFDB.plugin.js", (e, r, b) => {
+ if (!e && b && r.statusCode == 200) require("fs").writeFile(require("path").join(BdApi.Plugins.folder, "0BDFDB.plugin.js"), b, _ => BdApi.showToast("Finished downloading BDFDB Library", {type: "success"}));
+ else BdApi.alert("Error", "Could not download BDFDB Library Plugin. Try again later or download it manually from GitHub: https://mwittrien.github.io/downloader/?library");
+ });
+ }
+
+ load () {
+ if (!window.BDFDB_Global || !Array.isArray(window.BDFDB_Global.pluginQueue)) window.BDFDB_Global = Object.assign({}, window.BDFDB_Global, {pluginQueue: []});
+ if (!window.BDFDB_Global.downloadModal) {
+ window.BDFDB_Global.downloadModal = true;
+ BdApi.showConfirmationModal("Library Missing", `The Library Plugin needed for ${config.info.name} is missing. Please click "Download Now" to install it.`, {
+ confirmText: "Download Now",
+ cancelText: "Cancel",
+ onCancel: _ => {delete window.BDFDB_Global.downloadModal;},
+ onConfirm: _ => {
+ delete window.BDFDB_Global.downloadModal;
+ this.downloadLibrary();
+ }
+ });
+ }
+ if (!window.BDFDB_Global.pluginQueue.includes(config.info.name)) window.BDFDB_Global.pluginQueue.push(config.info.name);
+ }
+ start () {this.load();}
+ stop () {}
+ getSettingsPanel () {
+ let template = document.createElement("template");
+ template.innerHTML = `The Library Plugin needed for ${config.info.name} is missing.\nPlease click
Download Now to install it.
`;
+ template.content.firstElementChild.querySelector("a").addEventListener("click", this.downloadLibrary);
+ return template.content.firstElementChild;
+ }
+ } : (([Plugin, BDFDB]) => {
+ var _this;
+ var firedEvents = [];
+ var ownLocations = {}, downloadsFolder;
+
+ var firstViewedImage, viewedImage, viewedImageTimeout;
+ var cachedImages;
+ var eventTypes = {};
+
+ const imgUrlReplaceString = "DEVILBRO_BD_REVERSEIMAGESEARCH_REPLACE_IMAGEURL";
+
+ const rescaleOptions = {
+ NONE: "No Resize",
+ ORIGINAL: "Resize to Original Size",
+ WINDOW: "Resize to Window Size"
+ };
+
+ const fileTypes = {
+ "3gp": {copyable: false, searchable: false, video: true},
+ "3g2": {copyable: false, searchable: false, video: true},
+ "amv": {copyable: false, searchable: false, video: true},
+ "apng": {copyable: false, searchable: true, video: false},
+ "avi": {copyable: false, searchable: false, video: true},
+ "flv": {copyable: false, searchable: false, video: true},
+ "jpeg": {copyable: true, searchable: true, video: false},
+ "jpg": {copyable: true, searchable: true, video: false},
+ "gif": {copyable: false, searchable: true, video: false},
+ "m4v": {copyable: false, searchable: false, video: true},
+ "mkv": {copyable: false, searchable: false, video: true},
+ "mov": {copyable: false, searchable: false, video: true},
+ "mp4": {copyable: false, searchable: false, video: true},
+ "mpeg-1": {copyable: false, searchable: false, video: true},
+ "mpeg-2": {copyable: false, searchable: false, video: true},
+ "ogg": {copyable: false, searchable: false, video: true},
+ "ogv": {copyable: false, searchable: false, video: true},
+ "png": {copyable: true, searchable: true, video: false},
+ "svg": {copyable: false, searchable: false, video: false},
+ "webm": {copyable: false, searchable: false, video: true},
+ "webp": {copyable: false, searchable: true, video: false},
+ "wmv": {copyable: false, searchable: false, video: true}
+ };
+
+ const LazyImageSiblingComponent = class LazyImageSibling extends BdApi.React.Component {
+ render() {
+ if (!this.props.loadedImage) {
+ const instace = this;
+ const imageThrowaway = document.createElement("img");
+ imageThrowaway.addEventListener("load", function() {
+ let aRects = BDFDB.DOMUtils.getRects(document.querySelector(BDFDB.dotCN.appmount));
+ let resizeX = (aRects.width/this.width) * 0.8, resizeY = (aRects.height/this.height) * 0.65
+ let ratio = resizeX < resizeY ? resizeX : resizeY;
+ instace.props.loadedImage = BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.LazyImage, {
+ src: imageThrowaway.src,
+ width: this.width,
+ height: this.height,
+ maxWidth: this.width * ratio,
+ maxHeight: this.height * ratio
+ });
+ BDFDB.ReactUtils.forceUpdate(instace);
+ });
+ imageThrowaway.src = this.props.url;
+ }
+ return BDFDB.ReactUtils.createElement("div", {
+ className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN._imageutilitiessibling, this.props.className),
+ onClick: event => {
+ BDFDB.ListenerUtils.stopEvent(event);
+ _this.switchImages(this.props.modalInstance, this.props.offset);
+ },
+ children: [
+ this.props.loadedImage || BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Spinner, {
+ type: BDFDB.LibraryComponents.Spinner.Type.SPINNING_CIRCLE
+ }),
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, {
+ className: BDFDB.disCNS._imageutilitiesswitchicon + BDFDB.disCN.svgicon,
+ name: this.props.svgIcon
+ })
+ ]
+ });
+ }
+ };
+
+ const ImageDetailsComponent = class ImageDetails extends BdApi.React.Component {
+ componentDidMount() {
+ this.props.attachment = BDFDB.ReactUtils.findValue(BDFDB.ObjectUtils.get(this, `${BDFDB.ReactUtils.instanceKey}.return`), "attachment", {up: true});
+ BDFDB.ReactUtils.forceUpdate(this);
+ }
+ componentDidUpdate() {
+ if ((!this.props.attachment || !this.props.attachment.size) && !this.props.loaded) {
+ this.props.loaded = true;
+ this.props.attachment = BDFDB.ReactUtils.findValue(BDFDB.ObjectUtils.get(this, `${BDFDB.ReactUtils.instanceKey}.return`), "attachment", {up: true});
+ BDFDB.ReactUtils.forceUpdate(this);
+ }
+ }
+ render() {
+ return !this.props.attachment ? null : BDFDB.ReactUtils.createElement("span", {
+ className: BDFDB.disCN._imageutilitiesimagedetails,
+ children: [
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Anchor, {
+ title: this.props.original,
+ href: this.props.original,
+ children: this.props.attachment.filename,
+ onClick: event => {
+ BDFDB.ListenerUtils.stopEvent(event);
+ BDFDB.DiscordUtils.openLink(this.props.original);
+ }
+ }),
+ BDFDB.ReactUtils.createElement("span", {
+ children: BDFDB.NumberUtils.formatBytes(this.props.attachment.size)
+ }),
+ BDFDB.ReactUtils.createElement("span", {
+ children: `${this.props.attachment.width}x${this.props.attachment.height}px`
+ }),
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TooltipContainer, {
+ text: BDFDB.LanguageUtils.LibraryStrings.download,
+ children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, {
+ className: BDFDB.disCN.cursorpointer,
+ name: BDFDB.LibraryComponents.SvgIcon.Names.DOWNLOAD,
+ width: 16,
+ height: 16,
+ onClick: event => {
+ BDFDB.ListenerUtils.stopEvent(event);
+ _this.downloadFileAs(this.props.attachment.proxy_url || this.props.original);
+ },
+ onContextMenu: event => {
+ let locations = Object.keys(ownLocations).filter(n => ownLocations[n].enabled);
+ if (locations.length) BDFDB.ContextMenuUtils.open(_this, event, BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuGroup, {
+ children: locations.map((name, i) => BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, {
+ id: BDFDB.ContextMenuUtils.createItemId(_this.name, "download", name, i),
+ label: name,
+ action: _ => _this.downloadFile(this.props.attachment.proxy_url || this.props.original, ownLocations[name].location)
+ }))
+ }));
+ }
+ })
+ })
+ ]
+ });
+ }
+ };
+
+ return class ImageUtilities extends Plugin {
+ onLoad () {
+ _this = this;
+ firedEvents = [];
+ firstViewedImage = null;
+ viewedImage = null;
+ cachedImages = null;
+
+ this.defaults = {
+ general: {
+ nsfwMode: {value: false, description: "Blur Media that is posted in NSFW Channels"}
+ },
+ viewerSettings: {
+ zoomMode: {value: true, description: "Enable Zoom Mode to zoom into Images while holding down your Mouse"},
+ galleryMode: {value: true, description: "Enable Gallery Mode to quick-switch between Images"},
+ details: {value: true, description: "Add Image Details (Name, Size, Amount)"},
+ copyImage: {value: true, description: "Add a 'Copy Image' Option"},
+ saveImage: {value: true, description: "Add a 'Save Image as' Option"}
+ },
+ zoomSettings: {
+ pixelMode: {value: false, label: "Use Pixel Lens instead of a Blur Lens"},
+ zoomLevel: {value: 2, digits: 1, minValue: 1, maxValue: 20, unit: "x", label: "ACCESSIBILITY_ZOOM_LEVEL_LABEL"},
+ lensSize: {value: 200, digits: 0, minValue: 50, maxValue: 5000, unit: "px", label: "context_lenssize"}
+ },
+ rescaleSettings: {
+ messages: {value: "NONE", description: "Messages"},
+ imageViewer: {value: "NONE", description: "Image Viewer"}
+ },
+ detailsSettings: {
+ footnote: {value: true, description: "in the Image Description"},
+ tooltip: {value: false, description: "as a Hover Tooltip"},
+ tooltipDelay: {value: 0, min: 0, description: "Image Tooltip Delay (in ms)"}
+ },
+ places: {
+ userAvatars: {value: true, description: "User Avatars"},
+ groupIcons: {value: true, description: "Group Icons"},
+ guildIcons: {value: true, description: "Server Icons"},
+ emojis: {value: true, description: "Custom Emojis/Emotes"}
+ },
+ engines: {
+ _all: {value: true, name: BDFDB.LanguageUtils.LanguageStrings.FORM_LABEL_ALL, url: null},
+ Baidu: {value: true, name: "Baidu", url: "http://image.baidu.com/pcdutu?queryImageUrl=" + imgUrlReplaceString},
+ Bing: {value: true, name: "Bing", url: "https://www.bing.com/images/search?q=imgurl: " + imgUrlReplaceString + "&view=detailv2&iss=sbi&FORM=IRSBIQ"},
+ Google: {value: true, name: "Google", url: "https://images.google.com/searchbyimage?image_url=" + imgUrlReplaceString},
+ ImgOps: {value: true, name: "ImgOps", raw: true, url: "https://imgops.com/specialized+reverse/" + imgUrlReplaceString},
+ IQDB: {value: true, name: "IQDB", url: "https://iqdb.org/?url=" + imgUrlReplaceString},
+ Reddit: {value: true, name: "Reddit", url: "http://karmadecay.com/search?q=" + imgUrlReplaceString},
+ SauceNAO: {value: true, name: "SauceNAO", url: "https://saucenao.com/search.php?db=999&url=" + imgUrlReplaceString},
+ Sogou: {value: true, name: "Sogou", url: "http://pic.sogou.com/ris?flag=1&drag=0&query=" + imgUrlReplaceString + "&flag=1"},
+ TinEye: {value: true, name: "TinEye", url: "https://tineye.com/search?url=" + imgUrlReplaceString},
+ WhatAnime: {value: true, name: "WhatAnime", url: "https://trace.moe/?url=" + imgUrlReplaceString},
+ Yandex: {value: true, name: "Yandex", url: "https://yandex.com/images/search?url=" + imgUrlReplaceString + "&rpt=imageview"}
+ }
+ };
+
+ this.patchedModules = {
+ before: {
+ LazyImage: "render",
+ Spoiler: "render",
+ SimpleMessageAccessories: "default"
+ },
+ after: {
+ ImageModal: ["render", "componentDidMount", "componentWillUnmount"],
+ ModalCarousel: "render",
+ LazyImage: ["componentDidMount", "componentDidUpdate"],
+ LazyImageZoomable: "render",
+ Spoiler: "render",
+ UserBanner: "default"
+ }
+ };
+
+ this.css = `
+ ${BDFDB.dotCN._imageutilitiesimagedetails} {
+ display: inline-flex;
+ }
+ ${BDFDB.dotCNS.spoilerhidden + BDFDB.dotCN._imageutilitiesimagedetails} {
+ visibility: hidden;
+ }
+ span + ${BDFDB.dotCN._imageutilitiesimagedetails} {
+ margin-left: 12px;
+ }
+ ${BDFDB.dotCN._imageutilitiesimagedetails} > * {
+ display: inline-block;
+ margin-right: 12px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+ ${BDFDB.dotCN._imageutilitiesimagedetails} > a {
+ max-width: 300px;
+ }
+ span + ${BDFDB.dotCN._imageutilitiesimagedetails} > a {
+ max-width: 200px;
+ }
+ ${BDFDB.dotCN._imageutilitiesimagedetails} > span {
+ max-width: 100px;
+ }
+ ${BDFDB.dotCN._imageutilitiesgallery},
+ ${BDFDB.dotCN._imageutilitiesdetailsadded} {
+ transform: unset !important;
+ filter: unset !important;
+ backdrop-filter: unset !important;
+ }
+ ${BDFDB.dotCNS.imagemodal + BDFDB.notCN._imageutilitiessibling} > ${BDFDB.dotCN.imagewrapper} {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ min-width: 500px;
+ }
+ ${BDFDB.dotCNS.imagemodal + BDFDB.notCN._imageutilitiessibling} > ${BDFDB.dotCN.imagewrapper} img {
+ object-fit: contain;
+ width: unset;
+ }
+ ${BDFDB.dotCN._imageutilitiessibling} {
+ display: flex;
+ align-items: center;
+ position: fixed;
+ top: 50%;
+ bottom: 50%;
+ cursor: pointer;
+ }
+ ${BDFDB.dotCN._imageutilitiesprevious} {
+ justify-content: flex-end;
+ right: 90%;
+ }
+ ${BDFDB.dotCN._imageutilitiesnext} {
+ justify-content: flex-start;
+ left: 90%;
+ }
+ ${BDFDB.dotCN._imageutilitiesswitchicon} {
+ position: absolute;
+ background: rgba(0, 0, 0, 0.3);
+ border-radius: 50%;
+ padding: 15px;
+ transition: all 0.3s ease;
+ }
+ ${BDFDB.dotCNS._imageutilitiesprevious + BDFDB.dotCN._imageutilitiesswitchicon} {
+ right: 10px;
+ }
+ ${BDFDB.dotCNS._imageutilitiesnext + BDFDB.dotCN._imageutilitiesswitchicon} {
+ left: 10px;
+ }
+ ${BDFDB.dotCNS._imageutilitiessibling + BDFDB.dotCN.spinner} {
+ position: absolute;
+ }
+ ${BDFDB.dotCNS._imageutilitiesprevious + BDFDB.dotCN.spinner} {
+ right: 21px;
+ }
+ ${BDFDB.dotCNS._imageutilitiesnext + BDFDB.dotCN.spinner} {
+ left: 21px;
+ }
+ ${BDFDB.dotCN._imageutilitiessibling}:hover ${BDFDB.dotCN._imageutilitiesswitchicon} {
+ background: rgba(0, 0, 0, 0.5);
+ }
+ ${BDFDB.dotCN._imageutilitiesdetailswrapper} {
+ position: fixed;
+ bottom: 10px;
+ left: 15px;
+ right: 15px;
+ pointer-events: none;
+ }
+ ${BDFDB.dotCN._imageutilitiesdetails} {
+ color: #dcddde;
+ margin-top: 5px;
+ font-size: 14px;
+ font-weight: 500;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+ ${BDFDB.dotCN._imageutilitiesdetailslabel} {
+ display: inline-block;
+ width: 80px;
+ font-weight: 600;
+ }
+ ${BDFDB.dotCN._imageutilitieslense} {
+ border: 2px solid var(--bdfdb-blurple);
+ }
+ ${BDFDB.dotCN._imageutilitiesoperations} {
+ position: absolute;
+ display: flex;
+ }
+ ${BDFDB.dotCNS._imageutilitiesoperations + BDFDB.dotCN.downloadlink} {
+ position: relative !important;
+ white-space: nowrap !important;
+ }
+ ${BDFDB.dotCNS._imageutilitiesoperations + BDFDB.dotCN.anchor + BDFDB.dotCN.downloadlink} {
+ margin: 0 !important;
+ }
+ `;
+ }
+
+ onStart () {
+ BDFDB.ListenerUtils.add(this, document.body, "click", BDFDB.dotCNS.message + BDFDB.dotCNS.imagewrapper + BDFDB.dotCNC.imageoriginallink + BDFDB.dotCNS.message + BDFDB.dotCNS.imagewrapper + "img", e => this.cacheClickedImage(e.target));
+
+ BDFDB.PatchUtils.patch(this, BDFDB.LibraryModules.MediaComponentUtils, "renderImageComponent", {
+ after: e => {
+ if (this.settings.detailsSettings.footnote && e.methodArguments[0].original && e.methodArguments[0].src.indexOf("https://media.discordapp.net/attachments") == 0 && (e.methodArguments[0].className || "").indexOf(BDFDB.disCN.embedmedia) == -1 && (e.methodArguments[0].className || "").indexOf(BDFDB.disCN.embedthumbnail) == -1 && BDFDB.ReactUtils.findChild(e.returnValue, {name: ["ConnectedLazyImageZoomable", "LazyImageZoomable", "LazyImage"]})) {
+ const altText = e.returnValue.props.children[1] && e.returnValue.props.children[1].props.children;
+ const details = BDFDB.ReactUtils.createElement(ImageDetailsComponent, {
+ original: e.methodArguments[0].original,
+ attachment: {
+ height: 0,
+ width: 0,
+ filename: "unknown.png"
+ }
+ });
+ e.returnValue.props.children[1] = BDFDB.ReactUtils.createElement("span", {
+ className: BDFDB.disCN.imagealttext,
+ children: [
+ altText && altText.length >= 50 && BDFDB.ReactUtils.createElement("div", {
+ children: details
+ }),
+ altText && BDFDB.ReactUtils.createElement("span", {
+ children: altText
+ }),
+ (!altText || altText.length < 50) && details
+ ]
+ });
+ e.returnValue.props.children = BDFDB.ReactUtils.createElement("div", {
+ children: e.returnValue.props.children
+ });
+ }
+ }
+ });
+
+ this.forceUpdateAll();
+ }
+
+ onStop () {
+ this.cleanupListeners("Gallery");
+ this.cleanupListeners("Zoom");
+
+ this.forceUpdateAll();
+ }
+
+ getSettingsPanel (collapseStates = {}) {
+ let settingsPanel;
+
+ return settingsPanel = BDFDB.PluginUtils.createSettingsPanel(this, {
+ collapseStates: collapseStates,
+ children: _ => {
+ let settingsItems = [];
+
+ settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.CollapseContainer, {
+ title: "General",
+ collapseStates: collapseStates,
+ children: Object.keys(this.defaults.general).map(key => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsSaveItem, {
+ type: "Switch",
+ plugin: this,
+ keys: ["general", key],
+ label: this.defaults.general[key].description,
+ value: this.settings.general[key]
+ }))
+ }));
+
+ settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.CollapseContainer, {
+ title: "Image Viewer Settings",
+ collapseStates: collapseStates,
+ children: Object.keys(this.defaults.viewerSettings).map(key => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsSaveItem, {
+ type: "Switch",
+ plugin: this,
+ keys: ["viewerSettings", key],
+ label: this.defaults.viewerSettings[key].description,
+ value: this.settings.viewerSettings[key]
+ }))
+ }));
+
+ settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.CollapseContainer, {
+ title: "Resize Settings",
+ collapseStates: collapseStates,
+ children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsPanelList, {
+ title: "Automatically Resize Images in: ",
+ children: Object.keys(this.defaults.rescaleSettings).map(key => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsSaveItem, {
+ type: "Select",
+ plugin: this,
+ keys: ["rescaleSettings", key],
+ label: this.defaults.rescaleSettings[key].description,
+ basis: "50%",
+ options: Object.keys(rescaleOptions).map(n => ({value: n, label: rescaleOptions[n]})),
+ value: this.settings.rescaleSettings[key]
+ }))
+ })
+ }));
+
+ settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.CollapseContainer, {
+ title: "Image Details Settings",
+ collapseStates: collapseStates,
+ children: [BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsPanelList, {
+ title: "Show Image Details",
+ children: Object.keys(this.defaults.detailsSettings).filter(key => typeof this.defaults.detailsSettings[key].value == "boolean").map(key => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsSaveItem, {
+ type: "Switch",
+ plugin: this,
+ keys: ["detailsSettings", key],
+ label: this.defaults.detailsSettings[key].description,
+ value: this.settings.detailsSettings[key]
+ }))
+ })].concat(Object.keys(this.defaults.detailsSettings).filter(key => typeof this.defaults.detailsSettings[key].value != "boolean").map(key => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsSaveItem, {
+ type: "TextInput",
+ plugin: this,
+ keys: ["detailsSettings", key],
+ label: this.defaults.detailsSettings[key].description,
+ value: this.settings.detailsSettings[key],
+ basis: "50%",
+ childProps: {type: "number"},
+ min: this.defaults.detailsSettings[key].min,
+ max: this.defaults.detailsSettings[key].max,
+ })))
+ }));
+
+ const locationInputs = {name: "", location: ""};
+ settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.CollapseContainer, {
+ title: "Download Locations",
+ collapseStates: collapseStates,
+ children: [
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormTitle, {
+ className: BDFDB.disCN.marginbottom4,
+ tag: BDFDB.LibraryComponents.FormComponents.FormTitle.Tags.H3,
+ children: "Add additional Download Locations"
+ }),
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex, {
+ className: BDFDB.disCN.marginbottom8,
+ align: BDFDB.LibraryComponents.Flex.Align.END,
+ children: [
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, {
+ children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormItem, {
+ title: "Name:",
+ children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TextInput, {
+ value: locationInputs.name,
+ placeholder: "Name",
+ onChange: value => locationInputs.name = value
+ })
+ })
+ }),
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, {
+ children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormItem, {
+ title: "Location:",
+ children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TextInput, {
+ value: locationInputs.location,
+ placeholder: "Location",
+ onChange: value => locationInputs.location = value
+ })
+ })
+ }),
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Button, {
+ style: {marginBottom: 1},
+ onClick: _ => {
+ for (let key in locationInputs) if (!locationInputs[key] || !locationInputs[key].trim()) return BDFDB.NotificationUtils.toast("Fill out all fields to add a new Location", {type: "danger"});
+ let name = locationInputs.name.trim();
+ let location = locationInputs.location.trim();
+ if (ownLocations[name] || name == "Downloads") return BDFDB.NotificationUtils.toast("A Location with the choosen Name already exists, please choose another Name", {type: "danger"});
+ else if (!BDFDB.LibraryRequires.fs.existsSync(location)) return BDFDB.NotificationUtils.toast("The choosen download Location is not a valid Path to a Folder", {type: "danger"});
+ else {
+ ownLocations[name] = {enabled: true, location: location};
+ BDFDB.DataUtils.save(ownLocations, this, "ownLocations");
+ BDFDB.PluginUtils.refreshSettingsPanel(this, settingsPanel, collapseStates);
+ }
+ },
+ children: BDFDB.LanguageUtils.LanguageStrings.ADD
+ })
+ ]
+ })
+ ].concat(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsPanelList, {
+ title: "Your own Download Locations",
+ dividerTop: true,
+ children: Object.keys(ownLocations).map(name => {
+ let locationName = name;
+ let editable = name != "Downloads";
+ return BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Card, {
+ horizontal: true,
+ children: [
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, {
+ grow: 0,
+ basis: "180px",
+ children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TextInput, {
+ value: locationName,
+ placeholder: locationName,
+ size: BDFDB.LibraryComponents.TextInput.Sizes.MINI,
+ maxLength: 100000000000000000000,
+ disabled: !editable,
+ onChange: !editable ? null : value => {
+ ownLocations[value] = ownLocations[locationName];
+ delete ownLocations[locationName];
+ locationName = value;
+ BDFDB.DataUtils.save(ownLocations, this, "ownLocations");
+ }
+ })
+ }),
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, {
+ children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TextInput, {
+ value: ownLocations[locationName].location,
+ placeholder: ownLocations[locationName].location,
+ size: BDFDB.LibraryComponents.TextInput.Sizes.MINI,
+ maxLength: 100000000000000000000,
+ onChange: value => {
+ ownLocations[locationName].location = value;
+ BDFDB.DataUtils.save(ownLocations, this, "ownLocations");
+ }
+ })
+ }),
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, {
+ children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Switch, {
+ value: ownLocations[locationName].enabled,
+ size: BDFDB.LibraryComponents.Switch.Sizes.MINI,
+ onChange: value => {
+ ownLocations[locationName].enabled = value;
+ BDFDB.DataUtils.save(ownLocations, this, "ownLocations");
+ }
+ })
+ })
+ ],
+ noRemove: !editable,
+ onRemove: !editable ? null : _ => {
+ delete ownLocations[locationName];
+ BDFDB.DataUtils.save(ownLocations, this, "ownLocations");
+ BDFDB.PluginUtils.refreshSettingsPanel(this, settingsPanel);
+ }
+ });
+ })
+ })).filter(n => n)
+ }));
+
+ settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.CollapseContainer, {
+ title: "Context Menu Settings",
+ collapseStates: collapseStates,
+ children: [
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsPanelList, {
+ title: "Add additional Context Menu Entry for",
+ children: Object.keys(this.defaults.places).map(key => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsSaveItem, {
+ type: "Switch",
+ plugin: this,
+ keys: ["places", key],
+ label: this.defaults.places[key].description,
+ value: this.settings.places[key]
+ }))
+ }),
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsPanelList, {
+ title: "Reverse Image Search Engines",
+ children: Object.keys(this.defaults.engines).filter(key => key != "_all").map(key => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsSaveItem, {
+ type: "Switch",
+ plugin: this,
+ keys: ["engines", key],
+ label: this.defaults.engines[key].name,
+ value: this.settings.engines[key]
+ }))
+ })
+ ]
+ }));
+
+ return settingsItems;
+ }
+ });
+ }
+
+ onSettingsClosed () {
+ if (this.SettingsUpdated) {
+ delete this.SettingsUpdated;
+ this.forceUpdateAll();
+ }
+ }
+
+ forceUpdateAll () {
+ const loadedLocations = BDFDB.DataUtils.load(this, "ownLocations");
+ ownLocations = Object.assign(!loadedLocations || !loadedLocations.Downloads ? {"Downloads": {enabled:true, location: this.getDownloadLocation()}} : {}, loadedLocations);
+
+ BDFDB.PatchUtils.forceAllUpdates(this);
+ BDFDB.MessageUtils.rerenderAll();
+ }
+
+ onGuildContextMenu (e) {
+ if (e.instance.props.guild && this.settings.places.guildIcons) {
+ if (BDFDB.DOMUtils.getParent(BDFDB.dotCN.guildheader, e.instance.props.target) || BDFDB.DOMUtils.getParent(BDFDB.dotCN.guildchannels, e.instance.props.target) && !e.instance.props.target.className && e.instance.props.target.parentElement.firstElementChild == e.instance.props.target) {
+ let banner = BDFDB.GuildUtils.getBanner(e.instance.props.guild.id);
+ if (banner) this.injectItem(e, [banner.replace(/\.webp|\.gif/, ".png"), e.instance.props.guild.banner && BDFDB.LibraryModules.IconUtils.isAnimatedIconHash(e.instance.props.guild.banner), banner], BDFDB.LanguageUtils.LibraryStrings.guildbanner);
+ }
+ else if (e.type != "GuildChannelListContextMenu") this.injectItem(e, [(e.instance.props.guild.getIconURL(4096) || "").replace(/\.webp|\.gif/, ".png"), e.instance.props.guild.icon && BDFDB.LibraryModules.IconUtils.isAnimatedIconHash(e.instance.props.guild.icon) && e.instance.props.guild.getIconURL(4096, true)], BDFDB.LanguageUtils.LibraryStrings.guildicon);
+ }
+ }
+
+ onUserContextMenu (e) {
+ if (e.instance.props.user && this.settings.places.userAvatars && e.subType == "useBlockUserItem") {
+ const guildId = BDFDB.LibraryModules.LastGuildStore.getGuildId();
+ const member = BDFDB.LibraryModules.MemberStore.getMember(guildId, e.instance.props.user.id);
+ let validUrls = this.filterUrls((e.instance.props.user.getAvatarURL(null, 4096) || "").replace(/\.webp|\.gif/, ".png"), BDFDB.LibraryModules.IconUtils.isAnimatedIconHash(e.instance.props.user.avatar) && e.instance.props.user.getAvatarURL(null, 4096, true), (e.instance.props.user.getAvatarURL(guildId, 4096) || "").replace(/\.webp|\.gif/, ".png"), member && member.avatar && BDFDB.LibraryModules.IconUtils.isAnimatedIconHash(member.avatar) && e.instance.props.user.getAvatarURL(guildId, 4096, true));
+ if (!validUrls.length) return;
+
+ if (e.returnvalue.length) e.returnvalue.push(BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuSeparator, {}));
+ e.returnvalue.push(BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, {
+ label: this.isValid(validUrls[0].file, "video") ? BDFDB.LanguageUtils.LanguageStrings.VIDEO : BDFDB.LanguageUtils.LanguageStrings.IMAGE + " " + BDFDB.LanguageUtils.LanguageStrings.ACTIONS,
+ id: BDFDB.ContextMenuUtils.createItemId(this.name, "main-subitem"),
+ children: this.createSubMenus({
+ instance: e.instance,
+ urls: validUrls,
+ prefix: BDFDB.LanguageUtils.LanguageStrings.USER_SETTINGS_AVATAR
+ })
+ }));
+ }
+ }
+
+ onGroupDMContextMenu (e) {
+ if (e.instance.props.channel && e.instance.props.channel.isGroupDM() && this.settings.places.groupIcons) this.injectItem(e, );
+ }
+
+ onChannelContextMenu (e) {
+ if (e.instance.props.channel && e.instance.props.channel.isGroupDM() && this.settings.places.groupIcons && e.subType == "useChannelLeaveItem") {
+ let validUrls = this.filterUrls((BDFDB.DMUtils.getIcon(e.instance.props.channel.id) || "").replace(/\.webp|\.gif/, ".png"));
+ if (!validUrls.length) return;
+
+ if (e.returnvalue.length) e.returnvalue.unshift(BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuSeparator, {}));
+ e.returnvalue.unshift(BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, {
+ label: this.isValid(validUrls[0].file, "video") ? BDFDB.LanguageUtils.LanguageStrings.VIDEO : BDFDB.LanguageUtils.LanguageStrings.IMAGE + " " + BDFDB.LanguageUtils.LanguageStrings.ACTIONS,
+ id: BDFDB.ContextMenuUtils.createItemId(this.name, "main-subitem"),
+ children: this.createSubMenus({
+ instance: e.instance,
+ urls: validUrls,
+ prefix: BDFDB.LanguageUtils.LanguageStrings.USER_SETTINGS_AVATAR
+ })
+ }));
+ }
+ }
+
+
+ onNativeContextMenu (e) {
+ if (e.type == "NativeImageContextMenu" && (e.instance.props.href || e.instance.props.src)) this.injectItem(e, [e.instance.props.href || e.instance.props.src]);
+ }
+
+ onMessageContextMenu (e) {
+ if (e.instance.props.message && e.instance.props.channel && e.instance.props.target) {
+ if (e.instance.props.attachment) this.injectItem(e, [e.instance.props.attachment.url]);
+ else {
+ const target = e.instance.props.target.tagName == "A" && BDFDB.DOMUtils.containsClass(e.instance.props.target, BDFDB.disCN.imageoriginallink) && e.instance.props.target.parentElement.querySelector("img, video") || e.instance.props.target;
+ if (target.tagName == "A" && e.instance.props.message.embeds && e.instance.props.message.embeds[0] && (e.instance.props.message.embeds[0].type == "image" || e.instance.props.message.embeds[0].type == "video" || e.instance.props.message.embeds[0].type == "gifv")) this.injectItem(e, [target.href]);
+ else if (target.tagName == "IMG" && target.complete && target.naturalHeight) {
+ if (BDFDB.DOMUtils.getParent(BDFDB.dotCN.imagewrapper, target) || BDFDB.DOMUtils.containsClass(target, BDFDB.disCN.imagesticker)) this.injectItem(e, [{file: target.src, original: this.getTargetLink(e.instance.props.target) || this.getTargetLink(target)}]);
+ else if (BDFDB.DOMUtils.containsClass(target, BDFDB.disCN.embedauthoricon) && this.settings.places.userAvatars) this.injectItem(e, [target.src]);
+ else if (BDFDB.DOMUtils.containsClass(target, BDFDB.disCN.emojiold, "emote", false) && this.settings.places.emojis) this.injectItem(e, [{file: target.src, alternativeName: target.getAttribute("data-name")}]);
+ }
+ else if (target.tagName == "VIDEO") {
+ if (BDFDB.DOMUtils.containsClass(target, BDFDB.disCN.embedvideo) || BDFDB.DOMUtils.getParent(BDFDB.dotCN.attachmentvideo, target)) this.injectItem(e, [{file: target.src, original: this.getTargetLink(e.instance.props.target) || this.getTargetLink(target)}]);
+ }
+ else {
+ const reaction = BDFDB.DOMUtils.getParent(BDFDB.dotCN.messagereaction, target);
+ if (reaction && this.settings.places.emojis) {
+ const emoji = reaction.querySelector(BDFDB.dotCN.emojiold);
+ if (emoji) this.injectItem(e, [{file: emoji.src, alternativeName: emoji.getAttribute("data-name")}]);
+ }
+ }
+ }
+ }
+ }
+
+ getTargetLink (target) {
+ let ele = target;
+ let src = "", href = "";
+ while (ele instanceof Node) ele instanceof HTMLImageElement && null != ele.src && (src = ele.src), ele instanceof HTMLAnchorElement && null != ele.href && (href = ele.href), ele = ele.parentNode;
+ return href || src;
+ }
+
+ injectItem (e, urls, prefix) {
+ let validUrls = this.filterUrls(...urls);
+ if (!validUrls.length) return;
+ let [removeParent, removeIndex] = BDFDB.ContextMenuUtils.findItem(e.returnvalue, {id: "copy-native-link", group: true});
+ if (removeIndex > -1) {
+ removeParent.splice(removeIndex, 1);
+ removeIndex -= 1;
+ }
+ let [removeParent2, removeIndex2] = BDFDB.ContextMenuUtils.findItem(e.returnvalue, {id: "copy-image", group: true});
+ if (removeIndex2 > -1) removeParent2.splice(removeIndex2, 1);
+
+ let type = this.isValid(validUrls[0].file, "video") ? BDFDB.LanguageUtils.LanguageStrings.VIDEO : BDFDB.LanguageUtils.LanguageStrings.IMAGE;
+ let isNative = validUrls.length == 1 && removeIndex > -1;
+ let subMenu = this.createSubMenus({
+ instance: e.instance,
+ urls: validUrls,
+ prefix: prefix,
+ target: e.instance.props.target
+ });
+
+ let [children, index] = isNative ? [removeParent, removeIndex] : BDFDB.ContextMenuUtils.findItem(e.returnvalue, {id: "devmode-copy-id", group: true});
+ children.splice(index > -1 ? index : children.length, 0, isNative ? subMenu : BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuGroup, {
+ children: BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, {
+ label: type + " " + BDFDB.LanguageUtils.LanguageStrings.ACTIONS,
+ id: BDFDB.ContextMenuUtils.createItemId(this.name, "main-subitem"),
+ children: subMenu
+ })
+ }));
+ }
+
+ filterUrls (...urls) {
+ let addedUrls = [];
+ return urls.filter(n => this.isValid(n && n.file || n)).map(n => {
+ let srcUrl = (n.file || n).replace(/^url\(|\)$|"|'/g, "").replace(/\?size\=\d+$/, "?size=4096").replace(/\?size\=\d+&/, "?size=4096&").replace(/[\?\&](height|width)=\d+/g, "").split("%3A")[0];
+ if (srcUrl.startsWith("https://cdn.discordapp.com/") && !srcUrl.endsWith("?size=4096") && srcUrl.indexOf("?size=4096&") == -1) srcUrl += "?size=4096";
+ let originalUrl = (n.original || n.file || n).replace(/^url\(|\)$|"|'/g, "").replace(/\?size\=\d+$/, "?size=4096").replace(/\?size\=\d+&/, "?size=4096&").replace(/[\?\&](height|width)=\d+/g, "").split("%3A")[0];
+ if (originalUrl.startsWith("https://cdn.discordapp.com/") && !originalUrl.endsWith("?size=4096") && originalUrl.indexOf("?size=4096&") == -1) originalUrl += "?size=4096";
+ let fileUrl = srcUrl;
+ if (fileUrl.indexOf("https://images-ext-1.discordapp.net/external/") > -1 || fileUrl.indexOf("https://images-ext-2.discordapp.net/external/") > -1) {
+ if (fileUrl.split("/https/").length > 1) fileUrl = "https://" + fileUrl.split("/https/").pop();
+ else if (url.split("/http/").length > 1) fileUrl = "http://" + fileUrl.split("/http/").pop();
+ }
+ const file = fileUrl && (BDFDB.LibraryModules.URLParser.parse(fileUrl).pathname || "").toLowerCase();
+ const fileType = file && (file.split(".").pop() || "");
+ return fileUrl && fileType && !addedUrls.includes(srcUrl) && addedUrls.push(srcUrl) && {file: fileUrl, src: srcUrl, original: originalUrl, isGuildSpecific: /^https:\/\/cdn\.discordapp\.com\/guilds\/\d+\/users\/\d+/.test(srcUrl), fileType, alternativeName: escape((n.alternativeName || "").replace(/:/g, ""))};
+ }).filter(n => n);
+ }
+
+ isValid (url, type) {
+ if (!url) return false;
+ const file = url && (BDFDB.LibraryModules.URLParser.parse(url).pathname || "").split("%3A")[0].toLowerCase();
+ return file && (!type && (url.indexOf("discord.com/streams/guild:") > -1 || url.indexOf("discordapp.com/streams/guild:") > -1 || url.indexOf("discordapp.net/streams/guild:") > -1 || url.startsWith("https://images-ext-1.discordapp.net/") || url.startsWith("https://images-ext-2.discordapp.net/") || Object.keys(fileTypes).some(t => file.endsWith(`/${t}`) || file.endsWith(`.${t}`))) || type && Object.keys(fileTypes).filter(t => fileTypes[t][type]).some(t => file.endsWith(`/${t}`) || file.endsWith(`.${t}`)));
+ }
+
+ createSubMenus (data) {
+ return data.urls.length == 1 ? this.createUrlMenu(data.instance, data.urls[0], data.target) : data.urls.map((urlData, i) => BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, {
+ label: [urlData.isGuildSpecific && BDFDB.LanguageUtils.LanguageStrings.CHANGE_IDENTITY_SERVER_PROFILE, data.prefix, urlData.fileType.toUpperCase()].filter(n => n).join(" "),
+ id: BDFDB.ContextMenuUtils.createItemId(this.name, "subitem", i),
+ children: this.createUrlMenu(data.instance, urlData, data.target)
+ }));
+ }
+
+ createUrlMenu (instance, urlData, target) {
+ let enabledEngines = BDFDB.ObjectUtils.filter(this.settings.engines, n => n);
+ let enginesWithoutAll = BDFDB.ObjectUtils.filter(enabledEngines, n => n != "_all", true);
+ let engineKeys = Object.keys(enginesWithoutAll);
+ let locations = Object.keys(ownLocations).filter(n => ownLocations[n].enabled);
+ let isVideo = this.isValid(urlData.file, "video");
+ let type = isVideo ? BDFDB.LanguageUtils.LanguageStrings.VIDEO : BDFDB.LanguageUtils.LanguageStrings.IMAGE;
+ return BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuGroup, {
+ children: [
+ BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, {
+ label: BDFDB.LanguageUtils.LanguageStrings.COPY_LINK,
+ id: BDFDB.ContextMenuUtils.createItemId(this.name, "copy-link"),
+ action: _ => {
+ BDFDB.LibraryRequires.electron.clipboard.write({text: urlData.original});
+ BDFDB.NotificationUtils.toast(BDFDB.LanguageUtils.LanguageStrings.LINK_COPIED, {type: "success"});
+ }
+ }),
+ urlData.file != urlData.original && BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, {
+ label: BDFDB.LanguageUtils.LanguageStrings.COPY_MEDIA_LINK,
+ id: BDFDB.ContextMenuUtils.createItemId(this.name, "copy-media-link"),
+ action: _ => {
+ BDFDB.LibraryRequires.electron.clipboard.write({text: urlData.file});
+ BDFDB.NotificationUtils.toast(BDFDB.LanguageUtils.LanguageStrings.LINK_COPIED, {type: "success"});
+ }
+ }),
+ BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, {
+ label: BDFDB.LanguageUtils.LanguageStrings.OPEN_LINK,
+ id: BDFDB.ContextMenuUtils.createItemId(this.name, "open-link"),
+ action: _ => BDFDB.DiscordUtils.openLink(urlData.original)
+ }),
+ !this.isValid(urlData.file, "copyable") ? null : BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, {
+ label: this.labels.context_copy.replace("{{var0}}", type),
+ id: BDFDB.ContextMenuUtils.createItemId(this.name, "copy-file"),
+ action: _ => this.copyFile(urlData.original)
+ }),
+ !document.querySelector(BDFDB.dotCN.imagemodal) && BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, {
+ label: this.labels.context_view.replace("{{var0}}", type),
+ id: BDFDB.ContextMenuUtils.createItemId(this.name, "view-file"),
+ action: _ => {
+ const imageThrowaway = document.createElement(isVideo ? "video" : "img");
+ imageThrowaway.addEventListener(isVideo ? "loadedmetadata" : "load", function() {
+ BDFDB.LibraryModules.ModalUtils.openModal(modalData => {
+ _this.cacheClickedImage(target);
+ return BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.ModalComponents.ModalRoot, Object.assign({
+ className: BDFDB.disCN.imagemodal
+ }, modalData, {
+ size: BDFDB.LibraryComponents.ModalComponents.ModalSize.DYNAMIC,
+ "aria-label": BDFDB.LanguageUtils.LanguageStrings.IMAGE,
+ children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.ImageModal, {
+ animated: !!isVideo,
+ src: imageThrowaway.src,
+ original: urlData.original,
+ width: isVideo ? this.videoWidth : this.width,
+ height: isVideo ? this.videoHeight : this.height,
+ className: BDFDB.disCN.imagemodalimage,
+ shouldAnimate: true,
+ renderLinkComponent: props => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Anchor, props),
+ children: !isVideo ? null : (videoData => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Video, {
+ src: urlData.src || urlData.file,
+ width: videoData.size.width,
+ height: videoData.size.height,
+ naturalWidth: this.videoWidth,
+ naturalHeight: this.videoHeight,
+ play: true
+ }))
+ })
+ }), true);
+ });
+ });
+ imageThrowaway.src = urlData.src || urlData.file;
+ }
+ }),
+ BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, {
+ label: this.labels.context_saveas.replace("{{var0}}", type),
+ id: BDFDB.ContextMenuUtils.createItemId(this.name, "download-file-as"),
+ action: _ => this.downloadFileAs(urlData.original, urlData.src, urlData.alternativeName),
+ children: locations.length && BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuGroup, {
+ children: locations.map((name, i) => BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, {
+ id: BDFDB.ContextMenuUtils.createItemId(this.name, "download", name, i),
+ label: name,
+ action: _ => this.downloadFile(urlData.original, ownLocations[name].location, urlData.src, urlData.alternativeName)
+ }))
+ })
+ }),
+ !this.isValid(urlData.original, "searchable") || !engineKeys.length ? null : engineKeys.length == 1 ? BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, {
+ label: this.labels.context_searchwith.replace("{{var0}}", type).replace("...", this.defaults.engines[engineKeys[0]].name),
+ id: BDFDB.ContextMenuUtils.createItemId(this.name, "single-search"),
+ persisting: true,
+ action: event => {
+ if (!event.shiftKey) BDFDB.ContextMenuUtils.close(instance);
+ BDFDB.DiscordUtils.openLink(this.defaults.engines[engineKeys[0]].url.replace(imgUrlReplaceString, encodeURIComponent(urlData.original)), {
+ minimized: event.shiftKey
+ });
+ }
+ }) : BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, {
+ label: this.labels.context_searchwith.replace("{{var0}}", type),
+ id: BDFDB.ContextMenuUtils.createItemId(this.name, "submenu-search"),
+ children: !engineKeys.length ? BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, {
+ label: this.labels.submenu_disabled,
+ id: BDFDB.ContextMenuUtils.createItemId(this.name, "disabled"),
+ disabled: true
+ }) : Object.keys(enabledEngines).map(key => BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, {
+ label: this.defaults.engines[key].name,
+ id: BDFDB.ContextMenuUtils.createItemId(this.name, "search", key),
+ color: key == "_all" ? BDFDB.LibraryComponents.MenuItems.Colors.DANGER : BDFDB.LibraryComponents.MenuItems.Colors.DEFAULT,
+ persisting: true,
+ action: event => {
+ const open = (url, k) => BDFDB.DiscordUtils.openLink(this.defaults.engines[k].url.replace(imgUrlReplaceString, this.defaults.engines[k].raw ? url : encodeURIComponent(url)), {minimized: event.shiftKey});
+ if (!event.shiftKey) BDFDB.ContextMenuUtils.close(instance);
+ if (key == "_all") {
+ for (let key2 in enginesWithoutAll) open(urlData.original, key2);
+ }
+ else open(urlData.original, key);
+ }
+ }))
+ })
+ ].filter(n => n)
+ });
+ }
+
+ processImageModal (e) {
+ if (e.methodname == "componentDidMount") {
+ BDFDB.TimeUtils.clear(viewedImageTimeout);
+
+ let modal = BDFDB.DOMUtils.getParent(BDFDB.dotCN.modal, e.node);
+ if (modal) {
+ modal.className = BDFDB.DOMUtils.formatClassName(modal.className, this.settings.viewerSettings.galleryMode && BDFDB.disCN._imageutilitiesgallery, this.settings.viewerSettings.details && BDFDB.disCN._imageutilitiesdetailsadded);
+ if (this.settings.viewerSettings.zoomMode) {
+ BDFDB.DOMUtils.addClass(modal, BDFDB.disCN.imagemodal);
+ BDFDB.DOMUtils.removeClass(modal, BDFDB.disCN.modalcarouselmodal, BDFDB.disCN.modalcarouselmodalzoomed);
+ }
+ }
+ }
+ else if (e.methodname == "componentWillUnmount") {
+ firstViewedImage = null;
+ viewedImage = null;
+ this.cleanupListeners("Gallery");
+ }
+ else {
+ let url = this.getImageSrc(viewedImage && viewedImage.proxy_url || typeof e.instance.props.children == "function" && e.instance.props.children(Object.assign({}, e.instance.props, {size: e.instance.props})).props.src || e.instance.props.src);
+ let isVideo = this.isValid(url, "video");
+
+ let [children, index] = BDFDB.ReactUtils.findParent(e.returnvalue, {props: [["className", BDFDB.disCN.downloadlink]]});
+ if (index > -1) {
+ let type = isVideo ? BDFDB.LanguageUtils.LanguageStrings.VIDEO : BDFDB.LanguageUtils.LanguageStrings.IMAGE;
+ let openContext = event => BDFDB.ContextMenuUtils.open(this, event, BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuGroup, {
+ children: Object.keys(this.defaults.zoomSettings).map(type => {
+ let isBoolean = typeof this.defaults.zoomSettings[type].value == "boolean";
+ return BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems[isBoolean ? "MenuCheckboxItem" : "MenuSliderItem"], Object.assign({
+ id: BDFDB.ContextMenuUtils.createItemId(this.name, type)
+ }, isBoolean ? {
+ checked: this.settings.zoomSettings[type],
+ action: value => {
+ this.settings.zoomSettings[type] = value;
+ BDFDB.DataUtils.save(this.settings.zoomSettings, this, "zoomSettings");
+ }
+ } : {
+ value: this.settings.zoomSettings[type],
+ renderLabel: (value, instance) => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex, {
+ align: BDFDB.LibraryComponents.Flex.Align.CENTER,
+ children: [
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, {
+ children: `${this.labels[this.defaults.zoomSettings[type].label] || BDFDB.LanguageUtils.LanguageStrings[this.defaults.zoomSettings[type].label]}:`
+ }),
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TextInput, {
+ type: "number",
+ size: BDFDB.LibraryComponents.TextInput.Sizes.MINI,
+ style: {width: 70},
+ min: 1,
+ max: this.defaults.zoomSettings[type].maxValue,
+ value: this.settings.zoomSettings[type],
+ onChange: value => value && value >= this.defaults.zoomSettings[type].minValue && instance.handleValueChange(BDFDB.NumberUtils.mapRange([this.defaults.zoomSettings[type].minValue, this.defaults.zoomSettings[type].maxValue], [0, 100], value))
+ }),
+ BDFDB.ReactUtils.createElement("span", {
+ style: {width: 20},
+ children: this.defaults.zoomSettings[type].unit
+ })
+ ]
+ }),
+ onValueRender: value => `${value}${this.defaults.zoomSettings[type].unit}`,
+ onValueChange: value => {
+ this.settings.zoomSettings[type] = value;
+ BDFDB.DataUtils.save(this.settings.zoomSettings, this, "zoomSettings");
+ }
+ }, BDFDB.ObjectUtils.extract(this.defaults.zoomSettings[type], isBoolean ? ["label"] : ["digits", "minValue", "maxValue"])));
+ })
+ }));
+ children[index] = BDFDB.ReactUtils.createElement("span", {
+ className: BDFDB.disCN._imageutilitiesoperations,
+ children: [
+ children[index],
+ this.settings.viewerSettings.saveImage && [
+ BDFDB.ReactUtils.createElement("span", {
+ className: BDFDB.disCN.downloadlink,
+ children: "|",
+ style: {margin: "0 5px"}
+ }),
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Anchor, {
+ className: BDFDB.disCN.downloadlink,
+ children: this.labels.context_saveas.replace("{{var0}}", type),
+ onClick: event => {
+ BDFDB.ListenerUtils.stopEvent(event);
+ this.downloadFileAs(url);
+ },
+ onContextMenu: event => {
+ let locations = Object.keys(ownLocations).filter(n => ownLocations[n].enabled);
+ if (locations.length) BDFDB.ContextMenuUtils.open(this, event, BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuGroup, {
+ children: locations.map((name, i) => BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, {
+ id: BDFDB.ContextMenuUtils.createItemId(this.name, "download", name, i),
+ label: name,
+ action: _ => this.downloadFile(url, ownLocations[name].location)
+ }))
+ }));
+ }
+ })
+ ],
+ this.settings.viewerSettings.copyImage && this.isValid(url, "copyable") && [
+ BDFDB.ReactUtils.createElement("span", {
+ className: BDFDB.disCN.downloadlink,
+ children: "|",
+ style: {margin: "0 5px"}
+ }),
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Anchor, {
+ className: BDFDB.disCN.downloadlink,
+ children: this.labels.context_copy.replace("{{var0}}", type),
+ onClick: event => {
+ BDFDB.ListenerUtils.stopEvent(event);
+ this.copyFile(url);
+ }
+ })
+ ],
+ this.settings.viewerSettings.zoomMode && !isVideo && [
+ BDFDB.ReactUtils.createElement("span", {
+ className: BDFDB.disCN.downloadlink,
+ children: "|",
+ style: {margin: "0 5px"}
+ }),
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Anchor, {
+ className: BDFDB.disCN.downloadlink,
+ children: `Zoom ${BDFDB.LanguageUtils.LanguageStrings.SETTINGS}`,
+ onClick: openContext,
+ onContextMenu: openContext
+ })
+ ]
+ ].flat(10).filter(n => n)
+ });
+
+ if (this.settings.viewerSettings.details) {
+ e.returnvalue.props.children.push(BDFDB.ReactUtils.createElement("div", {
+ className: BDFDB.disCN._imageutilitiesdetailswrapper,
+ children: [
+ e.instance.props.alt && {label: "Alt", text: e.instance.props.alt},
+ {label: "Source", text: url},
+ {label: "Size", text: `${e.instance.props.width}x${e.instance.props.height}px`},
+ cachedImages && cachedImages.amount && cachedImages.amount > 1 && {label: "Image", text: `${cachedImages.index + 1 || 1} of ${cachedImages.amount}`}
+ ].filter(n => n).map(data => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TextElement, {
+ className: BDFDB.disCN._imageutilitiesdetails,
+ children: [
+ BDFDB.ReactUtils.createElement("div", {
+ className: BDFDB.disCN._imageutilitiesdetailslabel,
+ children: data.label + ":"
+ }),
+ data.text
+ ]
+ }))
+ }));
+ }
+ }
+
+ if (this.settings.viewerSettings.galleryMode && viewedImage) {
+ if (!cachedImages || cachedImages.channelId != viewedImage.channelId || cachedImages.amount && this.getImageIndex(cachedImages.all, viewedImage) == -1) {
+ BDFDB.TimeUtils.clear(viewedImageTimeout);
+ let channel = BDFDB.LibraryModules.ChannelStore.getChannel(viewedImage.channelId);
+ BDFDB.LibraryModules.APIUtils.get({
+ url: channel && channel.guild_id ? BDFDB.DiscordConstants.Endpoints.SEARCH_GUILD(channel && channel.guild_id) : BDFDB.DiscordConstants.Endpoints.SEARCH_CHANNEL(channel.id),
+ query: BDFDB.LibraryModules.APIEncodeUtils.stringify({
+ channel_id: channel && channel.guild_id ? (BDFDB.ChannelUtils.isThread(channel) && channel.parent_id || channel.id) : null,
+ has: "image",
+ include_nsfw: true,
+ around: viewedImage.messageId
+ })
+ }).catch(_ => {
+ cachedImages = {
+ channelId: viewedImage.channelId,
+ firstReached: null,
+ oldestId: null,
+ all: [],
+ index: -1,
+ amount: 0,
+ newestId: null,
+ lastReached: null
+ };
+ BDFDB.ReactUtils.forceUpdate(e.instance);
+ }).then(result => {
+ if (!viewedImage) return;
+ let messages = [], index = -1;
+ if (result) {
+ messages = result.body.messages.flat(10).reverse();
+ cachedImages = {all: this.filterMessagesForImages(messages, viewedImage)};
+ index = this.getImageIndex(cachedImages.all, viewedImage);
+ }
+ if (index > -1) cachedImages = Object.assign(cachedImages, {
+ channelId: viewedImage.channelId,
+ firstReached: index == 0,
+ oldestId: messages[0] ? messages[0].id : null,
+ index: index,
+ amount: cachedImages.all.length,
+ newestId: messages[messages.length-1] ? messages[messages.length-1].id : null,
+ lastReached: index == (cachedImages.all.length - 1)
+ });
+ else cachedImages = {
+ channelId: viewedImage.channelId,
+ firstReached: null,
+ oldestId: null,
+ all: [],
+ index: -1,
+ amount: 0,
+ newestId: null,
+ lastReached: null
+ };
+ BDFDB.ReactUtils.forceUpdate(e.instance);
+ });
+ }
+ else {
+ if (cachedImages.all[cachedImages.index - 1]) e.returnvalue.props.children.push(BDFDB.ReactUtils.createElement(LazyImageSiblingComponent, {
+ className: BDFDB.disCN._imageutilitiesprevious,
+ modalInstance: e.instance,
+ url: this.getImageSrc(cachedImages.all[cachedImages.index - 1].thumbnail || cachedImages.all[cachedImages.index - 1]),
+ offset: -1,
+ svgIcon: BDFDB.LibraryComponents.SvgIcon.Names.LEFT_CARET
+ }));
+ if (cachedImages.all[cachedImages.index + 1]) e.returnvalue.props.children.push(BDFDB.ReactUtils.createElement(LazyImageSiblingComponent, {
+ className: BDFDB.disCN._imageutilitiesnext,
+ modalInstance: e.instance,
+ url: this.getImageSrc(cachedImages.all[cachedImages.index + 1].thumbnail || cachedImages.all[cachedImages.index + 1]),
+ offset: 1,
+ svgIcon: BDFDB.LibraryComponents.SvgIcon.Names.RIGHT_CARET
+ }));
+ if (cachedImages.all[cachedImages.index - 1] || cachedImages.all[cachedImages.index + 1]) {
+ this.addListener("keydown", "Gallery", event => {
+ if (!firedEvents.includes("Gallery")) {
+ firedEvents.push("Gallery");
+ if (event.keyCode == 37) this.switchImages(e.instance, -1);
+ else if (event.keyCode == 39) this.switchImages(e.instance, 1);
+ }
+ });
+ this.addListener("keyup", "Gallery", _ => BDFDB.ArrayUtils.remove(firedEvents, "Gallery", true));
+ }
+ }
+ }
+ }
+ }
+
+ processModalCarousel (e) {
+ if (this.settings.viewerSettings.zoomMode) {
+ let [children, index] = BDFDB.ReactUtils.findParent(e.returnvalue, {name: "ImageModal"});
+ if (index > -1) return children[index];
+ }
+ }
+
+ processLazyImage (e) {
+ if (e.node) {
+ if (e.instance.props.resized) {
+ let embed = BDFDB.DOMUtils.getParent(BDFDB.dotCN.embedfull, e.node);
+ if (embed) embed.style.setProperty("max-width", "unset", "important");
+ if (e.instance.state.readyState != BDFDB.LibraryComponents.Image.ImageReadyStates.READY) {
+ e.instance.state.readyState = BDFDB.LibraryComponents.Image.ImageReadyStates.READY;
+ BDFDB.ReactUtils.forceUpdate(e.instance);
+ }
+ }
+ if (e.methodname == "componentDidMount") {
+ let isVideo = (typeof e.instance.props.children == "function" && e.instance.props.children(Object.assign({}, e.instance.props, {size: e.instance.props})) || {type: {}}).type.displayName == "Video";
+ if (this.settings.viewerSettings.zoomMode && !isVideo && !BDFDB.DOMUtils.containsClass(e.node.parentElement, BDFDB.disCN._imageutilitiessibling) && BDFDB.ReactUtils.findOwner(BDFDB.ReactUtils.getInstance(e.node), {name: "ImageModal", up: true})) {
+ e.node.style.setProperty("cursor", "zoom-in");
+ e.node.addEventListener("mousedown", event => {
+ if (event.which != 1) return;
+ BDFDB.ListenerUtils.stopEvent(event);
+
+ let vanishObserver;
+
+ let imgRects = BDFDB.DOMUtils.getRects(e.node.firstElementChild);
+
+ let lens = BDFDB.DOMUtils.create(`<${e.node.firstElementChild.tagName} src="${e.instance.props.src}" style="width: ${imgRects.width * this.settings.zoomSettings.zoomLevel}px; height: ${imgRects.height * this.settings.zoomSettings.zoomLevel}px; position: fixed !important;${this.settings.zoomSettings.pixelMode ? " image-rendering: pixelated !important;" : ""}"${e.node.firstElementChild.tagName == "VIDEO" ? " loop autoplay" : ""}>${e.node.firstElementChild.tagName}>
`);
+ let pane = lens.firstElementChild.firstElementChild;
+ let backdrop = BDFDB.DOMUtils.create(``);
+ let appMount = document.querySelector(BDFDB.dotCN.appmount);
+ appMount.appendChild(lens);
+ appMount.appendChild(backdrop);
+
+ let lensRects = BDFDB.DOMUtils.getRects(lens);
+
+ let halfW = lensRects.width / 2, halfH = lensRects.height / 2;
+ let minX = imgRects.left, maxX = minX + imgRects.width;
+ let minY = imgRects.top, maxY = minY + imgRects.height;
+
+ lens.update = _ => {
+ let x = event.clientX > maxX ? maxX - halfW : event.clientX < minX ? minX - halfW : event.clientX - halfW;
+ let y = event.clientY > maxY ? maxY - halfH : event.clientY < minY ? minY - halfH : event.clientY - halfH;
+ lens.style.setProperty("left", x + "px", "important");
+ lens.style.setProperty("top", y + "px", "important");
+ lens.style.setProperty("width", this.settings.zoomSettings.lensSize + "px", "important");
+ lens.style.setProperty("height", this.settings.zoomSettings.lensSize + "px", "important");
+ lens.style.setProperty("clip-path", `circle(${(this.settings.zoomSettings.lensSize/2) + 2}px at center)`, "important");
+ lens.firstElementChild.style.setProperty("clip-path", `circle(${this.settings.zoomSettings.lensSize/2}px at center)`, "important");
+ pane.style.setProperty("left", imgRects.left + ((this.settings.zoomSettings.zoomLevel - 1) * (imgRects.left - x - halfW)) + "px", "important");
+ pane.style.setProperty("top", imgRects.top + ((this.settings.zoomSettings.zoomLevel - 1) * (imgRects.top - y - halfH)) + "px", "important");
+ pane.style.setProperty("width", imgRects.width * this.settings.zoomSettings.zoomLevel + "px", "important");
+ pane.style.setProperty("height", imgRects.height * this.settings.zoomSettings.zoomLevel + "px", "important");
+ };
+ lens.update();
+
+ e.node.style.setProperty("pointer-events", "none", "important");
+
+ let dragging = event2 => {
+ event = event2;
+ lens.update();
+ };
+ let releasing = event2 => {
+ BDFDB.ListenerUtils.stopEvent(event2);
+ e.node.style.removeProperty("pointer-events");
+ this.cleanupListeners("Zoom");
+ document.removeEventListener("mousemove", dragging);
+ document.removeEventListener("mouseup", releasing);
+ if (vanishObserver) vanishObserver.disconnect();
+ BDFDB.DOMUtils.remove(lens, backdrop);
+ BDFDB.DataUtils.save(this.settings.zoomSettings, this, "zoomSettings");
+ };
+ document.addEventListener("mousemove", dragging);
+ document.addEventListener("mouseup", releasing);
+
+ this.cleanupListeners("Zoom");
+ this.addListener("wheel", "Zoom", event2 => {
+ if (!document.contains(e.node)) this.cleanupListeners("Zoom");
+ else {
+ if (event2.deltaY < 0 && (this.settings.zoomSettings.zoomLevel + 0.1) <= this.defaults.zoomSettings.zoomLevel.maxValue) {
+ this.settings.zoomSettings.zoomLevel += 0.1;
+ lens.update();
+ }
+ else if (event2.deltaY > 0 && (this.settings.zoomSettings.zoomLevel - 0.1) >= this.defaults.zoomSettings.zoomLevel.minValue) {
+ this.settings.zoomSettings.zoomLevel -= 0.1;
+ lens.update();
+ }
+ }
+ });
+ this.addListener("keydown", "Zoom", event2 => {
+ if (!document.contains(e.node)) this.cleanupListeners("Zoom");
+ else if (!firedEvents.includes("Zoom")) {
+ firedEvents.push("Zoom");
+ if (event2.keyCode == 187 && (this.settings.zoomSettings.zoomLevel + 0.5) <= this.defaults.zoomSettings.zoomLevel.maxValue) {
+ this.settings.zoomSettings.zoomLevel += 0.5;
+ lens.update();
+ }
+ else if (event2.keyCode == 189 && (this.settings.zoomSettings.zoomLevel - 0.5) >= this.defaults.zoomSettings.zoomLevel.minValue) {
+ this.settings.zoomSettings.zoomLevel -= 0.5;
+ lens.update();
+ }
+ }
+ });
+ this.addListener("keyup", "Zoom", _ => {
+ BDFDB.ArrayUtils.remove(firedEvents, "Zoom", true);
+ if (!document.contains(e.node)) this.cleanupListeners("Zoom");
+ });
+
+ vanishObserver = new MutationObserver(changes => {if (!document.contains(e.node)) releasing();});
+ vanishObserver.observe(appMount, {childList: true, subtree: true});
+ });
+ }
+ }
+ }
+ else {
+ let reactInstance = BDFDB.ObjectUtils.get(e, `instance.${BDFDB.ReactUtils.instanceKey}`);
+ if (this.settings.rescaleSettings.imageViewer != "NONE" && BDFDB.ReactUtils.findOwner(reactInstance, {name: "ImageModal", up: true})) {
+ let aRects = BDFDB.DOMUtils.getRects(document.querySelector(BDFDB.dotCN.appmount));
+ let ratio = Math.min((aRects.width * (this.settings.viewerSettings.galleryMode ? 0.8 : 1) - 20) / e.instance.props.width, (aRects.height - (this.settings.viewerSettings.details ? 280 : 100)) / e.instance.props.height);
+ ratio = this.settings.rescaleSettings.imageViewer == "ORIGINAL" && ratio > 1 ? 1 : ratio;
+ let width = Math.round(ratio * e.instance.props.width);
+ let height = Math.round(ratio * e.instance.props.height);
+ if (e.instance.props.width != width || e.instance.props.maxWidth != width || e.instance.props.height != height || e.instance.props.maxHeight != height) {
+ e.instance.props.width = width;
+ e.instance.props.maxWidth = width;
+ e.instance.props.height = height;
+ e.instance.props.maxHeight = height;
+ e.instance.props.src = e.instance.props.src.replace(/width=\d+/, `width=${width}`).replace(/height=\d+/, `height=${height}`);
+ e.instance.props.resized = true;
+ }
+ }
+ if (this.settings.rescaleSettings.messages != "NONE" && (!e.instance.props.className || e.instance.props.className.indexOf(BDFDB.disCN.embedthumbnail) == -1) && BDFDB.ReactUtils.findOwner(reactInstance, {name: "LazyImageZoomable", up: true})) {
+ let aRects = BDFDB.DOMUtils.getRects(document.querySelector(BDFDB.dotCN.appmount));
+ let mRects = BDFDB.DOMUtils.getRects(document.querySelector(BDFDB.dotCNC.messageaccessory + BDFDB.dotCN.messagecontents));
+ let mwRects = BDFDB.DOMUtils.getRects(document.querySelector(BDFDB.dotCN.messagewrapper));
+ if (mRects.width || mwRects.width) {
+ let embed = BDFDB.ReactUtils.findValue(reactInstance, "embed", {up: true});
+ let ratio = ((mRects.width || (mwRects.width - 120)) - (embed && embed.color ? 100 : 0)) / e.instance.props.width;
+ ratio = this.settings.rescaleSettings.messages == "ORIGINAL" && ratio > 1 ? 1 : ratio;
+ let width = Math.round(ratio * e.instance.props.width);
+ let height = Math.round(ratio * e.instance.props.height);
+ if (height > (aRects.height * 0.66)) {
+ let newHeight = Math.round(aRects.height * 0.66);
+ width = (newHeight/height) * width;
+ height = newHeight;
+ }
+ if (e.instance.props.width != width || e.instance.props.maxWidth != width || e.instance.props.height != height || e.instance.props.maxHeight != height) {
+ e.instance.props.width = width;
+ e.instance.props.maxWidth = width;
+ e.instance.props.height = height;
+ e.instance.props.maxHeight = height;
+ e.instance.props.src = e.instance.props.src.replace(/width=\d+/, `width=${width}`).replace(/height=\d+/, `height=${height}`);
+ e.instance.props.resized = true;
+ }
+ }
+ }
+ }
+ }
+
+ processLazyImageZoomable (e) {
+ if (this.settings.detailsSettings.tooltip && e.instance.props.original && e.instance.props.src.indexOf("https://media.discordapp.net/attachments") == 0) {
+ const attachment = BDFDB.ReactUtils.findValue(e.instance, "attachment", {up: true});
+ if (attachment) {
+ const onMouseEnter = e.returnvalue.props.onMouseEnter;
+ e.returnvalue.props.onMouseEnter = BDFDB.TimeUtils.suppress((...args) => {
+ BDFDB.TooltipUtils.create(args[0].target, [
+ attachment.filename,
+ BDFDB.NumberUtils.formatBytes(attachment.size),
+ `${attachment.width}x${attachment.height}px`
+ ].map(l => BDFDB.ReactUtils.createElement("div", {style: {padding: "2px 0"}, children: l})), {
+ type: "right",
+ delay: this.settings.detailsSettings.tooltipDelay
+ });
+ return onMouseEnter(...args);
+ }, "Error in onMouseEnter of LazyImageZoomable!");
+ }
+ }
+ }
+
+ processSimpleMessageAccessories (e) {
+ if (this.settings.general.nsfwMode && e.instance.props.channel.nsfw) {
+ e.instance.props.message = new BDFDB.DiscordObjects.Message(e.instance.props.message);
+ e.instance.props.message.attachments = [].concat(e.instance.props.message.attachments);
+ for (let i in e.instance.props.message.attachments) if (e.instance.props.message.attachments[i].spoiler != undefined) {
+ e.instance.props.message.attachments[i] = Object.assign({}, e.instance.props.message.attachments[i], {spoiler: true, nsfw: !e.instance.props.message.attachments[i].spoiler});
+ }
+ }
+ }
+
+ processSpoiler (e) {
+ if (!e.returnvalue) {
+ if (this.settings.rescaleSettings.messages != "NONE" && !e.instance.props.inline && e.instance.props.type == "attachment") delete e.instance.props.containerStyles.maxWidth;
+ }
+ else {
+ if (this.settings.general.nsfwMode) {
+ let childrenRender = e.returnvalue.props.children;
+ e.returnvalue.props.children = BDFDB.TimeUtils.suppress((...args) => {
+ let children = childrenRender(...args);
+ let attachment = BDFDB.ReactUtils.findValue(children, "attachment");
+ if (attachment && attachment.nsfw) {
+ let [children2, index] = BDFDB.ReactUtils.findParent(children, {name: "SpoilerWarning"});
+ if (index > -1) children2[index] = BDFDB.ReactUtils.createElement("div", {
+ className: BDFDB.disCN.spoilerwarning,
+ children: "NSFW"
+ });
+ }
+ return children;
+ }, "Error in Children Render of Spoiler!");
+ }
+ }
+ }
+
+ processUserBanner (e) {
+ if (this.settings.places.userAvatars && e.instance.props.displayProfile && e.instance.props.displayProfile.banner) e.returnvalue.props.onContextMenu = event => {
+ let validUrls = this.filterUrls(BDFDB.UserUtils.getBanner(e.instance.props.user.id, null, false), BDFDB.LibraryModules.IconUtils.isAnimatedIconHash(e.instance.props.displayProfile._userProfile.banner) && BDFDB.UserUtils.getBanner(e.instance.props.user.id, null, true), e.instance.props.displayProfile._guildMemberProfile.banner && BDFDB.UserUtils.getBanner(e.instance.props.user.id, e.instance.props.guildId, false), e.instance.props.displayProfile._guildMemberProfile.banner && BDFDB.LibraryModules.IconUtils.isAnimatedIconHash(e.instance.props.displayProfile._guildMemberProfile.banner) && BDFDB.UserUtils.getBanner(e.instance.props.user.id, e.instance.props.guildId, true));
+ if (validUrls.length) BDFDB.ContextMenuUtils.open(this, event, BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuGroup, {
+ children: validUrls.length == 1 ? this.createSubMenus({
+ instance: {},
+ urls: validUrls,
+ prefix: BDFDB.LanguageUtils.LanguageStrings.USER_SETTINGS_PROFILE_BANNER
+ }) : BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, {
+ label: BDFDB.LanguageUtils.LanguageStrings.IMAGE + " " + BDFDB.LanguageUtils.LanguageStrings.ACTIONS,
+ id: BDFDB.ContextMenuUtils.createItemId(this.name, "main-subitem"),
+ children: this.createSubMenus({
+ instance: {},
+ urls: validUrls,
+ prefix: BDFDB.LanguageUtils.LanguageStrings.USER_SETTINGS_PROFILE_BANNER
+ })
+ })
+ }));
+ };
+ }
+
+ cacheClickedImage (target) {
+ if (!target) return;
+ const image = (BDFDB.DOMUtils.getParent(BDFDB.dotCN.imagewrapper, target) || target).querySelector("img") || target;
+ if (!image) return;
+ const message = BDFDB.ReactUtils.findValue(image, "message", {up: true});
+ if (!message) return;
+ firstViewedImage = {messageId: message.id, channelId: message.channel_id, proxy_url: image.src};
+ viewedImage = firstViewedImage;
+ if (cachedImages) cachedImages.index = this.getImageIndex(cachedImages.all, viewedImage);
+ viewedImageTimeout = BDFDB.TimeUtils.timeout(_ => {
+ firstViewedImage = null;
+ viewedImage = null;
+ }, 1000);
+ }
+
+ downloadFile (url, path, fallbackUrl, alternativeName) {
+ url = url.startsWith("/assets") ? (window.location.origin + url) : url;
+ BDFDB.LibraryRequires.request(url, {agentOptions: {rejectUnauthorized: false}, encoding: null}, (error, response, body) => {
+ let type = this.isValid(url, "video") ? BDFDB.LanguageUtils.LanguageStrings.VIDEO : BDFDB.LanguageUtils.LanguageStrings.IMAGE;
+ if (error || response.statusCode != 200 || response.headers["content-type"] == "text/html") {
+ if (fallbackUrl) this.downloadFile(fallbackUrl, path, null, alternativeName);
+ else BDFDB.NotificationUtils.toast(this.labels.toast_save_failed.replace("{{var0}}", type).replace("{{var1}}", ""), {type: "danger"});
+ }
+ else {
+ BDFDB.LibraryRequires.fs.writeFile(this.getFileName(path, (alternativeName || url.split("/").pop().split(".").slice(0, -1).join(".") || "unknown").slice(0, 35), this.getFileExtenstion(response.headers["content-type"].split("/").pop().split("+")[0]), 0), body, error => {
+ if (error) BDFDB.NotificationUtils.toast(this.labels.toast_save_failed.replace("{{var0}}", type).replace("{{var1}}", path), {type: "danger"});
+ else BDFDB.NotificationUtils.toast(this.labels.toast_save_success.replace("{{var0}}", type).replace("{{var1}}", path), {type: "success"});
+ });
+ }
+ });
+ }
+
+ downloadFileAs (url, fallbackUrl, alternativeName) {
+ url = url.startsWith("/assets") ? (window.location.origin + url) : url;
+ BDFDB.LibraryRequires.request(url, {agentOptions: {rejectUnauthorized: false}, encoding: null}, (error, response, body) => {
+ let type = this.isValid(url, "video") ? BDFDB.LanguageUtils.LanguageStrings.VIDEO : BDFDB.LanguageUtils.LanguageStrings.IMAGE;
+ if (error || response.statusCode != 200 || response.headers["content-type"] == "text/html") {
+ if (fallbackUrl) this.downloadFileAs(fallbackUrl, null, alternativeName);
+ else BDFDB.NotificationUtils.toast(this.labels.toast_save_failed.replace("{{var0}}", type).replace("{{var1}}", ""), {type: "danger"});
+ }
+ else {
+ let hrefURL = window.URL.createObjectURL(new Blob([body]));
+ let tempLink = document.createElement("a");
+ tempLink.href = hrefURL;
+ tempLink.download = `${(alternativeName || url.split("/").pop().split(".").slice(0, -1).join(".") || "unknown").slice(0, 35)}.${this.getFileExtenstion(response.headers["content-type"].split("/").pop().split("+")[0])}`;
+ tempLink.click();
+ window.URL.revokeObjectURL(hrefURL);
+ }
+ });
+ }
+
+ copyFile (url) {
+ BDFDB.LibraryRequires.request(url, {agentOptions: {rejectUnauthorized: false}, encoding: null}, (error, response, body) => {
+ let type = this.isValid(url, "video") ? BDFDB.LanguageUtils.LanguageStrings.VIDEO : BDFDB.LanguageUtils.LanguageStrings.IMAGE;
+ if (error) BDFDB.NotificationUtils.toast(this.labels.toast_copy_failed.replace("{{var0}}", type), {type: "danger"});
+ else if (body) {
+ if (BDFDB.LibraryRequires.process.platform === "win32" || BDFDB.LibraryRequires.process.platform === "darwin") {
+ BDFDB.LibraryRequires.electron.clipboard.write({image: BDFDB.LibraryRequires.electron.nativeImage.createFromBuffer(body)});
+ }
+ else {
+ let file = BDFDB.LibraryRequires.path.join(BDFDB.LibraryRequires.process.env.USERPROFILE || BDFDB.LibraryRequires.process.env.HOMEPATH || BDFDB.LibraryRequires.process.env.HOME, "imageutilstempimg.png");
+ BDFDB.LibraryRequires.fs.writeFileSync(file, body, {encoding: null});
+ BDFDB.LibraryRequires.electron.clipboard.write({image: file});
+ BDFDB.LibraryRequires.fs.unlinkSync(file);
+ }
+ BDFDB.NotificationUtils.toast(this.labels.toast_copy_success.replace("{{var0}}", type), {type: "success"});
+ }
+ });
+ }
+
+ getDownloadLocation () {
+ if (downloadsFolder && BDFDB.LibraryRequires.fs.existsSync(downloadsFolder)) return downloadsFolder;
+ let homePath = BDFDB.LibraryRequires.process.env.USERPROFILE || BDFDB.LibraryRequires.process.env.HOMEPATH || BDFDB.LibraryRequires.process.env.HOME;
+ let downloadPath = homePath && BDFDB.LibraryRequires.path.join(homePath, "Downloads");
+ if (downloadPath && BDFDB.LibraryRequires.fs.existsSync(downloadPath)) return downloadsFolder = downloadPath;
+ else {
+ downloadsFolder = BDFDB.LibraryRequires.path.join(BDFDB.BDUtils.getPluginsFolder(), "downloads");
+ if (!BDFDB.LibraryRequires.fs.existsSync(downloadsFolder)) BDFDB.LibraryRequires.fs.mkdirSync(downloadsFolder);
+ return downloadsFolder;
+ }
+ }
+
+ getFileName (path, fileName, extension, i) {
+ fileName = fileName.split("?")[0];
+ let wholePath = BDFDB.LibraryRequires.path.join(path, i ? `${fileName} (${i}).${extension}` : `${fileName}.${extension}`);
+ if (BDFDB.LibraryRequires.fs.existsSync(wholePath)) return this.getFileName(path, fileName, extension, i + 1);
+ else return wholePath;
+ }
+
+ getFileExtenstion (ext) {
+ if (ext == "quicktime") ext = "mov";
+ return ext;
+ }
+
+ getImageSrc (img) {
+ if (!img) return null;
+ return (typeof img == "string" ? img : (img.proxy_url || img.src || (img.querySelector("canvas") ? img.querySelector("canvas").src : ""))).split("?width=")[0];
+ }
+
+ getImageIndex (messages, img) {
+ return messages.findIndex(i => i.messageId == img.messageId && (messages.filter(n => n.messageId == i.messageId).length < 2 || i.url && img.proxy_url.indexOf(i.url) > -1 || i.proxy_url && img.proxy_url.indexOf(i.proxy_url) > -1));
+ }
+
+ filterMessagesForImages (messages, img) {
+ return messages.filter(m => m && m.hit && m.channel_id == img.channelId && (m.id == firstViewedImage.messageId || m.id == img.messageId || m.embeds.length || m.attachments.filter(a => !a.filename.startsWith("SPOILER_")).length)).map(m => [m.attachments, m.embeds].flat(10).filter(n => n).map(i => Object.assign({messageId: m.id, channelId: img.channelId}, i, i.image, i.thumbnail, i.video))).flat(10);
+ }
+
+ switchImages (modalInstance, offset) {
+ const newIndex = parseInt(cachedImages.index) + parseInt(offset);
+ if (newIndex < 0 || newIndex > (cachedImages.amount - 1)) return;
+
+ cachedImages.index = newIndex;
+ const oldImage = viewedImage;
+ viewedImage = cachedImages.all[cachedImages.index];
+
+ if (offset > 0 && !cachedImages.lastReached && cachedImages.index == (cachedImages.amount - 1)) {
+ let channel = BDFDB.LibraryModules.ChannelStore.getChannel(viewedImage.channelId);
+ BDFDB.LibraryModules.APIUtils.get({
+ url: channel && channel.guild_id ? BDFDB.DiscordConstants.Endpoints.SEARCH_GUILD(channel && channel.guild_id) : BDFDB.DiscordConstants.Endpoints.SEARCH_CHANNEL(channel.id),
+ query: BDFDB.LibraryModules.APIEncodeUtils.stringify({
+ channel_id: channel && channel.guild_id ? (BDFDB.ChannelUtils.isThread(channel) && channel.parent_id || channel.id) : null,
+ has: "image",
+ include_nsfw: true,
+ min_id: (BigInt(cachedImages.newestId) - BigInt(1)).toString()
+ })
+ }).then(result => {
+ if (result && viewedImage) {
+ const messages = result.body.messages.flat(10).reverse();
+ const newCachedImages = this.filterMessagesForImages(messages, viewedImage);
+ const lastOldIndex = this.getImageIndex(newCachedImages, cachedImages.all[cachedImages.all.length-1]);
+ if (lastOldIndex > -1) {
+ cachedImages = Object.assign(cachedImages, {all: [].concat(cachedImages.all, newCachedImages.slice(lastOldIndex + 1))});
+ const index = this.getImageIndex(cachedImages.all, viewedImage);
+ cachedImages = Object.assign(cachedImages, {
+ channelId: viewedImage.channelId,
+ index: index,
+ amount: cachedImages.all.length,
+ newestId: messages[messages.length-1] ? messages[messages.length-1].id : null,
+ lastReached: index == (cachedImages.all.length - 1)
+ });
+ }
+ BDFDB.ReactUtils.forceUpdate(modalInstance);
+ }
+ });
+ }
+ if (offset < 0 && !cachedImages.firstReached && cachedImages.index == 0) {
+ let channel = BDFDB.LibraryModules.ChannelStore.getChannel(viewedImage.channelId);
+ BDFDB.LibraryModules.APIUtils.get({
+ url: channel && channel.guild_id ? BDFDB.DiscordConstants.Endpoints.SEARCH_GUILD(channel && channel.guild_id) : BDFDB.DiscordConstants.Endpoints.SEARCH_CHANNEL(channel.id),
+ query: BDFDB.LibraryModules.APIEncodeUtils.stringify({
+ channel_id: channel && channel.guild_id ? (BDFDB.ChannelUtils.isThread(channel) && channel.parent_id || channel.id) : null,
+ has: "image",
+ include_nsfw: true,
+ max_id: (BigInt(cachedImages.oldestId) + BigInt(1)).toString()
+ })
+ }).then(result => {
+ if (result && viewedImage) {
+ const messages = result.body.messages.flat(10).reverse();
+ const newCachedImages = this.filterMessagesForImages(messages, viewedImage);
+ const firstOldIndex = this.getImageIndex(newCachedImages, cachedImages.all[0]);
+ if (firstOldIndex > -1) {
+ cachedImages = Object.assign(cachedImages, {all: [].concat(newCachedImages.slice(0, firstOldIndex), cachedImages.all)});
+ const index = this.getImageIndex(cachedImages.all, viewedImage);
+ cachedImages = Object.assign(cachedImages, {
+ channelId: viewedImage.channelId,
+ firstReached: index == 0,
+ oldestId: messages[0] ? messages[0].id : null,
+ index: index,
+ amount: cachedImages.all.length
+ });
+ }
+ BDFDB.ReactUtils.forceUpdate(modalInstance);
+ }
+ });
+ }
+ let isVideo = this.isValid(viewedImage.proxy_url, "video");
+ modalInstance.props.animated = !!isVideo;
+ modalInstance.props.original = viewedImage.proxy_url;
+ modalInstance.props.placeholder = viewedImage.thumbnail && viewedImage.thumbnail.proxy_url || viewedImage.proxy_url;
+ modalInstance.props.src = viewedImage.proxy_url;
+ modalInstance.props.width = viewedImage.width;
+ modalInstance.props.height = viewedImage.height;
+ modalInstance.props.children = !isVideo ? null : (videoData => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Video, {
+ src: viewedImage.proxy_url,
+ width: videoData.size.width,
+ height: videoData.size.height,
+ naturalWidth: viewedImage.width,
+ naturalHeight: viewedImage.height,
+ play: true,
+ playOnHover: !!BDFDB.LibraryModules.LocalSettingsStore.useReducedMotion
+ }));
+ BDFDB.ReactUtils.forceUpdate(modalInstance);
+ }
+
+ addListener (eventType, type, callback) {
+ if (!type || !eventType || typeof callback != "function") return;
+ if (!eventTypes[type]) eventTypes[type] = [];
+ if (!eventTypes[type].includes(eventType)) eventTypes[type].push(eventType);
+ document.removeEventListener(eventType, document[`${eventType}${this.name}${type}Listener`]);
+ delete document[`${eventType}${this.name}${type}Listener`];
+ document[`${eventType}${this.name}${type}Listener`] = callback;
+ document.addEventListener(eventType, document[`${eventType}${this.name}${type}Listener`]);
+ }
+
+ cleanupListeners (type) {
+ if (!type || !eventTypes[type]) return;
+ for (let eventType of eventTypes[type]) {
+ document.removeEventListener(eventType, document[`${eventType}${this.name}${type}Listener`]);
+ delete document[`${eventType}${this.name}${type}Listener`];
+ }
+ }
+
+ setLabelsByLanguage () {
+ switch (BDFDB.LanguageUtils.getLanguage().id) {
+ case "bg": // Bulgarian
+ return {
+ context_copy: "Копирайте {{var0}}",
+ context_lenssize: "Размер на обектива",
+ context_saveas: "Запазете {{var0}} като ...",
+ context_searchwith: "Търсете {{var0}} с ...",
+ context_view: "Преглед {{var0}}",
+ submenu_disabled: "Всички инвалиди",
+ toast_copy_failed: "{{var0}} не можа да бъде копиран в клипборда",
+ toast_copy_success: "{{var0}} беше копиран в клипборда",
+ toast_save_failed: "{{var0}} не можа да бъде запазен в '{{var1}}'",
+ toast_save_success: "{{var0}} бе запазено в '{{var1}}'"
+ };
+ case "cs": // Czech
+ return {
+ context_copy: "Zkopírovat {{var0}}",
+ context_lenssize: "Velikost lupy",
+ context_saveas: "Uložit {{var0}} jako...",
+ context_searchwith: "Hledat {{var0}} pomocí...",
+ context_view: "Zobrazit {{var0}}",
+ submenu_disabled: "Vše zakázáno",
+ toast_copy_failed: "{{var0}} nemohl být zkopírován do schránky",
+ toast_copy_success: "{{var0}} byl zkopírován do schránky",
+ toast_save_failed: "{{var0}} nemohl být uložen do '{{var1}}'",
+ toast_save_success: "{{var0}} bylo uložen do '{{var1}}'"
+ };
+ case "da": // Danish
+ return {
+ context_copy: "Kopiér {{var0}}",
+ context_lenssize: "Objektivstørrelse",
+ context_saveas: "Gem {{var0}} som ...",
+ context_searchwith: "Søg i {{var0}} med ...",
+ context_view: "Se {{var0}}",
+ submenu_disabled: "Alle handicappede",
+ toast_copy_failed: "{{var0}} kunne ikke kopieres til udklipsholderen",
+ toast_copy_success: "{{var0}} blev kopieret til udklipsholderen",
+ toast_save_failed: "{{var0}} kunne ikke gemmes i '{{var1}}'",
+ toast_save_success: "{{var0}} blev gemt i '{{var1}}'"
+ };
+ case "de": // German
+ return {
+ context_copy: "{{var0}} kopieren",
+ context_lenssize: "Linsengröße",
+ context_saveas: "{{var0}} speichern als ...",
+ context_searchwith: "{{var0}} suchen mit ...",
+ context_view: "{{var0}} ansehen",
+ submenu_disabled: "Alle deaktiviert",
+ toast_copy_failed: "{{var0}} konnte nicht in die Zwischenablage kopiert werden",
+ toast_copy_success: "{{var0}} wurde in die Zwischenablage kopiert",
+ toast_save_failed: "{{var0}} konnte nicht in '{{var1}}' gespeichert werden",
+ toast_save_success: "{{var0}} wurde in '{{var1}}' gespeichert"
+ };
+ case "el": // Greek
+ return {
+ context_copy: "Αντιγραφή {{var0}}",
+ context_lenssize: "Μέγεθος φακού",
+ context_saveas: "Αποθήκευση {{var0}} ως ...",
+ context_searchwith: "Αναζήτηση {{var0}} με ...",
+ context_view: "Προβολή {{var0}}",
+ submenu_disabled: "Όλα τα άτομα με ειδικές ανάγκες",
+ toast_copy_failed: "Δεν ήταν δυνατή η αντιγραφή του {{var0}} στο πρόχειρο",
+ toast_copy_success: "Το {{var0}} αντιγράφηκε στο πρόχειρο",
+ toast_save_failed: "Δεν ήταν δυνατή η αποθήκευση του {{var0}} στο '{{var1}}'",
+ toast_save_success: "Το {{var0}} αποθηκεύτηκε στο '{{var1}}'"
+ };
+ case "es": // Spanish
+ return {
+ context_copy: "Copiar {{var0}}",
+ context_lenssize: "Tamaño de la lente",
+ context_saveas: "Guardar {{var0}} como ...",
+ context_searchwith: "Buscar {{var0}} con ...",
+ context_view: "Ver {{var0}}",
+ submenu_disabled: "Todos discapacitados",
+ toast_copy_failed: "{{var0}} no se pudo copiar al portapapeles",
+ toast_copy_success: "{{var0}} se copió en el portapapeles",
+ toast_save_failed: "{{var0}} no se pudo guardar en '{{var1}}'",
+ toast_save_success: "{{var0}} se guardó en '{{var1}}'"
+ };
+ case "fi": // Finnish
+ return {
+ context_copy: "Kopioi {{var0}}",
+ context_lenssize: "Linssin koko",
+ context_saveas: "Tallenna {{var0}} nimellä ...",
+ context_searchwith: "Tee haku {{var0}} ...",
+ context_view: "Näytä {{var0}}",
+ submenu_disabled: "Kaikki vammaiset",
+ toast_copy_failed: "Kohdetta {{var0}} ei voitu kopioida leikepöydälle",
+ toast_copy_success: "{{var0}} kopioitiin leikepöydälle",
+ toast_save_failed: "Kohdetta {{var0}} ei voitu tallentaa kansioon '{{var1}}'",
+ toast_save_success: "{{var0}} tallennettiin kansioon '{{var1}}'"
+ };
+ case "fr": // French
+ return {
+ context_copy: "Copier {{var0}}",
+ context_lenssize: "Taille de l'objectif",
+ context_saveas: "Enregistrer {{var0}} sous ...",
+ context_searchwith: "Rechercher {{var0}} avec ...",
+ context_view: "Afficher {{var0}}",
+ submenu_disabled: "Tout désactivé",
+ toast_copy_failed: "{{var0}} n'a pas pu être copié dans le presse-papiers",
+ toast_copy_success: "{{var0}} a été copié dans le presse-papiers",
+ toast_save_failed: "{{var0}} n'a pas pu être enregistré dans '{{var1}}'",
+ toast_save_success: "{{var0}} a été enregistré dans '{{var1}}'"
+ };
+ case "hi": // Hindi
+ return {
+ context_copy: "कॉपी {{var0}}",
+ context_lenssize: "लेंस का आकार",
+ context_saveas: "{{var0}} को इस रूप में सेव करें...",
+ context_searchwith: "इसके साथ {{var0}} खोजें ...",
+ context_view: "देखें {{var0}}",
+ submenu_disabled: "सभी अक्षम",
+ toast_copy_failed: "{{var0}} को क्लिपबोर्ड पर कॉपी नहीं किया जा सका",
+ toast_copy_success: "{{var0}} को क्लिपबोर्ड पर कॉपी किया गया था",
+ toast_save_failed: "{{var0}} '{{var1}}' में सहेजा नहीं जा सका",
+ toast_save_success: "{{var0}} '{{var1}}' में सहेजा गया था"
+ };
+ case "hr": // Croatian
+ return {
+ context_copy: "Kopiraj {{var0}}",
+ context_lenssize: "Veličina leće",
+ context_saveas: "Spremi {{var0}} kao ...",
+ context_searchwith: "Traži {{var0}} sa ...",
+ context_view: "Pogledajte {{var0}}",
+ submenu_disabled: "Svi invalidi",
+ toast_copy_failed: "{{var0}} nije moguće kopirati u međuspremnik",
+ toast_copy_success: "{{var0}} je kopirano u međuspremnik",
+ toast_save_failed: "{{var0}} nije moguće spremiti u '{{var1}}'",
+ toast_save_success: "{{var0}} spremljeno je u '{{var1}}'"
+ };
+ case "hu": // Hungarian
+ return {
+ context_copy: "{{var0}} másolása",
+ context_lenssize: "Lencse mérete",
+ context_saveas: "{{var0}} mentése másként ...",
+ context_searchwith: "Keresés a következőben: {{var0}} a következővel:",
+ context_view: "Megtekintés: {{var0}}",
+ submenu_disabled: "Minden fogyatékkal él",
+ toast_copy_failed: "A {{var0}} fájl nem másolható a vágólapra",
+ toast_copy_success: "A {{var0}} elemet a vágólapra másolta",
+ toast_save_failed: "A {{var0}} fájl mentése nem sikerült a '{{var1}}' mappába",
+ toast_save_success: "{{var0}} mentve a '{{var1}}' mappába"
+ };
+ case "it": // Italian
+ return {
+ context_copy: "Copia {{var0}}",
+ context_lenssize: "Dimensione della lente",
+ context_saveas: "Salva {{var0}} come ...",
+ context_searchwith: "Cerca {{var0}} con ...",
+ context_view: "Visualizza {{var0}}",
+ submenu_disabled: "Tutti disabilitati",
+ toast_copy_failed: "{{var0}} non può essere copiato negli appunti",
+ toast_copy_success: "{{var0}} è stato copiato negli appunti",
+ toast_save_failed: "Impossibile salvare {{var0}} in '{{var1}}'",
+ toast_save_success: "{{var0}} è stato salvato in '{{var1}}'"
+ };
+ case "ja": // Japanese
+ return {
+ context_copy: "{{var0}} をコピーします",
+ context_lenssize: "レンズサイズ",
+ context_saveas: "{{var0}} を...として保存します",
+ context_searchwith: "{{var0}} を...で検索",
+ context_view: "{{var0}} を表示",
+ submenu_disabled: "すべて無効",
+ toast_copy_failed: "{{var0}} をクリップボードにコピーできませんでした",
+ toast_copy_success: "{{var0}} がクリップボードにコピーされました",
+ toast_save_failed: "{{var0}} を「'{{var1}}'」に保存できませんでした",
+ toast_save_success: "{{var0}} は「'{{var1}}'」に保存されました"
+ };
+ case "ko": // Korean
+ return {
+ context_copy: "{{var0}} 복사",
+ context_lenssize: "렌즈 크기",
+ context_saveas: "{{var0}} 을 다른 이름으로 저장 ...",
+ context_searchwith: "{{var0}} 검색 ...",
+ context_view: "{{var0}} 보기",
+ submenu_disabled: "모두 비활성화 됨",
+ toast_copy_failed: "{{var0}} 을 클립 보드에 복사 할 수 없습니다.",
+ toast_copy_success: "{{var0}} 이 클립 보드에 복사되었습니다.",
+ toast_save_failed: "{{var0}} 을 '{{var1}}'에 저장할 수 없습니다.",
+ toast_save_success: "{{var0}} 이 '{{var1}}'에 저장되었습니다."
+ };
+ case "lt": // Lithuanian
+ return {
+ context_copy: "Kopijuoti {{var0}}",
+ context_lenssize: "Objektyvo dydis",
+ context_saveas: "Išsaugoti '{{var0}}' kaip ...",
+ context_searchwith: "Ieškoti {{var0}} naudojant ...",
+ context_view: "Žiūrėti {{var0}}",
+ submenu_disabled: "Visi neįgalūs",
+ toast_copy_failed: "{{var0}} nepavyko nukopijuoti į mainų sritį",
+ toast_copy_success: "{{var0}} buvo nukopijuota į mainų sritį",
+ toast_save_failed: "Nepavyko išsaugoti {{var0}} aplanke '{{var1}}'",
+ toast_save_success: "{{var0}} išsaugotas aplanke '{{var1}}'"
+ };
+ case "nl": // Dutch
+ return {
+ context_copy: "Kopieer {{var0}}",
+ context_lenssize: "Lens Maat",
+ context_saveas: "Bewaar {{var0}} als ...",
+ context_searchwith: "Zoek {{var0}} met ...",
+ context_view: "Bekijk {{var0}}",
+ submenu_disabled: "Allemaal uitgeschakeld",
+ toast_copy_failed: "{{var0}} kan niet naar het klembord worden gekopieerd",
+ toast_copy_success: "{{var0}} is naar het klembord gekopieerd",
+ toast_save_failed: "{{var0}} kan niet worden opgeslagen in '{{var1}}'",
+ toast_save_success: "{{var0}} is opgeslagen in '{{var1}}'"
+ };
+ case "no": // Norwegian
+ return {
+ context_copy: "Kopier {{var0}}",
+ context_lenssize: "Linsestørrelse",
+ context_saveas: "Lagre {{var0}} som ...",
+ context_searchwith: "Søk på {{var0}} med ...",
+ context_view: "Vis {{var0}}",
+ submenu_disabled: "Alle funksjonshemmede",
+ toast_copy_failed: "{{var0}} kunne ikke kopieres til utklippstavlen",
+ toast_copy_success: "{{var0}} ble kopiert til utklippstavlen",
+ toast_save_failed: "{{var0}} kunne ikke lagres i '{{var1}}'",
+ toast_save_success: "{{var0}} ble lagret i '{{var1}}'"
+ };
+ case "pl": // Polish
+ return {
+ context_copy: "Kopiuj {{var0}}",
+ context_lenssize: "Rozmiar soczewki",
+ context_saveas: "Zapisz {{var0}} jako ...",
+ context_searchwith: "Wyszukaj {{var0}} za pomocą ...",
+ context_view: "Wyświetl {{var0}}",
+ submenu_disabled: "Wszystkie wyłączone",
+ toast_copy_failed: "Nie można skopiować {{var0}} do schowka",
+ toast_copy_success: "{{var0}} został skopiowany do schowka",
+ toast_save_failed: "Nie można zapisać {{var0}} w '{{var1}}'",
+ toast_save_success: "{{var0}} został zapisany w '{{var1}}'"
+ };
+ case "pt-BR": // Portuguese (Brazil)
+ return {
+ context_copy: "Copiar {{var0}}",
+ context_lenssize: "Tamanho da lente",
+ context_saveas: "Salvar {{var0}} como ...",
+ context_searchwith: "Pesquisar {{var0}} com ...",
+ context_view: "Visualizar {{var0}}",
+ submenu_disabled: "Todos desativados",
+ toast_copy_failed: "{{var0}} não pôde ser copiado para a área de transferência",
+ toast_copy_success: "{{var0}} foi copiado para a área de transferência",
+ toast_save_failed: "{{var0}} não pôde ser salvo em '{{var1}}'",
+ toast_save_success: "{{var0}} foi salvo em '{{var1}}'"
+ };
+ case "ro": // Romanian
+ return {
+ context_copy: "Copiați {{var0}}",
+ context_lenssize: "Dimensiunea obiectivului",
+ context_saveas: "Salvați {{var0}} ca ...",
+ context_searchwith: "Căutați {{var0}} cu ...",
+ context_view: "Vizualizați {{var0}}",
+ submenu_disabled: "Toate sunt dezactivate",
+ toast_copy_failed: "{{var0}} nu a putut fi copiat în clipboard",
+ toast_copy_success: "{{var0}} a fost copiat în clipboard",
+ toast_save_failed: "{{var0}} nu a putut fi salvat în '{{var1}}'",
+ toast_save_success: "{{var0}} a fost salvat în '{{var1}}'"
+ };
+ case "ru": // Russian
+ return {
+ context_copy: "Скопируйте {{var0}}",
+ context_lenssize: "Размер линзы",
+ context_saveas: "Сохранить {{var0}} как ...",
+ context_searchwith: "Искать {{var0}} с помощью ...",
+ context_view: "Посмотреть {{var0}}",
+ submenu_disabled: "Все отключены",
+ toast_copy_failed: "{{var0}} не удалось скопировать в буфер обмена",
+ toast_copy_success: "{{var0}} скопирован в буфер обмена",
+ toast_save_failed: "{{var0}} не удалось сохранить в '{{var1}}'",
+ toast_save_success: "{{var0}} был сохранен в '{{var1}}'"
+ };
+ case "sv": // Swedish
+ return {
+ context_copy: "Kopiera {{var0}}",
+ context_lenssize: "Linsstorlek",
+ context_saveas: "Spara {{var0}} som ...",
+ context_searchwith: "Sök {{var0}} med ...",
+ context_view: "Visa {{var0}}",
+ submenu_disabled: "Alla funktionshindrade",
+ toast_copy_failed: "{{var0}} kunde inte kopieras till Urklipp",
+ toast_copy_success: "{{var0}} kopierades till Urklipp",
+ toast_save_failed: "{{var0}} kunde inte sparas i '{{var1}}'",
+ toast_save_success: "{{var0}} sparades i '{{var1}}'"
+ };
+ case "th": // Thai
+ return {
+ context_copy: "คัดลอก{{var0}}",
+ context_lenssize: "ขนาดเลนส์",
+ context_saveas: "บันทึก{{var0}}เป็น ...",
+ context_searchwith: "ค้นหา{{var0}} ้วย ...",
+ context_view: "ดู{{var0}}",
+ submenu_disabled: "ปิดใช้งานทั้งหมด",
+ toast_copy_failed: "ไม่สามารถคัดลอก{{var0}}ไปยังคลิปบอร์ดได้",
+ toast_copy_success: "คัดลอก{{var0}}ไปยังคลิปบอร์ดแล้ว",
+ toast_save_failed: "ไม่สามารถบันทึก{{var0}}ใน '{{var1}}'",
+ toast_save_success: "{{var0}} ูกบันทึกใน '{{var1}}'"
+ };
+ case "tr": // Turkish
+ return {
+ context_copy: "{{var0}} kopyala",
+ context_lenssize: "Lens Boyutu",
+ context_saveas: "{{var0}} farklı kaydet ...",
+ context_searchwith: "{{var0}} şununla ara ...",
+ context_view: "{{var0}} görüntüle",
+ submenu_disabled: "Hepsi devre dışı",
+ toast_copy_failed: "{{var0}} panoya kopyalanamadı",
+ toast_copy_success: "{{var0}} panoya kopyalandı",
+ toast_save_failed: "{{var0}}, '{{var1}}' konumuna kaydedilemedi",
+ toast_save_success: "{{var0}}, '{{var1}}' konumuna kaydedildi"
+ };
+ case "uk": // Ukrainian
+ return {
+ context_copy: "Копіювати {{var0}}",
+ context_lenssize: "Розмір лінзи",
+ context_saveas: "Збережіть {{var0}} як ...",
+ context_searchwith: "Шукати {{var0}} за допомогою ...",
+ context_view: "Переглянути {{var0}}",
+ submenu_disabled: "Всі інваліди",
+ toast_copy_failed: "Не вдалося скопіювати {{var0}} у буфер обміну",
+ toast_copy_success: "{{var0}} скопійовано в буфер обміну",
+ toast_save_failed: "Не вдалося зберегти {{var0}} у '{{var1}}'",
+ toast_save_success: "{{var0}} було збережено у '{{var1}}'"
+ };
+ case "vi": // Vietnamese
+ return {
+ context_copy: "Sao chép {{var0}}",
+ context_lenssize: "Kích thước ống kính",
+ context_saveas: "Lưu {{var0}} dưới dạng ...",
+ context_searchwith: "Tìm kiếm {{var0}} bằng ...",
+ context_view: "Xem {{var0}}",
+ submenu_disabled: "Tất cả đã bị vô hiệu hóa",
+ toast_copy_failed: "Không thể sao chép {{var0}} vào khay nhớ tạm",
+ toast_copy_success: "{{var0}} đã được sao chép vào khay nhớ tạm",
+ toast_save_failed: "Không thể lưu {{var0}} trong '{{var1}}'",
+ toast_save_success: "{{var0}} đã được lưu trong '{{var1}}'"
+ };
+ case "zh-CN": // Chinese (China)
+ return {
+ context_copy: "复制 {{var0}}",
+ context_lenssize: "缩放尺寸",
+ context_saveas: "将 {{var0}} 另存到...",
+ context_searchwith: "搜索 {{var0}} 使用...",
+ context_view: "查看 {{var0}}",
+ submenu_disabled: "全部禁用",
+ toast_copy_failed: "{{var0}} 无法复制到剪贴板",
+ toast_copy_success: "{{var0}} 已复制到剪贴板",
+ toast_save_failed: "{{var0}} 无法保存到'{{var1}}'",
+ toast_save_success: "{{var0}} 已保存到'{{var1}}'"
+ };
+ case "zh-TW": // Chinese (Taiwan)
+ return {
+ context_copy: "複製 {{var0}}",
+ context_lenssize: "縮放尺寸",
+ context_saveas: "將 {{var0}} 另存到...",
+ context_searchwith: "搜尋 {{var0}} 使用...",
+ context_view: "預覽 {{var0}}",
+ submenu_disabled: "全部關閉",
+ toast_copy_failed: "{{var0}} 無法複製到剪貼簿",
+ toast_copy_success: "{{var0}} 已複製到剪貼簿",
+ toast_save_failed: "{{var0}} 無法儲存到 '{{var1}}'",
+ toast_save_success: "{{var0}} 已儲存到 '{{var1}}'"
+ };
+ default: // English
+ return {
+ context_copy: "Copy {{var0}}",
+ context_lenssize: "Lens Size",
+ context_saveas: "Save {{var0}} as ...",
+ context_searchwith: "Search {{var0}} with ...",
+ context_view: "View {{var0}}",
+ submenu_disabled: "All disabled",
+ toast_copy_failed: "{{var0}} could not be copied to the Clipboard",
+ toast_copy_success: "{{var0}} was copied to the Clipboard",
+ toast_save_failed: "{{var0}} could not be saved in '{{var1}}'",
+ toast_save_success: "{{var0}} was saved in '{{var1}}'"
+ };
+ }
+ }
+ };
+ })(window.BDFDB_Global.PluginUtils.buildPlugin(config));
+})();
diff --git a/.config/BetterDiscord/plugins/InvisibleTyping.config.json b/.config/BetterDiscord/plugins/InvisibleTyping.config.json
new file mode 100755
index 0000000..700600f
--- /dev/null
+++ b/.config/BetterDiscord/plugins/InvisibleTyping.config.json
@@ -0,0 +1,10 @@
+{
+ "currentVersionInfo": {
+ "version": "1.2.2",
+ "hasShownChangelog": true
+ },
+ "settings": {
+ "exclude": [],
+ "autoEnable": false
+ }
+}
\ No newline at end of file
diff --git a/.config/BetterDiscord/plugins/InvisibleTyping.plugin.js b/.config/BetterDiscord/plugins/InvisibleTyping.plugin.js
new file mode 100755
index 0000000..591592b
--- /dev/null
+++ b/.config/BetterDiscord/plugins/InvisibleTyping.plugin.js
@@ -0,0 +1,690 @@
+/**
+ * @name InvisibleTyping
+ * @author Strencher
+ * @version 1.2.2
+ * @description Enhanced version of silent typing.
+ * @source https://github.com/Strencher/BetterDiscordStuff/blob/master/InvisibleTyping/InvisibleTyping.plugin.js
+ * @updateUrl https://raw.githubusercontent.com/Strencher/BetterDiscordStuff/master/InvisibleTyping/InvisibleTyping.plugin.js
+ */
+/*@cc_on
+@if (@_jscript)
+
+ // Offer to self-install for clueless users that try to run this directly.
+ var shell = WScript.CreateObject("WScript.Shell");
+ var fs = new ActiveXObject("Scripting.FileSystemObject");
+ var pathPlugins = shell.ExpandEnvironmentStrings("%APPDATA%\BetterDiscord\plugins");
+ var pathSelf = WScript.ScriptFullName;
+ // Put the user at ease by addressing them in the first person
+ 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);
+ if (fs.GetParentFolderName(pathSelf) === fs.GetAbsolutePathName(pathPlugins)) {
+ shell.Popup("I'm in the correct folder already.", 0, "I'm already installed", 0x40);
+ } else if (!fs.FolderExists(pathPlugins)) {
+ shell.Popup("I can't find the BetterDiscord plugins folder.\nAre you sure it's even installed?", 0, "Can't install myself", 0x10);
+ } else if (shell.Popup("Should I copy myself to BetterDiscord's plugins folder for you?", 0, "Do you need some help?", 0x34) === 6) {
+ fs.CopyFile(pathSelf, fs.BuildPath(pathPlugins, fs.GetFileName(pathSelf)), true);
+ // Show the user where to put plugins in the future
+ shell.Exec("explorer " + pathPlugins);
+ shell.Popup("I'm installed!", 0, "Successfully installed", 0x40);
+ }
+ WScript.Quit();
+@else@*/
+/* Generated Code */
+const config = {
+ "info": {
+ "name": "InvisibleTyping",
+ "authors": [{
+ "name": "Strencher",
+ "discord_id": "415849376598982656",
+ "github_username": "Strencher",
+ "twitter_username": "Strencher3"
+ }],
+ "version": "1.2.2",
+ "description": "Enhanced version of silent typing.",
+ "github": "https://github.com/Strencher/BetterDiscordStuff/blob/master/InvisibleTyping/InvisibleTyping.plugin.js",
+ "github_raw": "https://raw.githubusercontent.com/Strencher/BetterDiscordStuff/master/InvisibleTyping/InvisibleTyping.plugin.js"
+ },
+ "build": {
+ "copy": true,
+ "zlibrary": true,
+ "production": true,
+ "release": {
+ "source": true,
+ "readme": true,
+ "public": true
+ }
+ },
+ "changelog": [{
+ "type": "fixed",
+ "title": "Fixed",
+ "items": [
+ "Fixed latest discord update."
+ ]
+ }]
+};
+function buildPlugin([BasePlugin, PluginApi]) {
+ const module = {
+ exports: {}
+ };
+ (() => {
+ "use strict";
+ class StyleLoader {
+ static styles = "";
+ static element = null;
+ static append(module, css) {
+ this.styles += `/* ${module} */\n${css}`;
+ }
+ static inject(name = config.info.name) {
+ if (this.element) this.element.remove();
+ this.element = document.head.appendChild(Object.assign(document.createElement("style"), {
+ id: name,
+ textContent: this.styles
+ }));
+ }
+ static remove() {
+ if (this.element) {
+ this.element.remove();
+ this.element = null;
+ }
+ }
+ }
+ function ___createMemoize___(instance, name, value) {
+ value = value();
+ Object.defineProperty(instance, name, {
+ value,
+ configurable: true
+ });
+ return value;
+ };
+ const Modules = {
+ get 'react-spring'() {
+ return ___createMemoize___(this, 'react-spring', () => BdApi.findModuleByProps('useSpring'))
+ },
+ '@discord/utils': {
+ get 'joinClassNames'() {
+ return ___createMemoize___(this, 'joinClassNames', () => BdApi.findModule(e => e.toString().indexOf('return e.join(" ")') > 200))
+ },
+ get 'useForceUpdate'() {
+ return ___createMemoize___(this, 'useForceUpdate', () => BdApi.findModuleByProps('useForceUpdate')?.useForceUpdate)
+ },
+ get 'Logger'() {
+ return ___createMemoize___(this, 'Logger', () => BdApi.findModuleByProps('setLogFn')?.default)
+ },
+ get 'Navigation'() {
+ return ___createMemoize___(this, 'Navigation', () => BdApi.findModuleByProps('replaceWith', 'currentRouteIsPeekView'))
+ }
+ },
+ '@discord/components': {
+ get 'Tooltip'() {
+ return ___createMemoize___(this, 'Tooltip', () => BdApi.findModuleByDisplayName('Tooltip'))
+ },
+ get 'TooltipContainer'() {
+ return ___createMemoize___(this, 'TooltipContainer', () => BdApi.findModuleByProps('TooltipContainer')?.TooltipContainer)
+ },
+ get 'TextInput'() {
+ return ___createMemoize___(this, 'TextInput', () => BdApi.findModuleByDisplayName('TextInput'))
+ },
+ get 'SlideIn'() {
+ return ___createMemoize___(this, 'SlideIn', () => BdApi.findModuleByDisplayName('SlideIn'))
+ },
+ get 'SettingsNotice'() {
+ return ___createMemoize___(this, 'SettingsNotice', () => BdApi.findModuleByDisplayName('SettingsNotice'))
+ },
+ get 'TransitionGroup'() {
+ return ___createMemoize___(this, 'TransitionGroup', () => BdApi.findModuleByDisplayName('TransitionGroup'))
+ },
+ get 'Button'() {
+ return ___createMemoize___(this, 'Button', () => BdApi.findModule(m => 'DropdownSizes' in m && typeof(m) === 'function'))
+ },
+ get 'Popout'() {
+ return ___createMemoize___(this, 'Popout', () => BdApi.findModuleByDisplayName('Popout'))
+ },
+ get 'Flex'() {
+ return ___createMemoize___(this, 'Flex', () => BdApi.findModuleByDisplayName('Flex'))
+ },
+ get 'Text'() {
+ return ___createMemoize___(this, 'Text', () => BdApi.findModuleByDisplayName('LegacyText'))
+ },
+ get 'Card'() {
+ return ___createMemoize___(this, 'Card', () => BdApi.findModuleByDisplayName('Card'))
+ }
+ },
+ '@discord/modules': {
+ get 'Dispatcher'() {
+ return ___createMemoize___(this, 'Dispatcher', () => BdApi.findModuleByProps('dispatch', 'isDispatching'))
+ },
+ get 'ComponentDispatcher'() {
+ return ___createMemoize___(this, 'ComponentDispatcher', () => BdApi.findModuleByProps('ComponentDispatch')?.ComponentDispatch)
+ },
+ get 'EmojiUtils'() {
+ return ___createMemoize___(this, 'EmojiUtils', () => BdApi.findModuleByProps('uploadEmoji'))
+ },
+ get 'PermissionUtils'() {
+ return ___createMemoize___(this, 'PermissionUtils', () => BdApi.findModuleByProps('computePermissions', 'canManageUser'))
+ },
+ get 'DMUtils'() {
+ return ___createMemoize___(this, 'DMUtils', () => BdApi.findModuleByProps('openPrivateChannel'))
+ }
+ },
+ '@discord/stores': {
+ get 'Messages'() {
+ return ___createMemoize___(this, 'Messages', () => BdApi.findModuleByProps('getMessage', 'getMessages'))
+ },
+ get 'Channels'() {
+ return ___createMemoize___(this, 'Channels', () => BdApi.findModuleByProps('getChannel', 'getDMFromUserId'))
+ },
+ get 'Guilds'() {
+ return ___createMemoize___(this, 'Guilds', () => BdApi.findModuleByProps('getGuild'))
+ },
+ get 'SelectedGuilds'() {
+ return ___createMemoize___(this, 'SelectedGuilds', () => BdApi.findModuleByProps('getGuildId', 'getLastSelectedGuildId'))
+ },
+ get 'SelectedChannels'() {
+ return ___createMemoize___(this, 'SelectedChannels', () => BdApi.findModuleByProps('getChannelId', 'getLastSelectedChannelId'))
+ },
+ get 'Info'() {
+ return ___createMemoize___(this, 'Info', () => BdApi.findModuleByProps('getSessionId'))
+ },
+ get 'Status'() {
+ return ___createMemoize___(this, 'Status', () => BdApi.findModuleByProps('getStatus', 'getActivities', 'getState'))
+ },
+ get 'Users'() {
+ return ___createMemoize___(this, 'Users', () => BdApi.findModuleByProps('getUser', 'getCurrentUser'))
+ },
+ get 'SettingsStore'() {
+ return ___createMemoize___(this, 'SettingsStore', () => BdApi.findModuleByProps('afkTimeout', 'status'))
+ },
+ get 'UserProfile'() {
+ return ___createMemoize___(this, 'UserProfile', () => BdApi.findModuleByProps('getUserProfile'))
+ },
+ get 'Members'() {
+ return ___createMemoize___(this, 'Members', () => BdApi.findModuleByProps('getMember'))
+ },
+ get 'Activities'() {
+ return ___createMemoize___(this, 'Activities', () => BdApi.findModuleByProps('getActivities'))
+ },
+ get 'Games'() {
+ return ___createMemoize___(this, 'Games', () => BdApi.findModuleByProps('getGame', 'games'))
+ },
+ get 'Auth'() {
+ return ___createMemoize___(this, 'Auth', () => BdApi.findModuleByProps('getId', 'isGuest'))
+ },
+ get 'TypingUsers'() {
+ return ___createMemoize___(this, 'TypingUsers', () => BdApi.findModuleByProps('isTyping'))
+ }
+ },
+ '@discord/actions': {
+ get 'ProfileActions'() {
+ return ___createMemoize___(this, 'ProfileActions', () => BdApi.findModuleByProps('fetchProfile'))
+ },
+ get 'GuildActions'() {
+ return ___createMemoize___(this, 'GuildActions', () => BdApi.findModuleByProps('requestMembersById'))
+ }
+ },
+ get '@discord/i18n'() {
+ return ___createMemoize___(this, '@discord/i18n', () => BdApi.findModule(m => m.Messages?.CLOSE && typeof(m.getLocale) === 'function'))
+ },
+ get '@discord/constants'() {
+ return ___createMemoize___(this, '@discord/constants', () => BdApi.findModuleByProps('API_HOST'))
+ },
+ get '@discord/contextmenu'() {
+ return ___createMemoize___(this, '@discord/contextmenu', () => {
+ const ctx = Object.assign({}, BdApi.findModuleByProps('openContextMenu'), BdApi.findModuleByProps('MenuItem'));
+ ctx.Menu = ctx.default;
+ return ctx;
+ })
+ },
+ get '@discord/forms'() {
+ return ___createMemoize___(this, '@discord/forms', () => BdApi.findModuleByProps('FormItem'))
+ },
+ get '@discord/scrollbars'() {
+ return ___createMemoize___(this, '@discord/scrollbars', () => BdApi.findModuleByProps('ScrollerAuto'))
+ },
+ get '@discord/native'() {
+ return ___createMemoize___(this, '@discord/native', () => BdApi.findModuleByProps('requireModule'))
+ },
+ get '@discord/flux'() {
+ return ___createMemoize___(this, '@discord/flux', () => Object.assign({}, BdApi.findModuleByProps('useStateFromStores').default, BdApi.findModuleByProps('useStateFromStores')))
+ },
+ get '@discord/modal'() {
+ return ___createMemoize___(this, '@discord/modal', () => Object.assign({}, BdApi.findModuleByProps('ModalRoot'), BdApi.findModuleByProps('openModal', 'closeAllModals')))
+ },
+ get '@discord/connections'() {
+ return ___createMemoize___(this, '@discord/connections', () => BdApi.findModuleByProps('get', 'isSupported', 'map'))
+ },
+ get '@discord/sanitize'() {
+ return ___createMemoize___(this, '@discord/sanitize', () => BdApi.findModuleByProps('stringify', 'parse', 'encode'))
+ },
+ get '@discord/icons'() {
+ return ___createMemoize___(this, '@discord/icons', () => BdApi.findAllModules(m => m.displayName && ~m.toString().indexOf('currentColor')).reduce((icons, icon) => (icons[icon.displayName] = icon, icons), {}))
+ },
+ '@discord/classes': {
+ get 'Timestamp'() {
+ return ___createMemoize___(this, 'Timestamp', () => BdApi.findModuleByPrototypes('toDate', 'month'))
+ },
+ get 'Message'() {
+ return ___createMemoize___(this, 'Message', () => BdApi.findModuleByPrototypes('getReaction', 'isSystemDM'))
+ },
+ get 'User'() {
+ return ___createMemoize___(this, 'User', () => BdApi.findModuleByPrototypes('tag'))
+ },
+ get 'Channel'() {
+ return ___createMemoize___(this, 'Channel', () => BdApi.findModuleByPrototypes('isOwner', 'isCategory'))
+ }
+ }
+ };
+ var __webpack_modules__ = {
+ 646: (module, __webpack_exports__, __webpack_require__) => {
+ __webpack_require__.d(__webpack_exports__, {
+ Z: () => __WEBPACK_DEFAULT_EXPORT__
+ });
+ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(645);
+ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_0___default = __webpack_require__.n(_node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_0__);
+ var ___CSS_LOADER_EXPORT___ = _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_0___default()((function(i) {
+ return i[1];
+ }));
+ ___CSS_LOADER_EXPORT___.push([module.id, ".InvisibleTyping-settings-panel{color:#ddd}", ""]);
+ ___CSS_LOADER_EXPORT___.locals = {
+ panel: "InvisibleTyping-settings-panel"
+ };
+ StyleLoader.append(module.id, ___CSS_LOADER_EXPORT___.toString());
+ const __WEBPACK_DEFAULT_EXPORT__ = Object.assign(___CSS_LOADER_EXPORT___, ___CSS_LOADER_EXPORT___.locals);
+ },
+ 166: (module, __webpack_exports__, __webpack_require__) => {
+ __webpack_require__.d(__webpack_exports__, {
+ Z: () => __WEBPACK_DEFAULT_EXPORT__
+ });
+ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(645);
+ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_0___default = __webpack_require__.n(_node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_0__);
+ var ___CSS_LOADER_EXPORT___ = _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_0___default()((function(i) {
+ return i[1];
+ }));
+ ___CSS_LOADER_EXPORT___.push([module.id, ".InvisibleTyping-typingButton-invisibleTypingButton svg{color:var(--interactive-normal);overflow:visible}.InvisibleTyping-typingButton-invisibleTypingButton .InvisibleTyping-typingButton-disabledStrokeThrough{position:absolute;transform:translateX(-15px) translateY(530px) rotate(-45deg)}.InvisibleTyping-typingButton-invisibleTypingButton{margin-top:3px;background:rgba(0,0,0,0)}.InvisibleTyping-typingButton-invisibleTypingButton:hover:not(.InvisibleTyping-typingButton-disabled) svg{color:var(--interactive-hover)}.InvisibleTyping-typingButton-invisibleTypingTooltip{display:inline-flex}", ""]);
+ ___CSS_LOADER_EXPORT___.locals = {
+ invisibleTypingButton: "InvisibleTyping-typingButton-invisibleTypingButton",
+ disabledStrokeThrough: "InvisibleTyping-typingButton-disabledStrokeThrough",
+ disabled: "InvisibleTyping-typingButton-disabled",
+ invisibleTypingTooltip: "InvisibleTyping-typingButton-invisibleTypingTooltip"
+ };
+ StyleLoader.append(module.id, ___CSS_LOADER_EXPORT___.toString());
+ const __WEBPACK_DEFAULT_EXPORT__ = Object.assign(___CSS_LOADER_EXPORT___, ___CSS_LOADER_EXPORT___.locals);
+ },
+ 645: module => {
+ module.exports = function(cssWithMappingToString) {
+ var list = [];
+ list.toString = function toString() {
+ return this.map((function(item) {
+ var content = cssWithMappingToString(item);
+ if (item[2]) return "@media ".concat(item[2], " {").concat(content, "}");
+ return content;
+ })).join("");
+ };
+ list.i = function(modules, mediaQuery, dedupe) {
+ if ("string" === typeof modules) modules = [
+ [null, modules, ""]
+ ];
+ var alreadyImportedModules = {};
+ if (dedupe)
+ for (var i = 0; i < this.length; i++) {
+ var id = this[i][0];
+ if (null != id) alreadyImportedModules[id] = true;
+ }
+ for (var _i = 0; _i < modules.length; _i++) {
+ var item = [].concat(modules[_i]);
+ if (dedupe && alreadyImportedModules[item[0]]) continue;
+ if (mediaQuery)
+ if (!item[2]) item[2] = mediaQuery;
+ else item[2] = "".concat(mediaQuery, " and ").concat(item[2]);
+ list.push(item);
+ }
+ };
+ return list;
+ };
+ },
+ 113: module => {
+ module.exports = BdApi.React;
+ }
+ };
+ var __webpack_module_cache__ = {};
+ function __webpack_require__(moduleId) {
+ var cachedModule = __webpack_module_cache__[moduleId];
+ if (void 0 !== cachedModule) return cachedModule.exports;
+ var module = __webpack_module_cache__[moduleId] = {
+ id: moduleId,
+ exports: {}
+ };
+ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
+ return module.exports;
+ }
+ (() => {
+ __webpack_require__.n = module => {
+ var getter = module && module.__esModule ? () => module["default"] : () => module;
+ __webpack_require__.d(getter, {
+ a: getter
+ });
+ return getter;
+ };
+ })();
+ (() => {
+ __webpack_require__.d = (exports, definition) => {
+ for (var key in definition)
+ if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) Object.defineProperty(exports, key, {
+ enumerable: true,
+ get: definition[key]
+ });
+ };
+ })();
+ (() => {
+ __webpack_require__.o = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);
+ })();
+ (() => {
+ __webpack_require__.r = exports => {
+ if ("undefined" !== typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports, Symbol.toStringTag, {
+ value: "Module"
+ });
+ Object.defineProperty(exports, "__esModule", {
+ value: true
+ });
+ };
+ })();
+ var __webpack_exports__ = {};
+ (() => {
+ __webpack_require__.r(__webpack_exports__);
+ __webpack_require__.d(__webpack_exports__, {
+ default: () => InvisibleTyping
+ });
+ const external_PluginApi_namespaceObject = PluginApi;
+ const external_BasePlugin_namespaceObject = BasePlugin;
+ var external_BasePlugin_default = __webpack_require__.n(external_BasePlugin_namespaceObject);
+ const utils_namespaceObject = Modules["@discord/utils"];
+ const components_namespaceObject = Modules["@discord/components"];
+ var external_BdApi_React_ = __webpack_require__(113);
+ var external_BdApi_React_default = __webpack_require__.n(external_BdApi_React_);
+ const flux_namespaceObject = Modules["@discord/flux"];
+ const modules_namespaceObject = Modules["@discord/modules"];
+ function _defineProperty(obj, key, value) {
+ if (key in obj) Object.defineProperty(obj, key, {
+ value,
+ enumerable: true,
+ configurable: true,
+ writable: true
+ });
+ else obj[key] = value;
+ return obj;
+ }
+ class SettingsManager extends flux_namespaceObject.Store {
+ constructor(pluginName, defaultSettings = {}) {
+ super(modules_namespaceObject.Dispatcher, {});
+ _defineProperty(this, "settings", void 0);
+ _defineProperty(this, "pluginName", void 0);
+ _defineProperty(this, "get", ((key, defaultValue) => this.settings[key] ?? defaultValue));
+ _defineProperty(this, "set", ((key, value) => {
+ this.settings[key] = value;
+ external_PluginApi_namespaceObject.PluginUtilities.saveSettings(this.pluginName, this.settings);
+ this.emitChange();
+ return value;
+ }));
+ this.pluginName = pluginName;
+ this.settings = external_PluginApi_namespaceObject.PluginUtilities.loadSettings(pluginName, defaultSettings);
+ }
+ }
+ const package_namespaceObject = JSON.parse('{"um":{"u2":"InvisibleTyping"}}');
+ const Settings = new SettingsManager(package_namespaceObject.um.u2);
+ const settings = Settings;
+ var typingButton = __webpack_require__(166);
+ function Keyboard({
+ disabled,
+ ...props
+ }) {
+ return external_BdApi_React_default().createElement("svg", Object.assign({}, props, {
+ width: "25",
+ height: "25",
+ viewBox: "0 0 576 512"
+ }), external_BdApi_React_default().createElement("path", {
+ fill: "currentColor",
+ d: "M528 448H48c-26.51 0-48-21.49-48-48V112c0-26.51 21.49-48 48-48h480c26.51 0 48 21.49 48 48v288c0 26.51-21.49 48-48 48zM128 180v-40c0-6.627-5.373-12-12-12H76c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm-336 96v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm-336 96v-40c0-6.627-5.373-12-12-12H76c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm288 0v-40c0-6.627-5.373-12-12-12H172c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h232c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12z"
+ }), disabled ? external_BdApi_React_default().createElement("rect", {
+ className: typingButton.Z.disabledStrokeThrough,
+ x: "10",
+ y: "10",
+ width: "600pt",
+ height: "70px",
+ fill: "#f04747"
+ }) : null);
+ }
+ const contextmenu_namespaceObject = Modules["@discord/contextmenu"];
+ const TypingModule = external_PluginApi_namespaceObject.WebpackModules.getByProps("startTyping");
+ const removeItem = function(array, item) {
+ while (array.includes(item)) array.splice(array.indexOf(item), 1);
+ return array;
+ };
+ function InvisibleTypingContextMenu({
+ channelId
+ }) {
+ const enabled = (0, flux_namespaceObject.useStateFromStores)([settings], (() => settings.get("autoEnable", true)));
+ return external_BdApi_React_default().createElement(contextmenu_namespaceObject.Menu, {
+ navId: "invisible-typing-context-menu",
+ onClose: contextmenu_namespaceObject.closeContextMenu
+ }, external_BdApi_React_default().createElement(contextmenu_namespaceObject.MenuItem, {
+ id: "globally-disable-or-enable-typing",
+ label: enabled ? "Disable Globally" : "Enable Globally",
+ action: () => {
+ settings.set("autoEnable", !enabled);
+ }
+ }), external_BdApi_React_default().createElement(contextmenu_namespaceObject.MenuItem, {
+ color: "colorDanger",
+ label: "Reset Config",
+ disabled: !settings.get("exclude", []).length,
+ id: "reset-config",
+ action: () => {
+ settings.set("exclude", []);
+ external_PluginApi_namespaceObject.Toasts.success("Successfully reset config for all channels.");
+ }
+ }));
+ }
+ function InvisibleTypingButton({
+ channel,
+ isEmpty
+ }) {
+ const enabled = (0, flux_namespaceObject.useStateFromStores)([settings], InvisibleTypingButton.getState.bind(this, channel.id));
+ const handleClick = (0, external_BdApi_React_.useCallback)((() => {
+ const excludeList = [...settings.get("exclude", [])];
+ if (excludeList.includes(channel.id)) {
+ removeItem(excludeList, channel.id);
+ TypingModule.stopTyping(channel.id);
+ } else {
+ excludeList.push(channel.id);
+ if (!isEmpty) TypingModule.startTyping(channel.id);
+ }
+ settings.set("exclude", excludeList);
+ }), [enabled]);
+ const handleContextMenu = (0, external_BdApi_React_.useCallback)((event => {
+ (0, contextmenu_namespaceObject.openContextMenu)(event, (() => external_BdApi_React_default().createElement(InvisibleTypingContextMenu, {
+ channelId: channel.id
+ })));
+ }), [enabled]);
+ return external_BdApi_React_default().createElement(components_namespaceObject.TooltipContainer, {
+ text: enabled ? "Typing Enabled" : "Typing Disabled",
+ position: "top",
+ className: typingButton.Z.invisibleTypingTooltip
+ }, external_BdApi_React_default().createElement("button", {
+ className: (0, utils_namespaceObject.joinClassNames)(typingButton.Z.invisibleTypingButton, {
+ enabled,
+ disabled: !enabled
+ }),
+ onClick: handleClick,
+ onContextMenu: handleContextMenu
+ }, external_BdApi_React_default().createElement(Keyboard, {
+ disabled: !enabled
+ })));
+ }
+ InvisibleTypingButton.getState = function(channelId) {
+ const isGlobal = settings.get("autoEnable", true);
+ const isExcluded = settings.get("exclude", []).includes(channelId);
+ if (isGlobal && isExcluded) return false;
+ if (isExcluded && !isGlobal) return true;
+ return isGlobal;
+ };
+ const external_StyleLoader_namespaceObject = StyleLoader;
+ var external_StyleLoader_default = __webpack_require__.n(external_StyleLoader_namespaceObject);
+ var React = __webpack_require__(113);
+ const createUpdateWrapper = (Component, valueProp = "value", changeProp = "onChange", valueIndex = 0) => props => {
+ const [value, setValue] = React.useState(props[valueProp]);
+ return React.createElement(Component, Object.assign({}, props, {
+ [valueProp]: value,
+ [changeProp]: (...args) => {
+ const value = args[valueIndex];
+ if ("function" === typeof props[changeProp]) props[changeProp](value);
+ setValue(value);
+ }
+ }));
+ };
+ const hooks_createUpdateWrapper = createUpdateWrapper;
+ var components_settings = __webpack_require__(646);
+ const SwitchItem = hooks_createUpdateWrapper(external_PluginApi_namespaceObject.WebpackModules.getByDisplayName("SwitchItem"));
+ function SettingsPanel() {
+ const disabledChannels = (0, flux_namespaceObject.useStateFromStores)([settings], (() => settings.get("exclude", [])));
+ return external_BdApi_React_default().createElement(external_BdApi_React_default().Fragment, null, external_BdApi_React_default().createElement(SwitchItem, {
+ note: "Automatically enables the typing indicator for each channel that isn't manually disabled.",
+ value: settings.get("autoEnable", true),
+ onChange: value => settings.set("autoEnable", value)
+ }, "Automatically enable"), external_BdApi_React_default().createElement(components_namespaceObject.Flex, {
+ justify: components_namespaceObject.Flex.Justify.END,
+ direction: components_namespaceObject.Flex.Direction.VERTICAL
+ }, external_BdApi_React_default().createElement("p", {
+ className: components_settings.Z.panel
+ }, "Current disabled channels: ", disabledChannels.length), external_BdApi_React_default().createElement(components_namespaceObject.Button, {
+ look: components_namespaceObject.Button.Looks.OUTLINED,
+ color: components_namespaceObject.Button.Colors.RED,
+ disabled: !Boolean(disabledChannels.length),
+ size: components_namespaceObject.Button.Sizes.SMALL,
+ onClick: () => {
+ settings.set("exclude", []);
+ }
+ }, "Reset")));
+ }
+ const constants_namespaceObject = Modules["@discord/constants"];
+ const stores_namespaceObject = Modules["@discord/stores"];
+ var InvisibleTyping_React = __webpack_require__(113);
+ function InvisibleTyping_defineProperty(obj, key, value) {
+ if (key in obj) Object.defineProperty(obj, key, {
+ value,
+ enumerable: true,
+ configurable: true,
+ writable: true
+ });
+ else obj[key] = value;
+ return obj;
+ }
+ const ChannelTextAreaContainer = external_PluginApi_namespaceObject.WebpackModules.find((m => "ChannelTextAreaContainer" === m?.type?.render?.displayName))?.type;
+ const ChannelTextAreaButtons = external_PluginApi_namespaceObject.WebpackModules.find((m => m.type && "ChannelTextAreaButtons" === m.type.displayName));
+ const DMChannels = [constants_namespaceObject.ChannelTypes.DM, constants_namespaceObject.ChannelTypes.GROUP_DM];
+ const canViewChannel = function(channel) {
+ if (!channel) return false;
+ if (DMChannels.includes(channel.type)) return true;
+ try {
+ return modules_namespaceObject.PermissionUtils.can(constants_namespaceObject.Permissions.SEND_MESSAGES, channel, stores_namespaceObject.Users.getCurrentUser());
+ } catch (error) {
+ external_PluginApi_namespaceObject.Logger.error("Failed to request permissions:", error);
+ return true;
+ }
+ };
+ class InvisibleTyping extends(external_BasePlugin_default()) {
+ static setUpdating(state) {
+ this._updating = state;
+ }
+ onStart() {
+ external_StyleLoader_default().inject();
+ external_PluginApi_namespaceObject.Utilities.suppressErrors(this.patchTextAreaButtons.bind(this), "textarea buttons patch")();
+ external_PluginApi_namespaceObject.Utilities.suppressErrors(this.patchStartTyping.bind(this), "start typing patch")();
+ }
+ getSettingsPanel() {
+ return InvisibleTyping_React.createElement(SettingsPanel, null);
+ }
+ async patchTextAreaButtons() {
+ const shouldShow = function(children, props) {
+ if ("profile_bio_input" === props.type?.analyticsName) return false;
+ if (!Array.isArray(children)) return false;
+ if (children.some((child => child && child.type === InvisibleTyping))) return false;
+ if (!canViewChannel(props.channel)) return false;
+ return true;
+ };
+ if (ChannelTextAreaButtons) {
+ external_PluginApi_namespaceObject.Patcher.after(ChannelTextAreaButtons, "type", ((_, [props], returnValue) => {
+ const children = returnValue && returnValue.props && returnValue.props.children;
+ if (!shouldShow(children, props)) return;
+ children.unshift(InvisibleTyping_React.createElement(InvisibleTypingButton, props));
+ }));
+ this.forceUpdate();
+ } else external_PluginApi_namespaceObject.Patcher.after(ChannelTextAreaContainer, "render", ((_, [props], returnValue) => {
+ const tree = external_PluginApi_namespaceObject.Utilities.findInReactTree(returnValue, (e => e?.className?.indexOf("buttons-") > -1));
+ if (!tree || !shouldShow(tree.children, props)) return returnValue;
+ tree.children.unshift(InvisibleTyping_React.createElement(InvisibleTypingButton, Object.assign({}, props, {
+ isEmpty: !!props.textValue
+ })));
+ }));
+ }
+ forceUpdate() {
+ if (InvisibleTyping._updating) return;
+ InvisibleTyping.setUpdating(true);
+ external_PluginApi_namespaceObject.Patcher.after(ChannelTextAreaContainer, "render", (function() {
+ const [, , returnValue] = arguments;
+ this.unpatch();
+ InvisibleTyping.setUpdating(false);
+ const buttons = external_PluginApi_namespaceObject.Utilities.findInReactTree(returnValue, (e => e && e.type === ChannelTextAreaButtons));
+ if (!buttons) return;
+ buttons.key = Math.random().toString();
+ }));
+ }
+ async patchStartTyping() {
+ const TypingModule = external_PluginApi_namespaceObject.WebpackModules.getByProps("startTyping");
+ external_PluginApi_namespaceObject.Patcher.instead(TypingModule, "startTyping", ((_, [channelId], originalMethod) => {
+ if (InvisibleTypingButton.getState(channelId)) originalMethod(channelId);
+ }));
+ }
+ onStop() {
+ external_PluginApi_namespaceObject.Patcher.unpatchAll();
+ external_StyleLoader_default().remove();
+ if (ChannelTextAreaButtons) this.forceUpdate();
+ }
+ }
+ InvisibleTyping_defineProperty(InvisibleTyping, "_updating", false);
+ })();
+ module.exports.LibraryPluginHack = __webpack_exports__;
+ })();
+ const PluginExports = module.exports.LibraryPluginHack;
+ return PluginExports?.__esModule ? PluginExports.default : PluginExports;
+}
+module.exports = window.hasOwnProperty("ZeresPluginLibrary") ?
+ buildPlugin(window.ZeresPluginLibrary.buildPlugin(config)) :
+ class {
+ getName() {
+ return config.info.name;
+ }
+ getAuthor() {
+ return config.info.authors.map(a => a.name).join(", ");
+ }
+ getDescription() {
+ return `${config.info.description}. __**ZeresPluginLibrary was not found! This plugin will not work!**__`;
+ }
+ getVersion() {
+ return config.info.version;
+ }
+ load() {
+ BdApi.showConfirmationModal(
+ "Library plugin is needed",
+ [`The library plugin needed for ${config.info.name} is missing. Please click Download to install it.`], {
+ confirmText: "Download",
+ cancelText: "Cancel",
+ onConfirm: () => {
+ require("request").get("https://rauenzi.github.io/BDPluginLibrary/release/0PluginLibrary.plugin.js", async (error, response, body) => {
+ if (error) return require("electron").shell.openExternal("https://betterdiscord.net/ghdl?url=https://raw.githubusercontent.com/rauenzi/BDPluginLibrary/master/release/0PluginLibrary.plugin.js");
+ await new Promise(r => require("fs").writeFile(require("path").join(BdApi.Plugins.folder, "0PluginLibrary.plugin.js"), body, r));
+ });
+ }
+ }
+ );
+ }
+ start() {}
+ stop() {}
+ };
+/*@end@*/
\ No newline at end of file
diff --git a/.config/BetterDiscord/plugins/NotificationSounds.config.json b/.config/BetterDiscord/plugins/NotificationSounds.config.json
new file mode 100755
index 0000000..4a6bb3a
--- /dev/null
+++ b/.config/BetterDiscord/plugins/NotificationSounds.config.json
@@ -0,0 +1,252 @@
+{
+ "all": {
+ "audios": {
+ "Nya": {
+ "Nya": "data:audio/mpeg;base64,//uQRAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWGluZwAAAA8AAAAUAAAhAAAXFxcXFycnJycnNTU1NTVCQkJCQk5OTk5bW1tbW1tlZWVlcnJycnJyfHx8fIiIiIiIk5OTk5Ofn5+fn5+rq6urtra2trbExMTExNHR0dHR0d3d3d3q6urq6vf39/f3//////8AAABQTEFNRTMuMTAwBLkAAAAAAAAAADUgJASuTQAB4AAAIQATpbuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//vAZAAAAUMAT3gAAAghgAmPAAABEAU/K6wwzEDQByYw9pjMAAIRM0h9NZrKEkC7y4PvE8u8mKh7UCA45yn+Ud6hoeEH/8ENH+//w+AAIEiqsef4QAHAfeCAJvKRAD4EcGAyc01OqX+/6z///D8VTaJSIALakFWsnEjCQC0JehL5XkBERXCLINwWEbwFGXy+YL3yIWsis8WOVZSH5KnhCbvwZ0M5j7bRm377GP8z7iGbnJnoYgQzDMBEXUTpPeQdOdez09e/uv5fq3P4+f/5/em+T7+72i3aCijFJmikcWkXDpf+UfAoIMlHiiiVa46Ro1fNst4FxQ87B8HNJS4OIgjzgh6z63k6JTJCfa5LdYV6BVnX/p/XX//R2f6P//K765LESBlyQtltus5CyWmCqTy1cZdxAS6wZSWrrUuQSPu58Trw589ViTkPo/ErkM1Zqtic4VV6xf8GLNfvdbQwNGLOv/t8mpHcEBcTETmt2/z+xUWaeETT0RGoZu+5VXEz/8/Xj5AtWk0b31by0/Lj53ax3mZXbTnPY9yhSZACEi6Dcxmq+Gr1NCgiASSZuQghK/rrggZ+2kWDgOaLw69CBBEERJr6jSSpSQyw8wJiO2htSzWAmnWjh5k1u51vjLN6gL2f6f/s/t/0M/+zxfsviETAULZRZK4cdgURMTAjHIY+jaKH0aSwYGFtAMRiMDQTgQSAyIWsVbXl8IhD+OpBimMFw7K7GXxB1IXeqVpmGuG5GH/okCog4cIcKRqYPDVeGBWXfivBI9J44C5qM4E3I1hIMJI+Uujz8c0LMslh2tLxvjR1Q8vNNSIr58vpMSub6bczz6eP93fxL57ymc6hzZ1AunG8egYY/FytRX//1uFbBztyoZarg6E5Op9L6sZXOXc2vufiz8n2/zvXvUPNh5oEKYzAggoQAAAAQ5HAwoFE87SBCsoeoAAaUQMZATzOBaiIhCf7/1fiX///+6Bk3IAE4FNN6ww1eDEBaZ9gYhMZAaM3rbx34M6FZnzBJJTV9X///1f9wnv1H6RBCaS6kQRABHZqyjI3QimKJYDMZoOk9QNOYQaU1gZdBTPC+i63hSDUVeBAAo8ABDqyWqdaO0x0DDqeUVCE+loznf6dSJwyJ3mYeKZuuGRginOgmQaZc0QPwKucnBJITFlC2Oi4eqNzgMlKqC0F5LfEKV/iJluq/+sSwszKphiVf7w2PZX94D9zr/TGOpHDw9lAdLpjWFhke//ER+/j4YGSrO/ptsjzzy1/zTMA2eBryAGACbrhgAAFVrO+7DPwYLgINIQKJzF1ijv5oC670Ln8vP6CQDd/5HjtsKWbUpt3sGMYu6O3AsRIljFCU+ZIEtCuhMVblE3mX/jNQEyypFIjYiVemilSVI7JqsRVvacseRSt40h3RAzszSQMoMQRgLVr2d9FROQQCBCTMmJBgAFLGpoLp0KBjQJwQcABAhlKOaQ6RSaiuWJLnVrWFdi8vZ5bUyC9CBQynPhoZ0muul3hGuDkFRFpTjr//Bi9jfVe1jp4vZjsat2wttLUgYdd8QIYAvAhZhoWDJH8P0ScSx7nO5pyp+IldLCjtFVsdvj3YggAAYcAKAIEgDg+KogEoi2UkMKAL630gHf/orA3F2mJv+pzXcMsYJZlVgLG7QYscGRArK9C4hIbbWX/+5Bk6wAVUVPSe1h5WikiKb9tIisZEV9NrSH+aGwEpqjDCIybMSpOSpc9yxKnVwk0TafF571YpDP2+QQ0x+y6KAMaQAqkrW1dZkhfVt1jlpzCNMhHRvkm1cs3fGy3Nd6v4qtF1nJvtAXY5L4PM8j7wG88Tjd8BBcWUU///RykJH5Ts51+ZmZm3zq2A6HEpGUbSiyaJMogdbJxBWAMKY7DIQ5TBQDMz5Mx0mbOI4k9UR/YW5oDRTyV8AAAABckAw7IK5Ccn9BW/cvFHS1vc6kgTu8edNGLaLw+PQrFERxErLkcmNFA+rql6oOQEwtFZiFJKXOyljAWcPrACwMnSOQ99sYXGiaXQogwfaEoKDQJsEOHNx4+qH7SkzBTD9Qku/ysgTjn//n+6szc400q3/82eZTWhZDhxihIQA5G5wnNCA0VsPDQtFLjo+crCdAzbA4RAAAqPoBQBMBr/UjzI0ZIioQAM7dEYgqhQkqQm/DENjAxETLF1jAY0FaqtIiM9DcBmMJ+zqaWSKPmSmX8De3lp9N9bJYx1ZZDGM73IhItFJX/+5BkzgA1bVfXewVnmhYhyk8kIg9QtVtl7KT1KDaA6DjBAAQc5N2ISFv8yohnXMG8RTwGOG1Kha29eXBaFgVDhe9///x+qY13OhP8qBRw0H//+lcxK5CyK0l8F5Z1uC/DkXgXCBCRUgfLvaSOdhOEtGnLoHK2gAVAAAAO8b07Boy3Xx1B+KLW4oqzec6AAfk6IoVBKRWBNq1TkpBZcFu8lOLUZM7inl1y9o7IEJbcg2PFT9QCVtXXywb/wsyUComYtAFUArEQyw02QRhNDFJAoYMlAGUKOJTctQtN/jg/NIRJj//+Hi+aGMOiv4tGD87//a/rzL/2+PxyDlGjQ4OMkeEpmxYuqjhax6g2BcaH2LOxPBQAALxdUu1//BmRySQoKYpkAAsj23CyVuZj4I9ei0VEIImPQyWSHQKrLHxVaUWmYJJiUSUEyGIx5lUQ2KitjuGFhnRA3DUXMCOiUJP05jKJYkW0bytngM8C0ZCnj6H5ZXiqPCPCUdaRqWjMenYdVrQ7//8x4aCTHPoinfpxQoRh77//+5iF3//3/omH4kH/+4Bk4QA0oVfX+y9D+hRBui4sDVURaWFl7CURoC2AqZDwAAbBYTFwI0NFUcKGUUOmQPEQp4j+HJJMnoHAAAA4AAABlINcBHf8JcpykG80wAAdb4wUCMYsMulLTuoJDPhJy4JiilqJMz7SGzQzMpOs+uRFx34nr95ze51t0Oom5lNAy+mrwPQxy0pQxGHga0rYe++dniUzXKV68vnBIPF49KNYOG3G5q5c+JkICamoSPiv5/9q3PujOJWShGHSW//8Gh+IkFRX8T/qq2pQkCYs04MCQYKDybZgOQOg+Hhtbevh4q1YbJxgAKiQAirlNVZaiXAAF0XVmAO9UddKMssSEUeyf2qosTC5Xxg5MN+Yko+2soh6EMkpo2x8qvgbZskHRs5WIL3RQ6hnBRLbQCFhTSWRTZCeXs0UpHhCCgcRZ4gm9f4KRcaMpvSfi6tL/9ax7cG3/bG2y3+9prAuI2KwkJnf/1I25F9EeeIZyP/7kGTmAHTOXNf7T0RqDyAqPmAAARORdWHssRioGYDo0LEABdFKpgJiggiOAADbDTHoAAAAAAAADQnDm3f////////4g1RN5d1cwQBZDgE4aJcuFHSsO9mWBR+NdY0SOqD0j72UXkgorXT3jrotDR3naShXcXj6Dgkm9CE8pTGVbUjpuTVo1LSCibVrkfHSNHQ/b9GrKb+tZ1lJ7t/4FgYCoQ2hBAqNf/2/6j9pu+/6m5Wv6MHxEscWC80Xo2ov/+OZtCIeYg4sdQqKSQYmCB4nHCAHJwiig+gDLAAOBFw0MDUu3//8ACjWNqr5OGd1VCKAEqFMmUTbQBmMrk0u8/5ompFjTnTBAJUmKyrZCAe1qbMBFFjd3AGGGC1I55FhKlgQ5lUsFlivYEeasSuYvw+UqEmSVaFyvWtKtcRjZrTwSiUyiUuHJkgKp1Rgt8MQwwRtIFkj2//KNmn39zKIsOMQ+42//9jb+xXwaaStO9V/////27/6lWnMTOeEtiRB2DgfZ08rHSWF6AAAAAAGgYJAoIqRd/////////IEvRFhrf/7cGT+gGRhV9l7LETKFKA6LiRAABKBbWPssRNgRh6oPKA+fFDFQAOkUObFNYkYkAS1tsRiXD468HyARJcEvskTKp1gltGNyyy5IA3zEPGg96mopeLyNaWUEslRSzDk7N/qitAaRiFGqLmuElKoXvKUYoMjpIOLhdvb61NOO3ur2dX7fXs2Fp13fmaM58ZvaKa9b/+rKkRYarmOv6UZyjWc7aOpho8ROAtg4EgAD4aLKNGDwtqB8X8EsAMpOOO1GU9a07TGtSJNoA7FkqCEeCCWgZYCE9kxYlwDn2vhiSJRd9+WLMgZy3RWpijuJzQPGeADOo2BSJBqJBImWkgIRI7UauWgeRbij9CqgaLm0GWuo0eKh+DoeBk0Ry6GPWTQlOp6nGNEa3AhrEr42WIelp/////Y9R4cSLL/+5Bk5YB03VtX+08z+hagKj5gAAES6Xdf7LCz4CkBKDmjAAUptGPX/xXvqdAxjmhJolVZDCiDhUpjeUEc9K8pCwAAAAAAAAEICSg0+bpkwogvzAmb4UEZ7WSgZPRwqhWQBINZC0K5YJpmMLkaCwYfjgyGIUoTb5aJsJfbQ13IrrbdN+TJmb8fqyOSw1TghCxooOhc+BHBsx6XZ3HgvwmFh1oYrXxvckRJhVGeaZA5DYI///Sk3XmavOlIJr/9Rl0sWqEyQpKzLU0DDxUI685JLOlRGMIP4PAABPAcBWXQmchjIHvIDhMcAAGTYoGPViQoAgNG8EsHI8I10kna2qdgTwQA37WplVFvYpL4k0heDxv4/5+Z5ae1696J0KZmZ+u26k8+PrpsfGhnVeUy43CHolnZcQ0zDuzqg1gyiREvjrMcfRfbIs/HIppau3MG73pvT//9KP6mdYw5X9/cMNXAwVrACAwVYmO4AcC+aoLBysZgRQAAABDB9BR+UG7uAAVwAfbpiyGxXKhBMPXCAdwiI5JQuugiVvZ2ttUcBvQFgH7/+3Bk+gB0lFvY+ylEygygOi48QAERPXdl7DEPICAAqbmAAAUqOEmq9sZoaUCoGCxwWFBBWoUui0kRm/+7vhR7c2H0JB+GsfDcdxkVnx0BiSD0L0jU5iTiVSB1zmovrqf0ZSSWbt6Jbh3Pcgud////l3+2+1LZ8cVfPGunaitLNitQu1UKMjM5lCzr3slWhwld5KeHgAFd4AoTTVHAtErMOUKGG6UygrnZkgRL1rTLsIB1ocdlg7hYMopWgR2km+H8srmg8Kh+3VhJUwNv2YPvf0PmnX2PyD2FMR+aReaGpmcxEsGA7nQkJO7pANkSJMdD81o7w3Ut5y/+7ouQIoHor2P//2722/59StGLqsZ61vRyUaF4RiohKY3zTHQ0+c7AHEeqxBpAAAAAAAADS73zVL24iTutUxXT//uAZOwA9J9a1/ssHWoIgFl0MGEBEh1zW+yZcoAJgOYAkIAEOoUPBOyjIzIKcBEVASFhUBMFiSfLuQ3D6wzKYJZnPGxmuNVwxKaVORUFduOVOSrX7URz0zevbzVqMNHqcSySOkBotZqv1K0Qiky5RzdAQAOFHQQbkJmnm+gOaGN/7ra08fH15/j//uV9+/vM39nH/dqhiX76oQrdxSfy89IPTRDZQUXAw0DgAMENgMW///////////////////6VwtiIURVf4A81SJQWrjgcSPYnGMZ4SdSLgjHUtBwExK0e4bVJA+MCv22wakRGZgkW6N3JglvvOvk576LzPe207lrXl+tWvaOicQnU8v9c9WISRKWT3N/4yG4rSRcu6b5bL1+aDURb5cc3EzfHeS8+t///e///9mW6De7zvyqe4DECuw+imd5lMuUpICzw+GxuOtgoglUoAAAAAAwAQM4yt7P///////zkhqXLPBD/+4Bk+IJ0mlzX+yw02ArgGb4IAAFR6Wth7LDRqFiA5VDzAAQLbxF6SAUg5VVIJWBTA8IslAC/6CrcUOSrmvl/HASQsV4W0yXsji9m6PIoOSu1Q6vddjFG40axuCc5u7e1Ob/1VsK0uFsmqH6rjY/M2qKn3odjrQ7QQj3deFv/5GxRhw8YPtGq5HfA8bxP/6rH/yZNLj2MFOG8pSgOD8wOyFD1zyBjCIHgefKmDqMxIHm5fTN44ISIAK8tlzlsd///////+hXFuVZQAm3YDrLUDMoIRgifRSmODD2QcEvkmDEQIgBTXrF35eu9Hxn7F9OCLicH5Yw2QVuvD8/1jQ8biFyyL9yZ7GqUv/1y61z7PqjGFOoEo/OHIzlilsYsC0VDtDzURFht/xiut3BbZDC02/lj44b//kbX/jZWXh5+P6bYWk4u5H8HFGPdeccI1q8OeU/JHxAgAAA/AAPAA3c6QaNf/qd//////dl8K//7gGT7AGSWXNh7LDR6FeAqHwwAARKRcWHsMRNoRICo+YAABIVVABT+gPEwMAMmJSr+PKi9jVxwJeYeAPEQJq2Taar3ipVJsIVO5r7z1HKLtoMiaFIKEwWYUYpCVZZOMZktU1h5W/XSSWSaNkOSbIBVNETCaCJFl7I5I5DKg+Wylm/wRNYha1LZfoIwQN+9l//t+/MhvKqpnIo4phAlRY7jDjocOFnAUQDgqOFwiINFsbIHIAAvAAAICs0KlVrdf///9Tf//U3NT3kATcOZqFAAffAzR5eQU8SnRoAgEEw2NwJ8zkTnKomnNFVrmkATcisJYDFvGOaOCVe1YGcLT1mdgghjejjrE3zMOK3VvfxiqoU1g9TAcHI8MkWeRcYqcUIPDIt+10zFirNdsLatYgnU4+a//79OH4T/eyVibivpZqhWiVHUISjCRxjUKGKHAkLI3kQHMUPVVaC9qwAADMABPQSAnbkFmu//I/////uAZPgARG5bV3ssRGoXwJovDEESEbFxW+wks+hvHqe9MB8Y///9Vv///4jsoc2MAJnmGBwm4hAspAPHHSi32pvYLTB0ElKRqqx1bhaDiLdlc+x+4wx+pSjOwkYUD8rZs0hX0dXD9Mua3ZzWWnmWtmfqkak/SxI6OBXixQdbHs7OQWhhnIedtM9OdKQ+WrnW9OermJNvVE5BitfR+pdGxwiYyt/2jTocpg8PykZ0dxMUEwcpkRmGkABR5h4GAACMRRJazzJn///9Dt/M///ZT3///8z//yp/////+CIcwkwnSsOKNzAEf+ovN0YUOMokLJrJ4gTNEg4BEjC6QVFFoatMgRjqCBFV04ZrLOUAiV6mhXokhqMrS5/+yVLRuUR20S6oIUDueTSgmFxajRwsIYCrB+HVqxVT7WYQbI1h/xNXyhRBzttBuYccNGC9dlx////9RVVMkEr0n6TTvQyHSj1FBsyvA88UMscDUPD/+4Bk9QJEaFvXewxD2hxAKn88AAERcW9V7DCz4KasJrxwC01qC4mFeS7Ay2AAAAfAYKICEnXM8PuEAf//xO7///3N///u//+othTCmQAa70F1uAVA1byqiJQjTBfkrDL4A4ODUqC6KkZUXBYgFDWCr2Yum7VWVdqykHdnFwjEY+1m91iI5qucPOnXYXmUbiiJjaXOMIqAOFgMCwfPSpY5vdhyH2ME8OKPPxEj7ruZIyBcYSqTowrX//+jUSQKxCx0jStR0y7LKEYSmF5wsgsceMPFbEUWYQYQaShLthAagcAjBpgHjZ1jhVhxH//od///1L///iV3//+IqsR6RDADfeSbVxPUC0gx0IRIiTBIDGTVCAgaNzJBIJoUdR2SmbmuVnrXHRcCejGbxCMbHpUIy5d2sA2QlR0pOH9OZnGEsbEsx8ynYiheshuwe0KIFesuuWbgcmg9lFewuE4yahbQHuii3aRRbZfv8z+xsv/7kGTqAESGXVX7LESoH+CaD2RGBBJRaVHssRLoeIFnfMwIAHFz5LBTGO4n/7bFFCw+iYZmQc88zP+8s99sJ7gtomhaFJNk9aHiVtFoAABAAAAegAAALtn8O81JDE1IXV5+/61CwOiP///KgF3/////////u6KraqW4ZhAFT+pzR0CKBECAYcVDgBxGEACAIXESyFQoq7LmfIPCgYyl20p6ZuW0kUfWHdSiX1lhnVv/TzzDKxVkmWb/hC40lZqpeSiWZSlzPLsCbE4oYZaxRwqVCCMGD8cUjj5x8t6dNMyvK8tSRCXd3465/3v3vs51SfeZD+vWbjc7+Zd2wpbZpWq/daBI1yQSxJgzo2j1EpwN4AAAYAUAAdASgDJMGAuh4GSp5P/+z///6af5ZVACXfubOcLqAwNR8MZKGTnjWCFiQ4YYELzlpLamUrQEs8SKXYzJ+oTG6V34fAFZCQuREySCTQ6jQgBEQCLoFURVUlYXIUi/33SzsSmmuwZIR6NH9laKTIaEDRsVpA8gRzphhbUkVQ3wyHi260fIgqJvjdfG7f/7kGT3gATUWlR7LET6K4LZT2QHWxKpdU3sJNdobwHnPR1ABN97b2f24Et5348zVbJrKiC2zohIE8jYo680oge6BZi6S53ErNhAYAAB0AAAAmOyMQDEbGPnapgmo//6k3////////+1yjwxTzdqxAAL/pNbgiGTENsI1TgNMcUQEBxgQIRQlq0mLMFKVtYRziimTNqkscCM1rTz8ULCJlQnVEKaaww2jNNsrrQEC8BTsv6mMMj05NTSVSwbZSpaWSyba7CEQD4h5R+omdctSeS1yxqnx0bRvJ7e4Lc064Z9MOm/XmXZpjd/hzUB1Xfs6j0SRr4g70Tsm4AgAAAhDCCkTzAuvDLFzPxgaAAyAvkNPf/VX////////6oisnatmHZAAW/2nlwAA5piARUhxDGWMA4Is0EJAUIWDgiGmBLGYE12KHrRwCotytD9cdQqC7t9olzn+fK9n0O6dEmfX712mvqkHU1vRcjRVSHlDhlpVMiBQSWQCoPjnEOKsVJOWmvdmioqbi37muLHw67tDXVqb+3cTMOwkIhIa5V6ml1dB//7gGT6gCTbWtJ7KTT6JACZr2MGERFxT0fspNOom4KmuZxoBF1LPagygAYAAAB2SBR2SEGL7jXDHGlSFFkiwJ/6LP//+6j/izv//1VNH3/XQOWNNpxgZAEFIhA0DdAMIKmkSyFagqJH6A0JzluTDzcHxV7Tyd+NzcoVJFkZGc1RpvCOka71oyQoVWdb3wNwOroUpgsvf600mJpqugjZjGLdyfLJlJbS2rQKqNjlaaks6FLQmnNFm6z4ooH4JQMvYuM2WrmlJydiGmwt8z/tnt7tT5umok1d7yUBVa2iIBBdiKmvzOTKMtqWYNKRSwGxJzsQsBUglC2LiAMHLeLf/FfxX/+b///S0kSqRaZmUQVY000kAv0mYWFl3IiIYINhwUVx5ytCmtKz1rTsKqp8ARRbhOQTKzRW55QhE5ExLp4m9FrPr6QxYpcVMf/HqeJUpUHVHi1BS0ipo4siaWNBndODQyDsciy9M2WAwCaj//uQZOYABClTUXssQ+onwOl9YeIlEP1RNawk0+jOA2a8/GQMMp69uVdyqpPhEUeojCnb/t1br+obO2KqCMvgxpBG5aZwlv8FTzAAABtIUwdLfl60oy9pfcSWXfCKrNRiVhcQ6ZjvNV48K2FMz+0MRiBomtFpFdnPhQuBX///3T//9vOZ/X9f7////4zyP//OK6HKudMfmH/mCfLaG1ff8GIhb8SsXWAEBIow0EkBRUHS8K01VI4g450PTlE6MOVmhOeRKaokieTmEA8kTPtMlipMlQyEyEcNoZIURFMilT1X3BkTIU4qMyxPaTjl9bolcS+RVjFEwu0CmwyB6xtpV2bNIbeSwyg4AFhr3IGq0RZa3jpKj//t9E0rdUulDAWGVkSVeuNlACSNaDFtBGAGCLpKDk0I8LhQH3Wa2mIvwsAm+VThLNGgH2lIImm0LwoBk0hnGVo8VMH+v8lkbIBNGQyr/////////TRVCmqscdUAAmkhsttACwwtkTDMAywGTjZyqAQpRkJYBJrHAYeqwMKCiQxqPHVBQOgqHRKFXBoO//uQZPAABBxIy/sMM+pUyRktYYVpEJkJJYwkccEwiWW9h6Tkw0e/o1aCP///////////Ih7S2Vl1xhAAYQsQyQ5TICHEeP0txRDOC9LyihNlWTl5AX0apn13sWKTMYUSGFQCNVUmAhEBQEeDYKoBoSlQFKgEYP//Klh50l/////////6VUxBTUUzLjEwMFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//tgRNmP8i8SRuMPGbhQYtjtPeMeAAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV"
+ }
+ },
+ "choices": {
+ "call_calling": {
+ "category": "---",
+ "focus": null,
+ "mute": null,
+ "sound": "---",
+ "volume": 100
+ },
+ "call_ringing": {
+ "category": "---",
+ "focus": null,
+ "mute": null,
+ "sound": "---",
+ "volume": 100
+ },
+ "deafen": {
+ "category": "---",
+ "focus": null,
+ "mute": false,
+ "sound": "---",
+ "volume": 100
+ },
+ "disconnect": {
+ "category": "---",
+ "focus": null,
+ "mute": false,
+ "sound": "---",
+ "volume": 100
+ },
+ "ddr-down": {
+ "category": "---",
+ "focus": null,
+ "mute": false,
+ "sound": "---",
+ "volume": 100
+ },
+ "ddr-left": {
+ "category": "---",
+ "focus": null,
+ "mute": false,
+ "sound": "---",
+ "volume": 100
+ },
+ "ddr-right": {
+ "category": "---",
+ "focus": null,
+ "mute": false,
+ "sound": "---",
+ "volume": 100
+ },
+ "ddr-up": {
+ "category": "---",
+ "focus": null,
+ "mute": false,
+ "sound": "---",
+ "volume": 100
+ },
+ "reconnect": {
+ "category": "---",
+ "focus": null,
+ "mute": false,
+ "sound": "---",
+ "volume": 100
+ },
+ "message1": {
+ "category": "Nya",
+ "focus": null,
+ "mute": true,
+ "sound": "Nya",
+ "volume": 100
+ },
+ "dm": {
+ "category": "Nya",
+ "focus": true,
+ "mute": true,
+ "sound": "Nya",
+ "volume": 100
+ },
+ "mentioned": {
+ "category": "Nya",
+ "focus": true,
+ "mute": true,
+ "sound": "Nya",
+ "volume": 100
+ },
+ "everyone": {
+ "category": "Nya",
+ "focus": true,
+ "mute": true,
+ "sound": "Nya",
+ "volume": 100
+ },
+ "here": {
+ "category": "Nya",
+ "focus": true,
+ "mute": true,
+ "sound": "Nya",
+ "volume": 100
+ },
+ "reply": {
+ "category": "Nya",
+ "focus": true,
+ "mute": true,
+ "sound": "Nya",
+ "volume": 100
+ },
+ "role": {
+ "category": "Nya",
+ "focus": true,
+ "mute": true,
+ "sound": "Nya",
+ "volume": 100
+ },
+ "mute": {
+ "category": "---",
+ "focus": null,
+ "mute": false,
+ "sound": "---",
+ "volume": 100
+ },
+ "ptt_start": {
+ "category": "---",
+ "focus": null,
+ "mute": false,
+ "sound": "---",
+ "volume": 100
+ },
+ "ptt_stop": {
+ "category": "---",
+ "focus": null,
+ "mute": false,
+ "sound": "---",
+ "volume": 100
+ },
+ "stream_ended": {
+ "category": "---",
+ "focus": null,
+ "mute": false,
+ "sound": "---",
+ "volume": 100
+ },
+ "stream_started": {
+ "category": "---",
+ "focus": null,
+ "mute": false,
+ "sound": "---",
+ "volume": 100
+ },
+ "stream_user_joined": {
+ "category": "---",
+ "focus": null,
+ "mute": false,
+ "sound": "---",
+ "volume": 100
+ },
+ "stream_user_left": {
+ "category": "---",
+ "focus": null,
+ "mute": false,
+ "sound": "---",
+ "volume": 100
+ },
+ "undeafen": {
+ "category": "---",
+ "focus": null,
+ "mute": false,
+ "sound": "---",
+ "volume": 100
+ },
+ "unmute": {
+ "category": "---",
+ "focus": null,
+ "mute": false,
+ "sound": "---",
+ "volume": 100
+ },
+ "user_join": {
+ "category": "---",
+ "focus": null,
+ "mute": false,
+ "sound": "---",
+ "volume": 100
+ },
+ "user_leave": {
+ "category": "---",
+ "focus": null,
+ "mute": false,
+ "sound": "---",
+ "volume": 100
+ },
+ "user_moved": {
+ "category": "---",
+ "focus": null,
+ "mute": false,
+ "sound": "---",
+ "volume": 100
+ },
+ "success": {
+ "category": "---",
+ "focus": null,
+ "mute": false,
+ "sound": "---",
+ "volume": 100
+ },
+ "groupdm": {
+ "category": "---",
+ "focus": true,
+ "mute": true,
+ "sound": "---",
+ "volume": 100
+ },
+ "poggermode_applause": {
+ "category": "---",
+ "focus": false,
+ "mute": false,
+ "sound": "---",
+ "volume": 100
+ },
+ "poggermode_enabled": {
+ "category": "---",
+ "focus": false,
+ "mute": false,
+ "sound": "---",
+ "volume": 100
+ },
+ "poggermode_message_send": {
+ "category": "---",
+ "focus": false,
+ "mute": false,
+ "sound": "---",
+ "volume": 100
+ },
+ "poggermode_achievement_unlock": {
+ "category": "---",
+ "focus": false,
+ "mute": false,
+ "sound": "---",
+ "volume": 100
+ }
+ },
+ "volumes": {
+ "globalVolume": 100
+ }
+ }
+}
\ No newline at end of file
diff --git a/.config/BetterDiscord/plugins/NotificationSounds.plugin.js b/.config/BetterDiscord/plugins/NotificationSounds.plugin.js
new file mode 100644
index 0000000..93ade71
--- /dev/null
+++ b/.config/BetterDiscord/plugins/NotificationSounds.plugin.js
@@ -0,0 +1,747 @@
+/**
+ * @name NotificationSounds
+ * @author DevilBro
+ * @authorId 278543574059057154
+ * @version 3.7.2
+ * @description Allows you to replace the native Sounds with custom Sounds
+ * @invite Jx3TjNS
+ * @donate https://www.paypal.me/MircoWittrien
+ * @patreon https://www.patreon.com/MircoWittrien
+ * @website https://mwittrien.github.io/
+ * @source https://github.com/mwittrien/BetterDiscordAddons/tree/master/Plugins/NotificationSounds/
+ * @updateUrl https://mwittrien.github.io/BetterDiscordAddons/Plugins/NotificationSounds/NotificationSounds.plugin.js
+ */
+
+module.exports = (_ => {
+ const config = {
+ "info": {
+ "name": "NotificationSounds",
+ "author": "DevilBro",
+ "version": "3.7.2",
+ "description": "Allows you to replace the native Sounds with custom Sounds"
+ }
+ };
+
+ return !window.BDFDB_Global || (!window.BDFDB_Global.loaded && !window.BDFDB_Global.started) ? class {
+ getName () {return config.info.name;}
+ getAuthor () {return config.info.author;}
+ getVersion () {return config.info.version;}
+ getDescription () {return `The Library Plugin needed for ${config.info.name} is missing. Open the Plugin Settings to download it. \n\n${config.info.description}`;}
+
+ downloadLibrary () {
+ require("request").get("https://mwittrien.github.io/BetterDiscordAddons/Library/0BDFDB.plugin.js", (e, r, b) => {
+ if (!e && b && r.statusCode == 200) require("fs").writeFile(require("path").join(BdApi.Plugins.folder, "0BDFDB.plugin.js"), b, _ => BdApi.showToast("Finished downloading BDFDB Library", {type: "success"}));
+ else BdApi.alert("Error", "Could not download BDFDB Library Plugin. Try again later or download it manually from GitHub: https://mwittrien.github.io/downloader/?library");
+ });
+ }
+
+ load () {
+ if (!window.BDFDB_Global || !Array.isArray(window.BDFDB_Global.pluginQueue)) window.BDFDB_Global = Object.assign({}, window.BDFDB_Global, {pluginQueue: []});
+ if (!window.BDFDB_Global.downloadModal) {
+ window.BDFDB_Global.downloadModal = true;
+ BdApi.showConfirmationModal("Library Missing", `The Library Plugin needed for ${config.info.name} is missing. Please click "Download Now" to install it.`, {
+ confirmText: "Download Now",
+ cancelText: "Cancel",
+ onCancel: _ => {delete window.BDFDB_Global.downloadModal;},
+ onConfirm: _ => {
+ delete window.BDFDB_Global.downloadModal;
+ this.downloadLibrary();
+ }
+ });
+ }
+ if (!window.BDFDB_Global.pluginQueue.includes(config.info.name)) window.BDFDB_Global.pluginQueue.push(config.info.name);
+ }
+ start () {this.load();}
+ stop () {}
+ getSettingsPanel () {
+ let template = document.createElement("template");
+ template.innerHTML = `The Library Plugin needed for ${config.info.name} is missing.\nPlease click
Download Now to install it.
`;
+ template.content.firstElementChild.querySelector("a").addEventListener("click", this.downloadLibrary);
+ return template.content.firstElementChild;
+ }
+ } : (([Plugin, BDFDB]) => {
+ var audios, choices, firedEvents;
+ var volumes = {};
+
+ const removeAllKey = "REMOVE_ALL_BDFDB_DEVILBRO_DO_NOT_COPY";
+ const defaultDevice = "default";
+
+ var currentDevice = defaultDevice, createdAudios = {}, repatchIncoming;
+
+ let types = {};
+
+ const message1Types = {
+ dm: {src: "./message3.mp3", name: "Message (Direct Message)", force: null, focus: true},
+ groupdm: {src: "./message3.mp3", name: "Message (Group Message)", force: null, focus: true},
+ mentioned: {src: "./message2.mp3", name: "Message Mentioned", force: false, focus: true},
+ reply: {src: "./message2.mp3", name: "Message Mentioned (reply)", force: false, focus: true},
+ role: {src: "./mention1.mp3", name: "Message Mentioned (role)", force: false, focus: true},
+ everyone: {src: "./mention2.mp3", name: "Message Mentioned (@everyone)", force: false, focus: true},
+ here: {src: "./mention3.mp3", name: "Message Mentioned (@here)", force: false, focus: true}
+ };
+
+ const defaultAudios = {
+ "---": {
+ "---": null
+ },
+ "Discord": {}
+ };
+
+ const WebAudioSound = class WebAudioSound {
+ constructor (type) {
+ this._name = type;
+ this._src = audios[choices[type].category][choices[type].sound] || types[type].src;
+ this._volume = choices[type].volume;
+ }
+ loop () {
+ this._ensureAudio().then(audio => {
+ audio.loop = true;
+ audio.play();
+ });
+ }
+ play () {
+ this._ensureAudio().then(audio => {
+ audio.loop = false;
+ audio.play();
+ });
+ }
+ pause () {
+ this._audio.then(audio => {
+ audio.pause();
+ });
+ }
+ stop () {
+ this._destroyAudio();
+ }
+ setTime (time) {
+ this._audio.then(audio => {
+ audio.currentTime = time;
+ });
+ }
+ setLoop (loop) {
+ this._audio.then(audio => {
+ audio.loop = loop;
+ });
+ }
+ _destroyAudio () {
+ if (this._audio) {
+ this._audio.then(audio => {
+ audio.pause();
+ audio.src = "";
+ });
+ this._audio = null;
+ }
+ }
+ _ensureAudio () {
+ return this._audio = this._audio || new Promise((callback, errorCallback) => {
+ let audio = new Audio;
+ audio.src = this._src && this._src.startsWith("data") ? this._src.replace(/ /g, "") : this._src;
+ audio.onloadeddata = _ => {
+ audio.volume = Math.min((BDFDB.LibraryModules.MediaDeviceUtils.getOutputVolume() / 100) * (this._volume / 100) * (volumes.globalVolume / 100), 1);
+ BDFDB.LibraryModules.PlatformUtils.embedded && audio.setSinkId(currentDevice || defaultDevice);
+ callback(audio);
+ };
+ audio.onerror = _ => errorCallback(new Error("could not play audio"));
+ audio.onended = _ => this._destroyAudio();
+ audio.load();
+ }), this._audio;
+ }
+ };
+
+ return class NotificationSounds extends Plugin {
+ onLoad () {
+ audios = {};
+ choices = {};
+ firedEvents = {};
+
+ this.defaults = {
+ volumes: {
+ globalVolume: {value: 100, description: "Global Notification Sounds Volume"}
+ }
+ };
+
+ this.patchPriority = 9;
+
+ const soundKeys = BDFDB.LibraryModules.SoundParser.keys();
+ for (let key of soundKeys) {
+ const id = key.replace("./", "").replace(".mp3", "");
+ const name = id == "reconnect" ? "Invited To Speak" : id.replace("ddr-", "HotKeys_").replace("ptt_", "Push2Talk_").split("_").map(BDFDB.LibraryModules.StringUtils.upperCaseFirstChar).join(" ").replace(/1$/g, "");
+ const src = BDFDB.LibraryModules.SoundParser(key);
+
+ let soundPackName = id.split("_")[0];
+ if (soundPackName != id && soundKeys.filter(n => n.indexOf(`./${soundPackName}`) > -1).length > 10) {
+ soundPackName = BDFDB.LibraryModules.StringUtils.upperCaseFirstChar(soundPackName);
+ if (!defaultAudios[soundPackName]) defaultAudios[soundPackName] = {};
+ defaultAudios[soundPackName][name.replace(new RegExp(`${soundPackName} `, "i"), "").replace(/bootup/i, "Discodo")] = src;
+ }
+ else {
+ defaultAudios.Discord[name] = src;
+ if (this.isSoundUsedAnywhere(id)) types[id] = {
+ name: name,
+ src: src,
+ mute: id.startsWith("call_") ? null : false,
+ force: id == "message1" ? false : null,
+ focus: id == "message1" ? true : false
+ };
+ if (id == "message1") {
+ types[id].mute = true;
+ for (let subType in message1Types) types[subType] = {
+ name: message1Types[subType].name,
+ src: BDFDB.LibraryModules.SoundParser(message1Types[subType].src),
+ mute: true,
+ force: message1Types[subType].force,
+ focus: message1Types[subType].focus
+ }
+ }
+ }
+ types = BDFDB.ObjectUtils.sort(types, "name");
+ }
+ for (let pack in defaultAudios) defaultAudios[pack] = BDFDB.ObjectUtils.sort(defaultAudios[pack]);
+ }
+
+ onStart () {
+ if (BDFDB.LibraryModules.PlatformUtils.embedded) {
+ let change = _ => {
+ if (window.navigator.mediaDevices && window.navigator.mediaDevices.enumerateDevices) {
+ window.navigator.mediaDevices.enumerateDevices().then(enumeratedDevices => {
+ let id = BDFDB.LibraryModules.MediaDeviceUtils.getOutputDeviceId();
+ let allDevices = BDFDB.LibraryModules.MediaDeviceUtils.getOutputDevices();
+ let filteredDevices = enumeratedDevices.filter(d => d.kind == "audiooutput" && d.deviceId != "communications");
+ let deviceIndex = BDFDB.LibraryModules.ArrayUtils(allDevices).sortBy(d => d.index).findIndex(d => d.id == id);
+ let deviceViaId = allDevices[id];
+ let deviceViaIndex = filteredDevices[deviceIndex];
+ if (deviceViaId && deviceViaIndex && deviceViaIndex.label != deviceViaId.name) deviceViaIndex = filteredDevices.find(d => d.label == deviceViaId.name);
+ currentDevice = deviceViaIndex ? deviceViaIndex.deviceId : defaultDevice;
+ }).catch(_ => {
+ currentDevice = defaultDevice;
+ });
+ }
+ };
+ BDFDB.StoreChangeUtils.add(this, BDFDB.LibraryModules.MediaDeviceUtils, change);
+ change();
+ }
+
+ BDFDB.PatchUtils.patch(this, BDFDB.LibraryModules.DispatchApiUtils, "dispatch", {before: e => {
+ if (BDFDB.ObjectUtils.is(e.methodArguments[0]) && e.methodArguments[0].type == "MESSAGE_CREATE" && e.methodArguments[0].message) {
+ const message = e.methodArguments[0].message;
+ const guildId = message.guild_id || null;
+ if (message.author.id != BDFDB.UserUtils.me.id && !BDFDB.LibraryModules.RelationshipStore.isBlocked(message.author.id)) {
+ const channel = BDFDB.LibraryModules.ChannelStore.getChannel(message.channel_id);
+ const isGroupDM = channel.isGroupDM();
+ const muted = BDFDB.LibraryModules.MutedUtils.isGuildOrCategoryOrChannelMuted(guildId, channel.id);
+ const focused = document.hasFocus() && BDFDB.LibraryModules.LastChannelStore.getChannelId() == channel.id;
+ if (!guildId && !muted && !(choices[isGroupDM ? "groupdm" : "dm"].focus && focused)) {
+ this.fireEvent(isGroupDM ? "groupdm" : "dm");
+ this.playAudio(isGroupDM ? "groupdm" : "dm");
+ return;
+ }
+ else if (guildId) {
+ if (BDFDB.LibraryModules.MentionUtils.isRawMessageMentioned({rawMessage: message, userId: BDFDB.UserUtils.me.id})) {
+ if (message.mentions.length && !this.isSuppressMentionsEnabled(guildId, channel.id)) for (const mention of message.mentions) if (mention.id == BDFDB.UserUtils.me.id) {
+ if (message.message_reference && !message.interaction && (!muted || choices.reply.force) && !(choices.reply.focus && focused)) {
+ this.fireEvent("reply");
+ this.playAudio("reply");
+ return;
+ }
+ if (!message.message_reference && (!muted || choices.mentioned.force) && !(choices.mentioned.focus && focused)) {
+ this.fireEvent("mentioned");
+ this.playAudio("mentioned");
+ return;
+ }
+ }
+ if (message.mention_roles.length && !BDFDB.LibraryModules.MutedUtils.isSuppressRolesEnabled(guildId, channel.id) && (!muted || choices.role.force) && !(choices.role.focus && focused)) {
+ const member = BDFDB.LibraryModules.MemberStore.getMember(guildId, BDFDB.UserUtils.me.id);
+ if (member && member.roles.length) for (const roleId of message.mention_roles) if (member.roles.includes(roleId)) {
+ this.fireEvent("role");
+ this.playAudio("role");
+ return;
+ }
+ }
+ if (message.mention_everyone && !BDFDB.LibraryModules.MutedUtils.isSuppressEveryoneEnabled(guildId, channel.id)) {
+ if (message.content.indexOf("@everyone") > -1 && (!muted || choices.everyone.force) && !(choices.everyone.focus && focused)) {
+ this.fireEvent("everyone");
+ this.playAudio("everyone");
+ return;
+ }
+ if (message.content.indexOf("@here") > -1 && (!muted || choices.here.force) && !(choices.here.focus && focused)) {
+ this.fireEvent("here");
+ this.playAudio("here");
+ return;
+ }
+ }
+ }
+ if (BDFDB.LibraryModules.MutedUtils.allowAllMessages(channel) && (!muted || choices.message1.force) && !(choices.message1.focus && focused)) {
+ this.fireEvent("message1");
+ this.playAudio("message1");
+ return;
+ }
+ }
+ }
+ }
+ }});
+
+ BDFDB.PatchUtils.patch(this, BDFDB.LibraryModules.DesktopNotificationUtils, "showNotification", {before: e => {
+ let soundObjIndex = Array.from(e.methodArguments).findIndex(n => n && n.sound);
+ if (soundObjIndex && e.methodArguments[soundObjIndex].sound.includes("message")) e.methodArguments[soundObjIndex].sound = null;
+ }});
+ BDFDB.PatchUtils.patch(this, BDFDB.LibraryModules.SoundUtils, "playSound", {instead: e => {
+ let type = e.methodArguments[0];
+ if (type && choices[type]) {
+ e.stopOriginalMethodCall();
+ BDFDB.TimeUtils.timeout(_ => {
+ if (type == "message1") {
+ let called = false;
+ for (let subType of [type].concat(Object.keys(message1Types))) if (firedEvents[subType]) {
+ delete firedEvents[subType];
+ called = true;
+ break;
+ }
+ if (!called) this.playAudio(type);
+ }
+ else this.playAudio(type);
+ });
+ }
+ else e.callOriginalMethodAfterwards();
+ }});
+ BDFDB.PatchUtils.patch(this, BDFDB.LibraryModules.SoundUtils, "createSound", {after: e => {
+ let type = e.methodArguments[0];
+ if (type && choices[type]) {
+ let audio = new WebAudioSound(type);
+ createdAudios[type] = audio;
+ return audio;
+ }
+ else BDFDB.LogUtils.warn(`Could not create Sound for "${type}".`, this);
+ }});
+
+ this.loadAudios();
+ this.loadChoices();
+
+ let callListenerModule = BDFDB.ModuleUtils.findByProperties("handleRingUpdate");
+ if (callListenerModule) {
+ callListenerModule.terminate();
+ BDFDB.PatchUtils.patch(this, callListenerModule, "handleRingUpdate", {instead: e => {
+ if (BDFDB.LibraryModules.CallUtils.getCalls().filter(call => call.ringing.length > 0 && BDFDB.LibraryModules.VoiceUtils.getCurrentClientVoiceChannelId() === call.channelId).length > 0 && !BDFDB.LibraryModules.SoundStateUtils.isSoundDisabled("call_calling") && !BDFDB.LibraryModules.StreamerModeStore.disableSounds) {
+ createdAudios["call_calling"].loop();
+ }
+ else createdAudios["call_calling"].stop();
+ }});
+ callListenerModule.initialize();
+ }
+
+ this.forceUpdateAll();
+ }
+
+ onStop () {
+ for (let type in createdAudios) if (createdAudios[type]) createdAudios[type].stop();
+ }
+
+ getSettingsPanel (collapseStates = {}) {
+ let successSavedAudio = data => {
+ BDFDB.NotificationUtils.toast(`Sound ${data.sound} was added to category ${data.category}.`, {type: "success"});
+ if (!audios[data.category]) audios[data.category] = {};
+ audios[data.category][data.sound] = data.source;
+ BDFDB.DataUtils.save(audios, this, "audios");
+ BDFDB.PluginUtils.refreshSettingsPanel(this, settingsPanel, collapseStates);
+
+ };
+
+ let settingsPanel;
+ return settingsPanel = BDFDB.PluginUtils.createSettingsPanel(this, {
+ collapseStates: collapseStates,
+ children: _ => {
+ let settingsItems = [];
+
+ settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.CollapseContainer, {
+ title: "Settings",
+ collapseStates: collapseStates,
+ children: Object.keys(volumes).map(key => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsSaveItem, {
+ type: "Slider",
+ plugin: this,
+ keys: ["volumes", key],
+ basis: "50%",
+ label: this.defaults.volumes[key].description,
+ value: volumes[key]
+ }))
+ }));
+
+ settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.CollapseContainer, {
+ title: "Add new Sound",
+ collapseStates: collapseStates,
+ children: [
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex, {
+ className: BDFDB.disCN.margintop4,
+ children: [
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, {
+ children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormItem, {
+ title: "Categoryname",
+ children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TextInput, {
+ className: "input-newsound input-category",
+ value: "",
+ placeholder: "Categoryname"
+ })
+ })
+ }),
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, {
+ children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormItem, {
+ title: "Soundname",
+ children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TextInput, {
+ className: "input-newsound input-sound",
+ value: "",
+ placeholder: "Soundname"
+ })
+ })
+ })
+ ]
+ }),
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex, {
+ className: BDFDB.disCN.margintop4,
+ align: BDFDB.LibraryComponents.Flex.Align.END,
+ children: [
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, {
+ children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormItem, {
+ title: "Source",
+ children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TextInput, {
+ className: "input-newsound input-source",
+ type: "file",
+ filter: ["audio", "video"],
+ useFilePath: true,
+ value: "",
+ placeholder: "Source"
+ })
+ })
+ }),
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Button, {
+ style: {marginBottom: 1},
+ onClick: _ => {
+ for (let input of settingsPanel.props._node.querySelectorAll(".input-newsound " + BDFDB.dotCN.input)) if (!input.value || input.value.length == 0 || input.value.trim().length == 0) return BDFDB.NotificationUtils.toast("Fill out all fields to add a new sound", {type: "danger"});
+ let category = settingsPanel.props._node.querySelector(".input-category " + BDFDB.dotCN.input).value.trim();
+ let sound = settingsPanel.props._node.querySelector(".input-sound " + BDFDB.dotCN.input).value.trim();
+ let source = settingsPanel.props._node.querySelector(".input-source " + BDFDB.dotCN.input).value.trim();
+ if (source.indexOf("http") == 0) BDFDB.LibraryRequires.request(source, (error, response, result) => {
+ if (response) {
+ let type = response.headers["content-type"];
+ if (type && (type.indexOf("octet-stream") > -1 || type.indexOf("audio") > -1 || type.indexOf("video") > -1)) return successSavedAudio({category, sound, source});
+ }
+ BDFDB.NotificationUtils.toast("Use a valid direct link to a video or audio source, they usually end on something like .mp3, .mp4 or .wav", {type: "danger"});
+ });
+ else BDFDB.LibraryRequires.fs.readFile(source, (error, response) => {
+ if (error) BDFDB.NotificationUtils.toast("Could not fetch file. Please make sure the file exists", {type: "danger"});
+ else return successSavedAudio({category, sound, source: `data:audio/mpeg;base64,${response.toString("base64")}`});
+ });
+ },
+ children: BDFDB.LanguageUtils.LanguageStrings.SAVE
+ })
+ ]
+ })
+ ]
+ }));
+
+ settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.CollapseContainer, {
+ title: "Sound Configuration",
+ collapseStates: collapseStates,
+ children: Object.keys(types).map(type => [
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex, {
+ className: BDFDB.disCN.marginbottom8,
+ align: BDFDB.LibraryComponents.Flex.Align.CENTER,
+ direction: BDFDB.LibraryComponents.Flex.Direction.HORIZONTAL,
+ children: [
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsLabel, {
+ label: types[type].name
+ }),
+ types[type].force != null ? BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsItem, {
+ type: "Switch",
+ mini: true,
+ grow: 0,
+ label: "Force Play",
+ labelChildren: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TooltipContainer, {
+ text: "Plays the Sound even if the Channel, the Message was sent in, is muted",
+ children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, {
+ name: BDFDB.LibraryComponents.SvgIcon.Names.QUESTIONMARK,
+ style: {marginLeft: 4, marginRight: -2},
+ width: 14,
+ height: 14
+ })
+ }),
+ value: choices[type].force,
+ onChange: value => {
+ choices[type].force = value;
+ this.saveChoice(type, false);
+ }
+ }) : null,
+ types[type].focus != null ? BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsItem, {
+ type: "Switch",
+ mini: true,
+ grow: 0,
+ label: "Focus Mute",
+ labelChildren: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TooltipContainer, {
+ text: "Does not play the Sound when the Channel, the Message was sent in, is currently opened",
+ children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, {
+ name: BDFDB.LibraryComponents.SvgIcon.Names.QUESTIONMARK,
+ style: {marginLeft: 4, marginRight: -2},
+ width: 14,
+ height: 14
+ })
+ }),
+ value: choices[type].focus,
+ onChange: value => {
+ choices[type].focus = value;
+ this.saveChoice(type, false);
+ }
+ }) : null,
+ types[type].mute !== null && BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsItem, {
+ type: "Switch",
+ mini: true,
+ grow: 0,
+ label: "Mute in",
+ labelChildren: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Status, {
+ style: {marginLeft: 6},
+ size: 12,
+ status: BDFDB.DiscordConstants.StatusTypes.DND
+ }),
+ value: choices[type].mute,
+ onChange: value => {
+ choices[type].mute = value;
+ this.saveChoice(type, false);
+ }
+ })
+ ].filter(n => n)
+ }),
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex, {
+ className: BDFDB.disCN.marginbottom8,
+ children: [
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, {
+ grow: 0,
+ shrink: 0,
+ basis: "31%",
+ children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormItem, {
+ title: "Category",
+ children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Select, {
+ value: choices[type].category,
+ options: Object.keys(audios).map(name => ({value: name, label: name})),
+ searchable: true,
+ onChange: value => {
+ const categorySounds = audios[value] || {};
+ choices[type].category = value;
+ choices[type].sound = categorySounds[types[type].name] ? types[type].name : Object.keys(categorySounds)[0];
+ this.saveChoice(type, true);
+ BDFDB.PluginUtils.refreshSettingsPanel(this, settingsPanel, collapseStates);
+ }
+ })
+ })
+ }),
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, {
+ grow: 0,
+ shrink: 0,
+ basis: "31%",
+ children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormItem, {
+ title: "Sound",
+ children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Select, {
+ value: choices[type].sound,
+ options: Object.keys(audios[choices[type].category] || {}).map(name => ({value: name, label: name})),
+ searchable: true,
+ onChange: value => {
+ choices[type].sound = value;
+ this.saveChoice(type, true);
+ BDFDB.PluginUtils.refreshSettingsPanel(this, settingsPanel, collapseStates);
+ }
+ })
+ })
+ }),
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, {
+ grow: 0,
+ shrink: 0,
+ basis: "31%",
+ children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormItem, {
+ title: "Volume",
+ children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Slider, {
+ defaultValue: choices[type].volume,
+ digits: 1,
+ onValueRender: value => {
+ return value + "%";
+ },
+ onValueChange: value => {
+ choices[type].volume = value;
+ this.saveChoice(type, true);
+ }
+ })
+ })
+ })
+ ]
+ }),
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormDivider, {
+ className: BDFDB.disCN.marginbottom8
+ })
+ ]).flat(10).filter(n => n)
+ }));
+
+ let removeableCategories = [{value: removeAllKey, label: BDFDB.LanguageUtils.LanguageStrings.FORM_LABEL_ALL}].concat(Object.keys(audios).filter(category => !(defaultAudios[category] && !Object.keys(audios[category] || {}).filter(sound => defaultAudios[category][sound] === undefined).length)).map(name => ({value: name, label: name})));
+ let removeableSounds = {};
+ for (let category of removeableCategories) removeableSounds[category.value] = [{value: removeAllKey, label: BDFDB.LanguageUtils.LanguageStrings.FORM_LABEL_ALL}].concat(Object.keys(audios[category.value] || {}).filter(sound => !(defaultAudios[category.value] && defaultAudios[category.value][sound] !== undefined)).map(name => ({value: name, label: name})));
+ settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.CollapseContainer, {
+ title: "Remove Sounds",
+ collapseStates: collapseStates,
+ children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex, {
+ className: BDFDB.disCN.margintop4,
+ align: BDFDB.LibraryComponents.Flex.Align.END,
+ children: [
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, {
+ grow: 0,
+ shrink: 0,
+ basis: "35%",
+ children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormItem, {
+ title: "Category",
+ children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Select, {
+ key: "REMOVE_CATEGORY",
+ value: removeAllKey,
+ options: removeableCategories,
+ searchable: true,
+ onChange: (category, instance) => {
+ let soundSelectIns = BDFDB.ReactUtils.findOwner(BDFDB.ReactUtils.findOwner(instance, {name: ["BDFDB_Modal", "BDFDB_SettingsPanel"], up: true}), {key: "REMOVE_SOUND"});
+ if (soundSelectIns && removeableSounds[category.value]) {
+ soundSelectIns.props.options = removeableSounds[category.value];
+ soundSelectIns.props.value = removeAllKey;
+ BDFDB.ReactUtils.forceUpdate(soundSelectIns);
+ }
+ }
+ })
+ })
+ }),
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, {
+ grow: 0,
+ shrink: 0,
+ basis: "35%",
+ children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormItem, {
+ title: "Sound",
+ children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Select, {
+ key: "REMOVE_SOUND",
+ value: removeAllKey,
+ options: removeableSounds[removeAllKey],
+ searchable: true
+ })
+ })
+ }),
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, {
+ grow: 0,
+ shrink: 1,
+ basis: "25%",
+ children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Button, {
+ style: {marginBottom: 1},
+ color: BDFDB.LibraryComponents.Button.Colors.RED,
+ onClick: (event, instance) => {
+ let wrapperIns = BDFDB.ReactUtils.findOwner(instance, {name: ["BDFDB_Modal", "BDFDB_SettingsPanel"], up: true});
+ let categorySelectIns = BDFDB.ReactUtils.findOwner(wrapperIns, {key: "REMOVE_CATEGORY"});
+ let soundSelectIns = BDFDB.ReactUtils.findOwner(wrapperIns, {key: "REMOVE_SOUND"});
+ if (categorySelectIns && soundSelectIns) {
+ let soundAmount = 0;
+ let catAll = categorySelectIns.props.value == removeAllKey;
+ let soundAll = soundSelectIns.props.value == removeAllKey;
+ if (catAll) soundAmount = BDFDB.ArrayUtils.sum(Object.keys(audios).map(category => Object.keys(audios[category] || {}).filter(sound => !(defaultAudios[category] && defaultAudios[category][sound] !== undefined)).length));
+ else if (soundAll) soundAmount = Object.keys(audios[categorySelectIns.props.value] || {}).filter(sound => !(defaultAudios[categorySelectIns.props.value] && defaultAudios[categorySelectIns.props.value][sound] !== undefined)).length;
+ else if (audios[categorySelectIns.props.value][soundSelectIns.props.value]) soundAmount = 1;
+
+ if (soundAmount) BDFDB.ModalUtils.confirm(this, `Are you sure you want to delete ${soundAmount} added Sound${soundAmount == 1 ? "" : "s"}?`, _ => {
+ if (catAll) BDFDB.DataUtils.remove(this, "audios");
+ else if (soundAll) BDFDB.DataUtils.remove(this, "audios", categorySelectIns.props.value);
+ else {
+ delete audios[categorySelectIns.props.value][soundSelectIns.props.value];
+ if (BDFDB.ObjectUtils.isEmpty(audios[categorySelectIns.props.value])) delete audios[categorySelectIns.props.value];
+ BDFDB.DataUtils.save(audios, this, "audios");
+ }
+ this.loadAudios();
+ this.loadChoices();
+ BDFDB.PluginUtils.refreshSettingsPanel(this, settingsPanel, collapseStates);
+ });
+ else BDFDB.NotificationUtils.toast("No Sounds to delete", {type: "danger"});
+ }
+ },
+ children: BDFDB.LanguageUtils.LanguageStrings.DELETE
+ })
+ })
+ ]
+ })
+ }));
+
+ return settingsItems;
+ }
+ });
+ }
+
+ onSettingsClosed () {
+ if (this.SettingsUpdated) {
+ delete this.SettingsUpdated;
+ for (let type in createdAudios) if (createdAudios[type]) createdAudios[type].stop();
+ createdAudios = {};
+ this.forceUpdateAll();
+ }
+ }
+
+ forceUpdateAll () {
+ repatchIncoming = true;
+ createdAudios["call_calling"] = BDFDB.LibraryModules.SoundUtils.createSound("call_calling");
+ volumes = BDFDB.DataUtils.get(this, "volumes");
+ BDFDB.PatchUtils.forceAllUpdates(this);
+ BDFDB.DiscordUtils.rerenderAll();
+ }
+
+ loadAudios () {
+ audios = Object.assign({}, BDFDB.DataUtils.load(this, "audios"), defaultAudios);
+ BDFDB.DataUtils.save(BDFDB.ObjectUtils.exclude(audios, Object.keys(defaultAudios)), this, "audios");
+ }
+
+ loadChoices () {
+ let loadedChoices = BDFDB.DataUtils.load(this, "choices");
+ for (let type in types) {
+ let choice = loadedChoices[type] || {}, soundFound = false;
+ for (let category in audios) if (choice.category == category) for (let sound in audios[category]) if (choice.sound == sound) {
+ soundFound = true;
+ break;
+ }
+ if (!soundFound) choice = {
+ category: "---",
+ sound: "---",
+ volume: 100,
+ mute: types[type].mute,
+ focus: types[type].focus
+ };
+ choices[type] = choice;
+ this.saveChoice(type, false);
+ }
+ }
+
+ saveChoice (type, play) {
+ if (!choices[type]) return;
+ BDFDB.DataUtils.save(choices[type], this, "choices", type);
+ if (play) {
+ this.SettingsUpdated = true;
+ this.playAudio(type);
+ }
+ }
+
+ playAudio (type) {
+ if (this.dontPlayAudio(type) || BDFDB.LibraryModules.StreamerModeStore.disableSounds) return;
+ if (createdAudios[type]) createdAudios[type].stop();
+ createdAudios[type] = new WebAudioSound(type);
+ createdAudios[type].play();
+ }
+
+ isSuppressMentionsEnabled (guildId, channelId) {
+ let channelSettings = BDFDB.LibraryModules.MutedUtils.getChannelMessageNotifications(guildId, channelId);
+ return channelSettings && (channelSettings == BDFDB.DiscordConstants.UserNotificationSettings.NO_MESSAGES || channelSettings == BDFDB.DiscordConstants.UserNotificationSettings.NULL && BDFDB.LibraryModules.MutedUtils.getMessageNotifications(guildId) == BDFDB.DiscordConstants.UserNotificationSettings.NO_MESSAGES);
+ }
+
+ dontPlayAudio (type) {
+ let status = BDFDB.UserUtils.getStatus();
+ return choices[type].mute && (status == "dnd" || status == "streaming");
+ }
+
+ fireEvent (type) {
+ firedEvents[type] = true;
+ BDFDB.TimeUtils.timeout(_ => delete firedEvents[type], 3000);
+ }
+
+ isSoundUsedAnywhere (type) {
+ return type != "human_man" && type != "robot_man" && type != "discodo" && type != "overlayunlock" && type != "call_ringing_beat" && !(type != "message1" && /\d$/.test(type));
+ }
+ };
+ })(window.BDFDB_Global.PluginUtils.buildPlugin(config));
+})();
diff --git a/.config/BetterDiscord/plugins/PermissionsViewer.config.json b/.config/BetterDiscord/plugins/PermissionsViewer.config.json
new file mode 100755
index 0000000..d66c35a
--- /dev/null
+++ b/.config/BetterDiscord/plugins/PermissionsViewer.config.json
@@ -0,0 +1,6 @@
+{
+ "currentVersionInfo": {
+ "version": "0.2.2",
+ "hasShownChangelog": true
+ }
+}
\ No newline at end of file
diff --git a/.config/BetterDiscord/plugins/PermissionsViewer.plugin.js b/.config/BetterDiscord/plugins/PermissionsViewer.plugin.js
new file mode 100755
index 0000000..e7350f1
--- /dev/null
+++ b/.config/BetterDiscord/plugins/PermissionsViewer.plugin.js
@@ -0,0 +1,736 @@
+/**
+ * @name PermissionsViewer
+ * @version 0.2.2
+ * @authorLink https://twitter.com/IAmZerebos
+ * @website https://github.com/rauenzi/BetterDiscordAddons/tree/master/Plugins/PermissionsViewer
+ * @source https://raw.githubusercontent.com/rauenzi/BetterDiscordAddons/master/Plugins/PermissionsViewer/PermissionsViewer.plugin.js
+ * @updateUrl https://raw.githubusercontent.com/rauenzi/BetterDiscordAddons/master/Plugins/PermissionsViewer/PermissionsViewer.plugin.js
+ */
+/*@cc_on
+@if (@_jscript)
+
+ // Offer to self-install for clueless users that try to run this directly.
+ var shell = WScript.CreateObject("WScript.Shell");
+ var fs = new ActiveXObject("Scripting.FileSystemObject");
+ var pathPlugins = shell.ExpandEnvironmentStrings("%APPDATA%\\BetterDiscord\\plugins");
+ var pathSelf = WScript.ScriptFullName;
+ // Put the user at ease by addressing them in the first person
+ 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);
+ if (fs.GetParentFolderName(pathSelf) === fs.GetAbsolutePathName(pathPlugins)) {
+ shell.Popup("I'm in the correct folder already.", 0, "I'm already installed", 0x40);
+ } else if (!fs.FolderExists(pathPlugins)) {
+ shell.Popup("I can't find the BetterDiscord plugins folder.\nAre you sure it's even installed?", 0, "Can't install myself", 0x10);
+ } else if (shell.Popup("Should I copy myself to BetterDiscord's plugins folder for you?", 0, "Do you need some help?", 0x34) === 6) {
+ fs.CopyFile(pathSelf, fs.BuildPath(pathPlugins, fs.GetFileName(pathSelf)), true);
+ // Show the user where to put plugins in the future
+ shell.Exec("explorer " + pathPlugins);
+ shell.Popup("I'm installed!", 0, "Successfully installed", 0x40);
+ }
+ WScript.Quit();
+
+@else@*/
+
+module.exports = (() => {
+ const config = {info:{name:"PermissionsViewer",authors:[{name:"Zerebos",discord_id:"249746236008169473",github_username:"rauenzi",twitter_username:"ZackRauen"}],version:"0.2.2",description:"Allows you to view a user's permissions. Thanks to Noodlebox for the idea!",github:"https://github.com/rauenzi/BetterDiscordAddons/tree/master/Plugins/PermissionsViewer",github_raw:"https://raw.githubusercontent.com/rauenzi/BetterDiscordAddons/master/Plugins/PermissionsViewer/PermissionsViewer.plugin.js"},changelog:[{title:"Fixes",type:"fixed",items:["Can select other roles in the modal."]}],defaultConfig:[{type:"switch",id:"contextMenus",name:"Context Menus",value:true},{type:"switch",id:"popouts",name:"Popouts",value:true},{type:"radio",id:"displayMode",name:"Modal Display Mode",value:"compact",options:[{name:"Cozy",value:"cozy"},{name:"Compact",value:"compact"}]}],strings:{es:{contextMenuLabel:"Permisos",popoutLabel:"Permisos",modal:{header:"Permisos de ${name}",rolesLabel:"Roles",permissionsLabel:"Permisos",owner:"@propietario"},settings:{popouts:{name:"Mostrar en Popouts",note:"Mostrar los permisos de usuario en popouts como los roles."},contextMenus:{name:"Botón de menú contextual",note:"Añadir un botón para ver permisos en los menús contextuales."}}},pt:{contextMenuLabel:"Permissões",popoutLabel:"Permissões",modal:{header:"Permissões de ${name}",rolesLabel:"Cargos",permissionsLabel:"Permissões",owner:"@dono"},settings:{popouts:{name:"Mostrar em Popouts",note:"Mostrar as permissões em popouts como os cargos."},contextMenus:{name:"Botão do menu de contexto",note:"Adicionar um botão parar ver permissões ao menu de contexto."}}},de:{contextMenuLabel:"Berechtigungen",popoutLabel:"Berechtigungen",modal:{header:"${name}s Berechtigungen",rolesLabel:"Rollen",permissionsLabel:"Berechtigungen",owner:"@eigentümer"},settings:{popouts:{name:"In Popouts anzeigen",note:"Zeigt die Gesamtberechtigungen eines Benutzers in seinem Popup ähnlich den Rollen an."},contextMenus:{name:"Kontextmenü-Schaltfläche",note:"Fügt eine Schaltfläche hinzu, um die Berechtigungen mithilfe von Kontextmenüs anzuzeigen."}}},en:{contextMenuLabel:"Permissions",popoutLabel:"Permissions",modal:{header:"${name}'s Permissions",rolesLabel:"Roles",permissionsLabel:"Permissions",owner:"@owner"},settings:{popouts:{name:"Show In Popouts",note:"Shows a user's total permissions in their popout similar to roles."},contextMenus:{name:"Context Menu Button",note:"Adds a button to view the permissions modal to select context menus."},displayMode:{name:"Modal Display Mode"}}},ru:{contextMenuLabel:"Полномочия",popoutLabel:"Полномочия",modal:{header:"Полномочия ${name}",rolesLabel:"Роли",permissionsLabel:"Полномочия",owner:"Владелец"},settings:{popouts:{name:"Показать во всплывающих окнах",note:"Отображает полномочия пользователя в их всплывающем окне, аналогичном ролям."},contextMenus:{name:"Кнопка контекстного меню",note:"Добавить кнопку для отображения полномочий с помощью контекстных меню."}}}},main:"index.js"};
+
+ return !global.ZeresPluginLibrary ? class {
+ constructor() {this._config = config;}
+ getName() {return config.info.name;}
+ getAuthor() {return config.info.authors.map(a => a.name).join(", ");}
+ getDescription() {return config.info.description;}
+ getVersion() {return config.info.version;}
+ load() {
+ BdApi.showConfirmationModal("Library Missing", `The library plugin needed for ${config.info.name} is missing. Please click Download Now to install it.`, {
+ confirmText: "Download Now",
+ cancelText: "Cancel",
+ onConfirm: () => {
+ require("request").get("https://rauenzi.github.io/BDPluginLibrary/release/0PluginLibrary.plugin.js", async (error, response, body) => {
+ if (error) return require("electron").shell.openExternal("https://betterdiscord.net/ghdl?url=https://raw.githubusercontent.com/rauenzi/BDPluginLibrary/master/release/0PluginLibrary.plugin.js");
+ await new Promise(r => require("fs").writeFile(require("path").join(BdApi.Plugins.folder, "0PluginLibrary.plugin.js"), body, r));
+ });
+ }
+ });
+ }
+ start() {}
+ stop() {}
+ } : (([Plugin, Api]) => {
+ const plugin = (Plugin, Api) => {
+ const {Patcher, DiscordModules, WebpackModules, PluginUtilities, Toasts, DiscordClasses, Utilities, DOMTools, ColorConverter, DCM, Structs, ReactTools} = Api;
+
+ const GuildStore = DiscordModules.GuildStore;
+ const SelectedGuildStore = DiscordModules.SelectedGuildStore;
+ const MemberStore = DiscordModules.GuildMemberStore;
+ const UserStore = DiscordModules.UserStore;
+ const DiscordPerms = Object.assign({}, DiscordModules.DiscordConstants.Permissions);
+ const AvatarDefaults = WebpackModules.getByProps("DEFAULT_AVATARS");
+ const UserPopoutSelectors = Object.assign({}, WebpackModules.getByProps("userPopout"), WebpackModules.getByProps("rolesList"));
+ for (const key in UserPopoutSelectors) UserPopoutSelectors[key] = new Structs.Selector(UserPopoutSelectors[key]);
+ const escapeHTML = DOMTools.escapeHTML ? DOMTools.escapeHTML : function(html) {
+ const textNode = document.createTextNode("");
+ const spanElement = document.createElement("span");
+ spanElement.append(textNode);
+ textNode.nodeValue = html;
+ return spanElement.innerHTML;
+ };
+
+ if (DiscordPerms.STREAM) {
+ DiscordPerms.VIDEO = DiscordPerms.STREAM;
+ delete DiscordPerms.STREAM;
+ }
+ if (DiscordPerms.MANAGE_GUILD) {
+ DiscordPerms.MANAGE_SERVER = DiscordPerms.MANAGE_GUILD;
+ delete DiscordPerms.MANAGE_GUILD;
+ }
+
+ return class PermissionsViewer extends Plugin {
+ constructor() {
+ super();
+ this.css = `.member-perms-header {
+ display: flex;
+ justify-content: space-between;
+}
+
+.member-perms {
+ display: flex;
+ flex-wrap: wrap;
+ margin-top: 2px;
+ max-height: 160px;
+ overflow-y: auto;
+ overflow-x: hidden;
+}
+
+.member-perms .member-perm .perm-circle {
+ border-radius: 50%;
+ height: 12px;
+ margin-right: 4px;
+ width: 12px;
+}
+
+.member-perms .member-perm .name {
+ margin-right: 4px;
+ max-width: 200px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.perm-details-button {
+ cursor: pointer;
+ height: 12px;
+}
+
+.perm-details {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.member-perm-details {
+ cursor: pointer;
+}
+
+.member-perm-details-button {
+ fill: #72767d;
+ height: 10px;
+}
+
+/* Modal */
+
+@keyframes permissions-backdrop {
+ to { opacity: 0.85; }
+}
+
+@keyframes permissions-modal-wrapper {
+ to { transform: scale(1); opacity: 1; }
+}
+
+@keyframes permissions-backdrop-closing {
+ to { opacity: 0; }
+}
+
+@keyframes permissions-modal-wrapper-closing {
+ to { transform: scale(0.7); opacity: 0; }
+}
+
+#permissions-modal-wrapper {
+ z-index: 100;
+}
+
+#permissions-modal-wrapper .callout-backdrop {
+ animation: permissions-backdrop 250ms ease;
+ animation-fill-mode: forwards;
+ opacity: 0;
+ background-color: rgb(0, 0, 0);
+ transform: translateZ(0px);
+}
+
+#permissions-modal-wrapper.closing .callout-backdrop {
+ animation: permissions-backdrop-closing 200ms linear;
+ animation-fill-mode: forwards;
+ animation-delay: 50ms;
+ opacity: 0.85;
+}
+
+#permissions-modal-wrapper.closing .modal-wrapper {
+ animation: permissions-modal-wrapper-closing 250ms cubic-bezier(0.19, 1, 0.22, 1);
+ animation-fill-mode: forwards;
+ opacity: 1;
+ transform: scale(1);
+}
+
+#permissions-modal-wrapper .modal-wrapper {
+ animation: permissions-modal-wrapper 250ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
+ animation-fill-mode: forwards;
+ transform: scale(0.7);
+ transform-origin: 50% 50%;
+ display: flex;
+ align-items: center;
+ box-sizing: border-box;
+ contain: content;
+ justify-content: center;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ right: 0;
+ opacity: 0;
+ pointer-events: none;
+ position: absolute;
+ user-select: none;
+ z-index: 1000;
+}
+
+#permissions-modal-wrapper .modal-body {
+ background-color: #36393f;
+ height: 440px;
+ width: auto;
+ /*box-shadow: 0 0 0 1px rgba(32,34,37,.6), 0 2px 10px 0 rgba(0,0,0,.2);*/
+ flex-direction: row;
+ overflow: hidden;
+ display: flex;
+ flex: 1;
+ contain: layout;
+ position: relative;
+}
+
+#permissions-modal-wrapper #permissions-modal {
+ display: flex;
+ contain: layout;
+ flex-direction: column;
+ pointer-events: auto;
+ border: 1px solid rgba(28,36,43,.6);
+ border-radius: 5px;
+ box-shadow: 0 2px 10px 0 rgba(0,0,0,.2);
+ overflow: hidden;
+}
+
+#permissions-modal-wrapper .header {
+ background-color: #35393e;
+ box-shadow: 0 2px 3px 0 rgba(0,0,0,.2);
+ padding: 12px 20px;
+ z-index: 1;
+ color: #fff;
+ font-size: 16px;
+ font-weight: 700;
+ line-height: 19px;
+}
+
+.role-side, .perm-side {
+ flex-direction: column;
+ padding-left: 6px;
+}
+
+.role-scroller, .perm-scroller {
+ contain: layout;
+ flex: 1;
+ min-height: 1px;
+ overflow-y: scroll;
+}
+
+#permissions-modal-wrapper .scroller-title {
+ color: #fff;
+ padding: 8px 0 4px 4px;
+ margin-right: 8px;
+ border-bottom: 1px solid rgba(0,0,0,0.3);
+ display: none;
+}
+
+#permissions-modal-wrapper .role-side {
+ width: auto;
+ min-width: 150px;
+ background: #2f3136;
+ flex: 0 0 auto;
+ overflow: hidden;
+ display: flex;
+ height: 100%;
+ min-height: 1px;
+ position: relative;
+}
+
+#permissions-modal-wrapper .role-scroller {
+ contain: layout;
+ flex: 1;
+ min-height: 1px;
+ overflow-y: scroll;
+ padding-top: 8px;
+}
+
+#permissions-modal-wrapper .role-item {
+ display: flex;
+ border-radius: 2px;
+ padding: 6px;
+ margin-bottom: 5px;
+ cursor: pointer;
+ color: #dcddde;
+}
+
+#permissions-modal-wrapper .role-item:hover {
+ background-color: rgba(0,0,0,0.1);
+}
+
+#permissions-modal-wrapper .role-item.selected {
+ background-color: rgba(0,0,0,0.2);
+}
+
+#permissions-modal-wrapper .perm-side {
+ width: 250px;
+ background-color: #36393f;
+ flex: 0 0 auto;
+ display: flex;
+ height: 100%;
+ min-height: 1px;
+ position: relative;
+ padding-left: 10px;
+}
+
+#permissions-modal-wrapper .perm-item {
+ box-shadow: inset 0 -1px 0 rgba(79,84,92,.3);
+ box-sizing: border-box;
+ height: 44px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ flex-direction: row;
+ justify-content: flex-start;
+ align-items: center;
+ display: flex;
+}
+
+#permissions-modal-wrapper .perm-item.allowed svg {
+ fill: #43B581;
+}
+
+#permissions-modal-wrapper .perm-item.denied svg {
+ fill: #F04747;
+}
+
+#permissions-modal-wrapper .perm-name {
+ display: inline;
+ flex: 1;
+ font-size: 16px;
+ font-weight: 400;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ user-select: text;
+ color: #dcddde;
+ margin-left: 10px;
+}
+
+
+.member-perms::-webkit-scrollbar-thumb, .member-perms::-webkit-scrollbar-track,
+#permissions-modal-wrapper *::-webkit-scrollbar-thumb, #permissions-modal-wrapper *::-webkit-scrollbar-track {
+ background-clip: padding-box;
+ border-radius: 7.5px;
+ border-style: solid;
+ border-width: 3px;
+ visibility: hidden;
+}
+
+.member-perms:hover::-webkit-scrollbar-thumb, .member-perms:hover::-webkit-scrollbar-track,
+#permissions-modal-wrapper *:hover::-webkit-scrollbar-thumb, #permissions-modal-wrapper *:hover::-webkit-scrollbar-track {
+ visibility: visible;
+}
+
+.member-perms::-webkit-scrollbar-track,
+#permissions-modal-wrapper *::-webkit-scrollbar-track {
+ border-width: initial;
+ background-color: transparent;
+ border: 2px solid transparent;
+}
+
+.member-perms::-webkit-scrollbar-thumb,
+#permissions-modal-wrapper *::-webkit-scrollbar-thumb {
+ border: 2px solid transparent;
+ border-radius: 4px;
+ cursor: move;
+ background-color: rgba(32,34,37,.6);
+}
+
+.member-perms::-webkit-scrollbar,
+#permissions-modal-wrapper *::-webkit-scrollbar {
+ height: 8px;
+ width: 8px;
+}
+
+
+
+.theme-light #permissions-modal-wrapper #permissions-modal {
+ background: #fff;
+}
+
+.theme-light #permissions-modal-wrapper .modal-body {
+ background: transparent;
+}
+
+.theme-light #permissions-modal-wrapper .header {
+ background: transparent;
+ color: #000;
+}
+
+.theme-light #permissions-modal-wrapper .role-side {
+ background: rgba(0,0,0,.2);
+}
+
+.theme-light #permissions-modal-wrapper .perm-side {
+ background: rgba(0,0,0,.1);
+}
+
+.theme-light #permissions-modal-wrapper .role-item,
+.theme-light #permissions-modal-wrapper .perm-name {
+ color: #000;
+}`;
+ this.jumbo = `#permissions-modal-wrapper #permissions-modal {
+ height: 840px;
+}
+
+#permissions-modal-wrapper #permissions-modal .perm-side {
+ width: 500px;
+}
+
+#permissions-modal .perm-scroller {
+ display: flex;
+ flex-wrap: wrap;
+ align-content: flex-start;
+}
+
+#permissions-modal .perm-item {
+ width: 50%;
+}`;
+ this.listHTML = `
+
+
\${label}
+
+
+
+
+
+
`;
+ this.itemHTML = `
+
+
+ `;
+ this.modalHTML = `
+
+
+
+
+
+
+
+
\${permissionsLabel}
+
+
+
+
+
+
+
+
`;
+ this.modalItem = `
`;
+ this.modalButton = `
`;
+ this.modalButtonUser = ``;
+ this.permAllowedIcon = ``;
+ this.permDeniedIcon = ``;
+
+ this.cancelUserPopout = () => {};
+ this.contextMenuPatches = [];
+ }
+
+ onStart() {
+ PluginUtilities.addStyle(this.getName(), this.css);
+
+ this.listHTML = Utilities.formatTString(this.listHTML, DiscordClasses.UserPopout);
+ this.listHTML = Utilities.formatTString(this.listHTML, DiscordClasses.PopoutRoles);
+ this.itemHTML = Utilities.formatTString(this.itemHTML, DiscordClasses.PopoutRoles);
+ this.modalHTML = Utilities.formatTString(this.modalHTML, DiscordClasses.Backdrop);
+ this.modalHTML = Utilities.formatTString(this.modalHTML, DiscordClasses.Modals);
+
+ this.promises = {state: {cancelled: false}, cancel() {this.state.cancelled = true;}};
+ if (this.settings.popouts) this.bindPopouts();
+ if (this.settings.contextMenus) this.bindContextMenus();
+ this.setDisplayMode(this.settings.displayMode);
+ }
+
+ onStop() {
+ PluginUtilities.removeStyle(this.getName());
+ this.promises.cancel();
+ this.unbindPopouts();
+ this.unbindContextMenus();
+ }
+
+ setDisplayMode(mode) {
+ if (mode === "cozy") PluginUtilities.addStyle(this.getName() + "-jumbo", this.jumbo);
+ else PluginUtilities.removeStyle(this.getName() + "-jumbo");
+ }
+
+ async bindPopouts() {
+ const popoutMount = (props) => {
+ const popout = document.querySelector(UserPopoutSelectors.userPopout);
+ if (!popout || popout.querySelector("#permissions-popout")) return;
+ const user = MemberStore.getMember(props.guildId, props.user.id);
+ const guild = GuildStore.getGuild(props.guildId);
+ const name = MemberStore.getNick(props.guildId, props.user.id) ?? props.user.username;
+ if (!user || !guild || !name) return;
+
+ const userRoles = user.roles.slice(0);
+ userRoles.push(guild.id);
+ userRoles.reverse();
+ let perms = 0n;
+
+ const permBlock = DOMTools.createElement(Utilities.formatTString(this.listHTML, {label: this.strings.popoutLabel}));
+ const memberPerms = permBlock.querySelector(".member-perms");
+ const strings = DiscordModules.Strings;
+
+ for (let r = 0; r < userRoles.length; r++) {
+ const role = userRoles[r];
+ if (!guild.roles[role]) continue;
+ perms = perms | guild.roles[role].permissions;
+ for (const perm in DiscordPerms) {
+ const permName = strings[perm] || perm.split("_").map(n => n[0].toUpperCase() + n.slice(1).toLowerCase()).join(" ");
+ const hasPerm = (perms & DiscordPerms[perm]) == DiscordPerms[perm];
+ if (hasPerm && !memberPerms.querySelector(`[data-name="${permName}"]`)) {
+ const element = DOMTools.createElement(this.itemHTML);
+ let roleColor = guild.roles[role].colorString;
+ element.querySelector(".name").textContent = permName;
+ element.setAttribute("data-name", permName);
+ if (!roleColor) roleColor = "#B9BBBE";
+ element.querySelector(".perm-circle").style.backgroundColor = ColorConverter.rgbToAlpha(roleColor, 1);
+ element.style.borderColor = ColorConverter.rgbToAlpha(roleColor, 0.6);
+ memberPerms.prepend(element);
+ }
+ }
+ }
+
+ permBlock.querySelector(".perm-details").addEventListener("click", () => {
+ this.showModal(this.createModalUser(name, user, guild));
+ });
+ const roleList = popout.querySelector(UserPopoutSelectors.rolesList);
+ roleList.parentNode.insertBefore(permBlock, roleList.nextSibling);
+
+
+ const popoutInstance = ReactTools.getOwnerInstance(popout, {include: ["Popout"]});
+ if (!popoutInstance || !popoutInstance.updateOffsets) return;
+ popoutInstance.updateOffsets();
+ };
+
+
+ this.cancelUserPopout = Patcher.after(DiscordModules.UserPopout, "type", (_, __, retVal) => popoutMount(retVal.props));
+ }
+
+ unbindPopouts() {
+ this.cancelUserPopout();
+ }
+
+ async bindContextMenus() {
+ this.patchChannelContextMenu();
+ this.patchGuildContextMenu();
+ this.patchUserContextMenu();
+ }
+
+ unbindContextMenus() {
+ for (const cancel of this.contextMenuPatches) cancel();
+ }
+
+ async patchGuildContextMenu() {
+ const GuildContextMenu = await DCM.getDiscordMenu("GuildContextMenu");
+ if (this.promises.state.cancelled) return;
+ this.contextMenuPatches.push(Patcher.after(GuildContextMenu, "default", (_, [props], retVal) => {
+ const original = retVal.props.children[0].props.children;
+ const newOne = DCM.buildMenuItem({
+ label: this.strings.contextMenuLabel,
+ action: () => {
+ this.showModal(this.createModalGuild(props.guild.name, props.guild));
+ }
+ });
+ if (Array.isArray(original)) original.splice(1, 0, newOne);
+ else retVal.props.children[0].props.children = [original, newOne];
+ }));
+ }
+
+ patchChannelContextMenu() {
+ const patch = (_, [props], retVal) => {
+ const original = retVal.props.children[0].props.children;
+ const newOne = DCM.buildMenuItem({
+ label: this.strings.contextMenuLabel,
+ action: () => {
+ const channel = props.channel;
+ if (!Object.keys(channel.permissionOverwrites).length) return Toasts.info(`#${channel.name} has no permission overrides`);
+ this.showModal(this.createModalChannel(channel.name, channel, props.guild));
+ }
+ });
+ if (Array.isArray(original)) original.splice(1, 0, newOne);
+ else retVal.props.children[0].props.children = [original, newOne];
+ };
+
+ DCM.getDiscordMenu("ChannelListVoiceChannelContextMenu").then(VoiceChannelContextMenu => {
+ if (this.promises.state.cancelled) return;
+ this.contextMenuPatches.push(Patcher.after(VoiceChannelContextMenu, "default", patch));
+ });
+
+ DCM.getDiscordMenu(m => m.displayName === "ChannelListTextChannelContextMenu" && !m.toString().includes("AnalyticsLocations.CONTEXT_MENU")).then(TextChannelContextMenu => {
+ if (this.promises.state.cancelled) return;
+ this.contextMenuPatches.push(Patcher.after(TextChannelContextMenu, "default", patch));
+ });
+
+ DCM.getDiscordMenu(m => m.displayName === "ChannelListTextChannelContextMenu" && m.toString().includes("AnalyticsLocations.CONTEXT_MENU")).then(CategoryChannelContextMenu => {
+ if (this.promises.state.cancelled) return;
+ this.contextMenuPatches.push(Patcher.after(CategoryChannelContextMenu, "default", patch));
+ });
+ }
+
+ async patchUserContextMenu() {
+ const UserContextMenu = await DCM.getDiscordMenu("GuildChannelUserContextMenu");
+ if (this.promises.state.cancelled) return;
+ this.contextMenuPatches.push(Patcher.after(UserContextMenu, "default", (_, [props], retVal) => {
+ const guildId = SelectedGuildStore.getGuildId();
+ const guild = GuildStore.getGuild(guildId);
+ if (!guild) return;
+ const original = retVal.props.children.props.children[1].props.children;
+ const newOne = DCM.buildMenuItem({
+ label: this.strings.contextMenuLabel,
+ action: () => {
+ const user = MemberStore.getMember(guildId, props.user.id);
+ const name = user.nick ? user.nick : UserStore.getUser(user.userId).username;
+ this.showModal(this.createModalUser(name, user, guild));
+ }
+ });
+ if (Array.isArray(original)) original.splice(1, 0, newOne);
+ else retVal.props.children.props.children[1].props.children = [original, newOne];
+ }));
+ }
+
+ showModal(modal) {
+ const popout = document.querySelector(UserPopoutSelectors.userPopout);
+ if (popout) popout.style.display = "none";
+ const app = document.querySelector(".app-19_DXt");
+ if (app) app.append(modal);
+ else document.querySelector("#app-mount").append(modal);
+ }
+
+ createModalChannel(name, channel, guild) {
+ return this.createModal(`#${name}`, channel.permissionOverwrites, guild.roles, true);
+ }
+
+ createModalUser(name, user, guild) {
+ const userRoles = user.roles.slice(0);
+ const guildRoles = Object.assign({}, guild.roles);
+
+ userRoles.push(guild.id);
+ userRoles.sort((a, b) => {return guildRoles[b].position - guildRoles[a].position;});
+
+ if (user.userId == guild.ownerId) {
+ const ALL_PERMISSIONS = Object.values(DiscordModules.DiscordConstants.Permissions).reduce((all, p) => all | p);
+ userRoles.push(user.userId);
+ guildRoles[user.userId] = {name: this.strings.modal.owner, permissions: ALL_PERMISSIONS};
+ }
+ return this.createModal(name, userRoles, guildRoles);
+ }
+
+ createModalGuild(name, guild) {
+ return this.createModal(name, guild.roles);
+ }
+
+ createModal(title, displayRoles, referenceRoles, isOverride = false) {
+ if (!referenceRoles) referenceRoles = displayRoles;
+ const modal = DOMTools.createElement(Utilities.formatTString(Utilities.formatTString(this.modalHTML, this.strings.modal), {name: escapeHTML(title)}));
+ modal.querySelector(".callout-backdrop").addEventListener("click", () => {
+ modal.classList.add("closing");
+ setTimeout(() => {modal.remove();}, 300);
+ });
+
+ const strings = DiscordModules.Strings;
+ for (const r in displayRoles) {
+ const role = Array.isArray(displayRoles) ? displayRoles[r] : r;
+ const user = UserStore.getUser(role) || {avatarURL: AvatarDefaults.DEFAULT_AVATARS[Math.floor(Math.random() * AvatarDefaults.DEFAULT_AVATARS.length)], username: role};
+ const member = MemberStore.getMember(SelectedGuildStore.getGuildId(), role) || {colorString: ""};
+ const item = DOMTools.createElement(!isOverride || displayRoles[role].type == 0 ? this.modalButton : Utilities.formatTString(this.modalButtonUser, {avatarUrl: user.avatarURL}));
+ if (!isOverride || displayRoles[role].type == 0) item.style.color = referenceRoles[role].colorString;
+ else item.style.color = member.colorString;
+ if (isOverride) item.querySelector(".role-name").innerHTML = escapeHTML(displayRoles[role].type == 0 ? referenceRoles[role].name : user.username);
+ else item.querySelector(".role-name").innerHTML = escapeHTML(referenceRoles[role].name);
+ modal.querySelector(".role-scroller").append(item);
+ item.addEventListener("click", () => {
+ modal.querySelectorAll(".role-item.selected").forEach(e => e.classList.remove("selected"));
+ item.classList.add("selected");
+ const allowed = isOverride ? displayRoles[role].allow : referenceRoles[role].permissions;
+ const denied = isOverride ? displayRoles[role].deny : null;
+
+ const permList = modal.querySelector(".perm-scroller");
+ permList.innerHTML = "";
+ for (const perm in DiscordPerms) {
+ const element = DOMTools.createElement(this.modalItem);
+ const permAllowed = (allowed & DiscordPerms[perm]) == DiscordPerms[perm];
+ const permDenied = isOverride ? (denied & DiscordPerms[perm]) == DiscordPerms[perm] : !permAllowed;
+ if (!permAllowed && !permDenied) continue;
+ if (permAllowed) {
+ element.classList.add("allowed");
+ element.prepend(DOMTools.createElement(this.permAllowedIcon));
+ }
+ if (permDenied) {
+ element.classList.add("denied");
+ element.prepend(DOMTools.createElement(this.permDeniedIcon));
+ }
+ element.querySelector(".perm-name").textContent = strings[perm] || perm.split("_").map(n => n[0].toUpperCase() + n.slice(1).toLowerCase()).join(" ");
+ permList.append(element);
+ }
+ });
+ item.addEventListener("contextmenu", (e) => {
+ DCM.openContextMenu(e, DCM.buildMenu([
+ {label: DiscordModules.Strings.COPY_ID, action: () => {DiscordModules.ElectronModule.copy(role);}}
+ ]));
+ });
+ }
+
+ modal.querySelector(".role-item").click();
+
+ return modal;
+ }
+
+ getSettingsPanel() {
+ const panel = this.buildSettingsPanel();
+ panel.addListener((id, checked) => {
+ if (id == "popouts") {
+ if (checked) this.bindPopouts();
+ else this.unbindPopouts();
+ }
+ if (id == "contextMenus") {
+ if (checked) this.bindContextMenus();
+ this.unbindContextMenus();
+ }
+ if (id == "displayMode") this.setDisplayMode(checked);
+ });
+ return panel.getElement();
+ }
+
+ };
+};
+ return plugin(Plugin, Api);
+ })(global.ZeresPluginLibrary.buildPlugin(config));
+})();
+/*@end@*/
\ No newline at end of file
diff --git a/.config/BetterDiscord/plugins/PinDMs.config.json b/.config/BetterDiscord/plugins/PinDMs.config.json
new file mode 100755
index 0000000..701003a
--- /dev/null
+++ b/.config/BetterDiscord/plugins/PinDMs.config.json
@@ -0,0 +1,52 @@
+{
+ "all": {
+ "general": {
+ "pinIcon": true,
+ "unreadAmount": true,
+ "channelAmount": true
+ },
+ "pinned": {
+ "662731831908761636": {
+ "channelList": {
+ "6103412714032048": {
+ "id": "6103412714032048",
+ "name": "Pins",
+ "dms": [
+ "851263280551165977",
+ "709081938622939166",
+ "736308155964850176",
+ "662733504534020118",
+ "740325591684874305",
+ "815678838809296926"
+ ],
+ "pos": 0,
+ "collapsed": false,
+ "color": null
+ }
+ }
+ }
+ },
+ "preCategories": {
+ "friends": {
+ "enabled": false,
+ "collapsed": false
+ },
+ "blocked": {
+ "enabled": false,
+ "collapsed": false
+ },
+ "bots": {
+ "enabled": false,
+ "collapsed": false
+ },
+ "groups": {
+ "enabled": false,
+ "collapsed": false
+ }
+ },
+ "recentOrder": {
+ "channelList": false,
+ "guildList": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/.config/BetterDiscord/plugins/PinDMs.plugin.js b/.config/BetterDiscord/plugins/PinDMs.plugin.js
new file mode 100755
index 0000000..5e36324
--- /dev/null
+++ b/.config/BetterDiscord/plugins/PinDMs.plugin.js
@@ -0,0 +1,1346 @@
+/**
+ * @name PinDMs
+ * @author DevilBro
+ * @authorId 278543574059057154
+ * @version 1.9.3
+ * @description Allows you to pin DMs, making them appear at the top of your DMs/ServerList
+ * @invite Jx3TjNS
+ * @donate https://www.paypal.me/MircoWittrien
+ * @patreon https://www.patreon.com/MircoWittrien
+ * @website https://mwittrien.github.io/
+ * @source https://github.com/mwittrien/BetterDiscordAddons/tree/master/Plugins/PinDMs/
+ * @updateUrl https://mwittrien.github.io/BetterDiscordAddons/Plugins/PinDMs/PinDMs.plugin.js
+ */
+
+module.exports = (_ => {
+ const config = {
+ "info": {
+ "name": "PinDMs",
+ "author": "DevilBro",
+ "version": "1.9.3",
+ "description": "Allows you to pin DMs, making them appear at the top of your DMs/ServerList"
+ }
+ };
+
+ return !window.BDFDB_Global || (!window.BDFDB_Global.loaded && !window.BDFDB_Global.started) ? class {
+ getName () {return config.info.name;}
+ getAuthor () {return config.info.author;}
+ getVersion () {return config.info.version;}
+ getDescription () {return `The Library Plugin needed for ${config.info.name} is missing. Open the Plugin Settings to download it. \n\n${config.info.description}`;}
+
+ downloadLibrary () {
+ require("request").get("https://mwittrien.github.io/BetterDiscordAddons/Library/0BDFDB.plugin.js", (e, r, b) => {
+ if (!e && b && r.statusCode == 200) require("fs").writeFile(require("path").join(BdApi.Plugins.folder, "0BDFDB.plugin.js"), b, _ => BdApi.showToast("Finished downloading BDFDB Library", {type: "success"}));
+ else BdApi.alert("Error", "Could not download BDFDB Library Plugin. Try again later or download it manually from GitHub: https://mwittrien.github.io/downloader/?library");
+ });
+ }
+
+ load () {
+ if (!window.BDFDB_Global || !Array.isArray(window.BDFDB_Global.pluginQueue)) window.BDFDB_Global = Object.assign({}, window.BDFDB_Global, {pluginQueue: []});
+ if (!window.BDFDB_Global.downloadModal) {
+ window.BDFDB_Global.downloadModal = true;
+ BdApi.showConfirmationModal("Library Missing", `The Library Plugin needed for ${config.info.name} is missing. Please click "Download Now" to install it.`, {
+ confirmText: "Download Now",
+ cancelText: "Cancel",
+ onCancel: _ => {delete window.BDFDB_Global.downloadModal;},
+ onConfirm: _ => {
+ delete window.BDFDB_Global.downloadModal;
+ this.downloadLibrary();
+ }
+ });
+ }
+ if (!window.BDFDB_Global.pluginQueue.includes(config.info.name)) window.BDFDB_Global.pluginQueue.push(config.info.name);
+ }
+ start () {this.load();}
+ stop () {}
+ getSettingsPanel () {
+ let template = document.createElement("template");
+ template.innerHTML = `The Library Plugin needed for ${config.info.name} is missing.\nPlease click
Download Now to install it.
`;
+ template.content.firstElementChild.querySelector("a").addEventListener("click", this.downloadLibrary);
+ return template.content.firstElementChild;
+ }
+ } : (([Plugin, BDFDB]) => {
+ var hoveredCategory, draggedCategory, releasedCategory;
+ var hoveredChannel, draggedChannel, releasedChannel;
+
+ return class PinDMs extends Plugin {
+ onLoad () {
+ this.defaults = {
+ general: {
+ pinIcon: {value: true, description: "Show a little 'Pin' Icon for pinned DMs in the Server List"},
+ unreadAmount: {value: true, description: "Shows the Amount of unread Messages in a Category in the Channel List"},
+ channelAmount: {value: true, description: "Shows the Amount of pinned DMs in a Category in the Channel List"}
+ },
+ recentOrder: {
+ channelList: {value: false, description: "Channel List"},
+ guildList: {value: false, description: "Server List"},
+ },
+ preCategories: {
+ friends: {value: {enabled: false, collapsed: false}, description: "FRIENDS"},
+ blocked: {value: {enabled: false, collapsed: false}, description: "BLOCKED"},
+ bots: {value: {enabled: false, collapsed: false}, description: "Bots"},
+ groups: {value: {enabled: false, collapsed: false}, description: "GROUPS"}
+ }
+ };
+
+ this.patchedModules = {
+ before: {
+ PrivateChannelsList: "render"
+ },
+ after: {
+ PrivateChannelsList: "render",
+ PrivateChannel: ["render", "componentDidMount"],
+ DirectMessage: ["render", "componentDidMount", "componentWillUnmount"]
+ }
+ };
+
+ this.css = `
+ ${BDFDB.dotCN.dmchannel}:hover ${BDFDB.dotCN._pindmsunpinbutton} {
+ display: block;
+ }
+ ${BDFDB.dotCN._pindmspinnedchannelsheadercontainer} {
+ display: flex;
+ cursor: pointer;
+ }
+ ${BDFDB.dotCNS._pindmspinnedchannelsheadercontainer + BDFDB.dotCN.dmchannelheadertext} {
+ margin-right: 6px;
+ }
+ ${BDFDB.dotCN._pindmspinnedchannelsheadercontainer + BDFDB.dotCN._pindmspinnedchannelsheadercolored}:hover ${BDFDB.dotCN.dmchannelheadertext} {
+ filter: brightness(150%);
+ }
+ ${BDFDB.dotCNS._pindmspinnedchannelsheadercontainer + BDFDB.dotCN._pindmspinnedchannelsheaderamount} {
+ position: relative;
+ top: -1px;
+ margin-right: 6px;
+ background-color: var(--background-accent);
+ }
+ ${BDFDB.dotCN._pindmspinnedchannelsheaderarrow} {
+ flex: 0;
+ width: 16px;
+ height: 16px;
+ margin-left: 0;
+ margin-right: 2px;
+ }
+ ${BDFDB.dotCNS._pindmspinnedchannelsheadercollapsed + BDFDB.dotCN._pindmspinnedchannelsheaderarrow + BDFDB.dotCN.channelheadericonwrapper} {
+ transform: rotate(-90deg);
+ }
+ ${BDFDB.dotCN._pindmsunpinbutton} {
+ display: none;
+ width: 16px;
+ height: 16px;
+ opacity: .7;
+ margin: 2px;
+ }
+ ${BDFDB.dotCN._pindmsunpinbutton}:hover {
+ opacity: 1;
+ }
+ ${BDFDB.dotCN._pindmsunpinicon} {
+ display: block;
+ width: 16px;
+ height: 16px;
+ }
+ ${BDFDB.dotCNS._pindmsdmchannelplaceholder + BDFDB.dotCN.namecontainerlayout} {
+ box-sizing: border-box;
+ border: 1px dashed currentColor;
+ }
+ ${BDFDB.dotCN._pindmspinnedchannelsheadercontainer + BDFDB.dotCN._pindmsdmchannelplaceholder} {
+ margin-left: 8px;
+ height: 12px;
+ box-sizing: border-box;
+ border: 1px dashed currentColor;
+ }
+ ${BDFDB.dotCN._pindmsdragpreview} {
+ pointer-events: none !important;
+ position: absolute !important;
+ opacity: 0.5 !important;
+ z-index: 10000 !important;
+ }
+ `;
+ }
+
+ onStart () {
+ BDFDB.PatchUtils.patch(this, BDFDB.LibraryModules.DirectMessageUnreadStore, "getUnreadPrivateChannelIds", {after: e => {
+ let sortedRecents = this.sortAndUpdate("guildList");
+ if (sortedRecents.length) {
+ const dms = [];
+ for (let pos in sortedRecents) if (!dms.includes(sortedRecents[pos])) dms.push(sortedRecents[pos]);
+ e.returnValue = BDFDB.ArrayUtils.removeCopies(dms.concat(e.returnValue));
+ }
+ }});
+
+ this.forceUpdateAll();
+ }
+
+ onStop () {
+ this.forceUpdateAll();
+ }
+
+ getSettingsPanel (collapseStates = {}) {
+ let settingsPanel;
+ return settingsPanel = BDFDB.PluginUtils.createSettingsPanel(this, {
+ collapseStates: collapseStates,
+ children: _ => {
+ let settingsItems = [];
+
+ settingsItems.push(Object.keys(this.defaults.general).map(key => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsSaveItem, {
+ type: "Switch",
+ plugin: this,
+ keys: ["general", key],
+ label: this.defaults.general[key].description,
+ value: this.settings.general[key]
+ })));
+
+ settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormDivider, {
+ className: BDFDB.disCN.marginbottom8
+ }));
+
+ settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsPanelList, {
+ title: "Sort pinned DMs in the 'Recent Message' instead of the 'Pinned at' Order in:",
+ children: Object.keys(this.defaults.recentOrder).map(key => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsSaveItem, {
+ type: "Switch",
+ plugin: this,
+ keys: ["recentOrder", key],
+ label: this.defaults.recentOrder[key].description,
+ value: this.settings.recentOrder[key]
+ }))
+ }));
+
+ settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormDivider, {
+ className: BDFDB.disCN.marginbottom8
+ }));
+
+ settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsPanelList, {
+ title: "Add predefined Category for:",
+ children: Object.keys(this.defaults.preCategories).map(key => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsItem, {
+ type: "Switch",
+ label: BDFDB.LanguageUtils.LanguageStringsCheck[this.defaults.preCategories[key].description] ? BDFDB.LanguageUtils.LanguageStrings[this.defaults.preCategories[key].description] : this.defaults.preCategories[key].description,
+ value: this.settings.preCategories[key].enabled,
+ onChange: value => {
+ this.settings.preCategories[key].enabled = value;
+ BDFDB.DataUtils.save(this.settings.preCategories, this, "preCategories");
+ }
+ }))
+ }));
+
+ settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormDivider, {
+ className: BDFDB.disCN.marginbottom8
+ }));
+
+ settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsItem, {
+ type: "Button",
+ color: BDFDB.LibraryComponents.Button.Colors.RED,
+ label: "Unpin all pinned DMs",
+ onClick: _ => {
+ BDFDB.ModalUtils.confirm(this, "Are you sure you want to unpin all pinned DMs?", _ => BDFDB.DataUtils.remove(this, "pinned", BDFDB.UserUtils.me.id));
+ },
+ children: BDFDB.LanguageUtils.LanguageStrings.UNPIN
+ }));
+
+ return settingsItems.flat(10);
+ }
+ });
+ }
+
+ onSettingsClosed () {
+ if (this.SettingsUpdated) {
+ delete this.SettingsUpdated;
+ this.forceUpdateAll();
+ }
+ }
+
+ forceUpdateAll () {
+ BDFDB.PatchUtils.forceAllUpdates(this);
+ BDFDB.DiscordUtils.rerenderAll();
+ }
+
+ onUserContextMenu (e) {
+ if (e.instance.props.channel && !e.instance.props.channel.guild_id && e.subType == "useCloseDMItem") e.returnvalue.unshift(this.createItem(e.instance.props.channel.id));
+ }
+
+ onChannelContextMenu (e) {
+ if (e.instance.props.channel && !e.instance.props.channel.guild_id && e.instance.props.channel.isGroupDM() && e.subType == "useChannelMarkAsReadItem") {
+ if (e.returnvalue.length > 0) e.returnvalue.push(BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuSeparator, {}));
+ e.returnvalue.push(this.createItem(e.instance.props.channel.id));
+ }
+ }
+
+ createItem (id) {
+ if (!id) return;
+ let pinnedInGuild = this.isPinnedInGuilds(id);
+
+ let categories = this.sortAndUpdateCategories("channelList", true);
+ let currentCategory = this.getChannelListCategory(id);
+
+ return BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, {
+ label: this.labels.context_pindm,
+ id: BDFDB.ContextMenuUtils.createItemId(this.name, "submenu-pin"),
+ children: [
+ BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, {
+ label: this.labels.context_pinchannel,
+ id: BDFDB.ContextMenuUtils.createItemId(this.name, "submenu-channelist"),
+ children: this.getPredefinedCategory(id) ? BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, {
+ label: this.labels.context_inpredefined,
+ id: BDFDB.ContextMenuUtils.createItemId(this.name, "in-predefined"),
+ disabled: true
+ }) : [
+ BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuGroup, {
+ children: currentCategory ? BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, {
+ label: this.labels.context_unpinchannel,
+ id: BDFDB.ContextMenuUtils.createItemId(this.name, "unpin-channellist"),
+ color: BDFDB.LibraryComponents.MenuItems.Colors.DANGER,
+ action: _ => {
+ this.removeFromCategory(id, currentCategory, "channelList");
+ }
+ }) : BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, {
+ label: this.labels.context_addtonewcategory,
+ id: BDFDB.ContextMenuUtils.createItemId(this.name, "new-channellist"),
+ color: BDFDB.LibraryComponents.MenuItems.Colors.BRAND,
+ action: _ => {
+ this.openCategorySettingsModal({
+ id: this.generateID("channelList").toString(),
+ name: `${this.labels.header_pinneddms} #${categories.length + 1}`,
+ dms: [id],
+ pos: categories.length,
+ collapsed: false,
+ color: null
+ }, "channelList", true);
+ }
+ })
+ }),
+ categories.length ? BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuGroup, {
+ children: categories.map(category => currentCategory && currentCategory.id == category.id || category.predefined ? null : BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, {
+ label: category.name || this.labels.header_pinneddms,
+ id: BDFDB.ContextMenuUtils.createItemId(this.name, "pin-channellist", category.id),
+ action: _ => {
+ if (currentCategory) this.removeFromCategory(id, currentCategory, "channelList");
+ this.addToCategory(id, category, "channelList");
+ }
+ })).filter(n => n)
+ }) : null
+ ].filter(n => n)
+ }),
+ BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, {
+ label: this.labels[pinnedInGuild ? "context_unpinguild" : "context_pinguild"],
+ id: BDFDB.ContextMenuUtils.createItemId(this.name, pinnedInGuild ? "unpin-serverlist" : "pin-serverlist"),
+ color: pinnedInGuild ? BDFDB.LibraryComponents.MenuItems.Colors.DANGER : BDFDB.LibraryComponents.MenuItems.Colors.DEFAULT,
+ action: _ => {
+ if (!pinnedInGuild) this.addPin(id, "guildList");
+ else this.removePin(id, "guildList");
+ }
+ })
+ ].filter(n => n)
+ });
+ }
+
+ processPrivateChannelsList (e) {
+ let categories = this.sortAndUpdateCategories("channelList", true);
+ if (categories.length) {
+ e.instance.props.channels = Object.assign({}, e.instance.props.channels);
+ e.instance.props.privateChannelIds = [].concat(e.instance.props.privateChannelIds || []);
+ e.instance.props.pinnedChannelIds = Object.assign({}, e.instance.props.pinnedChannelIds);
+ if (!e.returnvalue) {
+ if (draggedChannel && releasedChannel) {
+ let categoryId = releasedChannel.split("header_")[1];
+ let category = categories.find(n => categoryId != undefined ? n.id == categoryId : n.dms.includes(releasedChannel));
+ if (category) {
+ BDFDB.ArrayUtils.remove(category.dms, draggedChannel, true);
+ category.dms.splice(categoryId != undefined ? 0 : category.dms.indexOf(releasedChannel) + 1, 0, draggedChannel);
+ this.savePinnedChannels(Object.assign({}, this.getPinnedChannels("channelList"), {[category.id]: category}), "channelList");
+ }
+ draggedChannel = null;
+ releasedChannel = null;
+ }
+ if (draggedCategory && releasedCategory) {
+ let maybedDraggedCategory = categories.find(n => n.id == draggedCategory);
+ let maybedReleasedCategory = categories.find(n => n.id == releasedCategory);
+ if (maybedDraggedCategory && maybedReleasedCategory) {
+ BDFDB.ArrayUtils.remove(categories, maybedDraggedCategory, true);
+ categories.splice(categories.indexOf(maybedReleasedCategory) + 1, 0, maybedDraggedCategory);
+ let newCategories = {}, newPos = 0;
+ for (let category of [].concat(categories).reverse()) if (!category.predefined) newCategories[category.id] = Object.assign(category, {pos: newPos++});
+ this.savePinnedChannels(newCategories, "channelList");
+ }
+ draggedCategory = null;
+ releasedCategory = null;
+ }
+ e.instance.props.pinnedChannelIds = {};
+ for (let category of [].concat(categories).reverse()) {
+ e.instance.props.pinnedChannelIds[category.id] = [];
+ for (let id of this.sortDMsByTime(this.filterDMs(category.dms, !category.predefined), "channelList").reverse()) {
+ BDFDB.ArrayUtils.remove(e.instance.props.privateChannelIds, id, true);
+ if (!category.collapsed || e.instance.props.selectedChannelId == id) {
+ e.instance.props.privateChannelIds.unshift(id);
+ e.instance.props.pinnedChannelIds[category.id].push(id);
+ }
+ }
+ }
+ }
+ else this.injectCategories(e.instance, e.returnvalue, categories);
+
+ let pinnedIds = BDFDB.ObjectUtils.toArray(e.instance.props.pinnedChannelIds).reverse();
+ BDFDB.PatchUtils.unpatch(this, e.instance, "renderDM");
+ BDFDB.PatchUtils.patch(this, e.instance, "renderDM", {before: e2 => {
+ if (e2.methodArguments[0] != 0) e2.methodArguments[1] += pinnedIds.slice(0, e2.methodArguments[0] - 1).flat().length;
+ }, after: e2 => {
+ if (e2.methodArguments[0] != 0) {
+ let id = e.instance.props.privateChannelIds[e2.methodArguments[1]];
+ e2.returnValue = e.instance.props.channels[id] ? BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.PrivateChannelItems[e.instance.props.channels[id].isMultiUserDM() ? "GroupDM" : "DirectMessage"], Object.assign({
+ key: id,
+ channel: e.instance.props.channels[id],
+ selected: e.instance.props.selectedChannelId == id
+ }, (e.instance.props.navigator || e.instance.props.listNavigator || {getItemProps: (_ => {})}).getItemProps({
+ index: e2.methodArguments[2]
+ }))) : null;
+
+ let category = categories[e2.methodArguments[0] - 1]; // WRONG
+ if (category) {
+ if (!id || (category.collapsed && e.instance.props.selectedChannelId != id) || !this.filterDMs(category.dms, !category.predefined).includes(id) || draggedCategory == category.id || draggedChannel == id) e2.returnValue = null;
+ else if (hoveredCategory == category.id && [].concat(category.dms).reverse()[0] == id) e2.returnValue = [
+ e2.returnValue,
+ BDFDB.ReactUtils.createElement("h2", {
+ className: BDFDB.disCNS.dmchannelheadercontainer + BDFDB.disCNS._pindmspinnedchannelsheadercontainer + BDFDB.disCNS._pindmsdmchannelplaceholder + BDFDB.disCN.namecontainernamecontainer
+ })
+ ].filter(n => n);
+ else if (hoveredChannel == id) e2.returnValue = [
+ e2.returnValue,
+ BDFDB.ReactUtils.createElement("div", {
+ className: BDFDB.disCNS.dmchannel + BDFDB.disCNS._pindmsdmchannelpinned + BDFDB.disCNS._pindmsdmchannelplaceholder + BDFDB.disCN.namecontainernamecontainer,
+ children: BDFDB.ReactUtils.createElement("div", {
+ className: BDFDB.disCN.namecontainerlayout
+ })
+ })
+ ].filter(n => n);
+ }
+ }
+ }}, {force: true, noCache: true});
+ }
+ }
+
+ injectCategories (instance, returnvalue, categories) {
+ if (!returnvalue) return;
+ else if (returnvalue && returnvalue.props && BDFDB.ArrayUtils.is(returnvalue.props.sections)) {
+ returnvalue.props.sections = [];
+ returnvalue.props.sections.push(instance.state.preRenderedChildren);
+ let shownPinnedIds = BDFDB.ObjectUtils.toArray(instance.props.pinnedChannelIds).reverse();
+ for (let ids of shownPinnedIds) returnvalue.props.sections.push(ids.length || 1);
+ returnvalue.props.sections.push(instance.props.privateChannelIds.length - shownPinnedIds.flat().length);
+
+ let sectionHeight = returnvalue.props.sectionHeight;
+ let sectionHeightFunc = typeof sectionHeight != "function" ? _ => sectionHeight : sectionHeight;
+ returnvalue.props.sectionHeight = (...args) => {
+ if (args[0] != 0 && args[0] != returnvalue.props.sections.length - 1) {
+ let category = categories[args[0] - 1];
+ if (category) return 40;
+ }
+ return sectionHeightFunc(...args);
+ };
+
+ let rowHeight = returnvalue.props.rowHeight;
+ let rowHeightFunc = typeof rowHeight != "function" ? _ => rowHeight : rowHeight;
+ returnvalue.props.rowHeight = (...args) => {
+ if (args[0] != 0 && args[0] != returnvalue.props.sections.length - 1) {
+ let category = categories[args[0] - 1];
+ if (category && (category.collapsed || category.id == draggedCategory)) return 0;
+ }
+ return rowHeightFunc(...args);
+ };
+
+ let renderRow = returnvalue.props.renderRow;
+ returnvalue.props.renderRow = (...args) => {
+ let row = renderRow(...args);
+ return row && row.key == "no-private-channels" ? null : row;
+ };
+
+ let renderSection = returnvalue.props.renderSection;
+ returnvalue.props.renderSection = (...args) => {
+ if (args[0].section != 0 && args[0].section != returnvalue.props.sections.length - 1) {
+ let category = categories[args[0].section - 1];
+ if (category && draggedCategory != category.id) {
+ let color = BDFDB.ColorUtils.convert(category.color, "RGBA");
+ let foundDMs = this.filterDMs(category.dms, !category.predefined);
+ let unreadAmount = this.settings.general.unreadAmount && BDFDB.ArrayUtils.sum(foundDMs.map(id => BDFDB.LibraryModules.UnreadChannelUtils.getMentionCount(id)));
+ return category.predefined && foundDMs.length < 1 ? null : [
+ BDFDB.ReactUtils.createElement("h2", {
+ className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.dmchannelheadercontainer, BDFDB.disCN._pindmspinnedchannelsheadercontainer, category.collapsed && BDFDB.disCN._pindmspinnedchannelsheadercollapsed, color && BDFDB.disCN._pindmspinnedchannelsheadercolored, BDFDB.disCN.namecontainernamecontainer),
+ categoryId: category.id,
+ onMouseDown: category.predefined ? null : event => {
+ event = event.nativeEvent || event;
+ let node = BDFDB.DOMUtils.getParent(BDFDB.dotCN._pindmspinnedchannelsheadercontainer, event.target).cloneNode(true);
+ let mouseMove = event2 => {
+ if (Math.sqrt((event.pageX - event2.pageX)**2) > 20 || Math.sqrt((event.pageY - event2.pageY)**2) > 20) {
+ BDFDB.ListenerUtils.stopEvent(event);
+ draggedCategory = category.id;
+ this.updateContainer("channelList");
+ let dragPreview = this.createDragPreview(node, event2);
+ document.removeEventListener("mousemove", mouseMove);
+ document.removeEventListener("mouseup", mouseUp);
+ let dragging = event3 => {
+ this.updateDragPreview(dragPreview, event3);
+ let placeholder = BDFDB.DOMUtils.getParent(BDFDB.dotCN._pindmsdmchannelplaceholder, event3.target);
+ let categoryNode = BDFDB.DOMUtils.getParent(BDFDB.dotCN._pindmspinnedchannelsheadercontainer, placeholder ? placeholder.previousSibling : event3.target);
+ let maybeHoveredCategory = categoryNode && categoryNode.getAttribute("categoryId");
+ let update = maybeHoveredCategory != hoveredCategory;
+ if (maybeHoveredCategory && !this.defaults.preCategories[maybeHoveredCategory]) hoveredCategory = maybeHoveredCategory;
+ else hoveredCategory = null;
+ if (update) this.updateContainer("channelList");
+ };
+ let releasing = event3 => {
+ BDFDB.DOMUtils.remove(dragPreview);
+ if (hoveredCategory) releasedCategory = hoveredCategory;
+ else draggedCategory = null;
+ hoveredCategory = null;
+ this.updateContainer("channelList");
+ document.removeEventListener("mousemove", dragging);
+ document.removeEventListener("mouseup", releasing);
+ };
+ document.addEventListener("mousemove", dragging);
+ document.addEventListener("mouseup", releasing);
+ }
+ };
+ let mouseUp = _ => {
+ document.removeEventListener("mousemove", mouseMove);
+ document.removeEventListener("mouseup", mouseUp);
+ };
+ document.addEventListener("mousemove", mouseMove);
+ document.addEventListener("mouseup", mouseUp);
+ },
+ onClick: _ => {
+ if (foundDMs.length || !category.collapsed) {
+ category.collapsed = !category.collapsed;
+ if (category.predefined) {
+ this.settings.preCategories[category.id].collapsed = category.collapsed;
+ BDFDB.DataUtils.save(this.settings.preCategories, this, "preCategories");
+ }
+ else this.savePinnedChannels(Object.assign({}, this.getPinnedChannels("channelList"), {[category.id]: category}), "channelList");
+ this.updateContainer("channelList");
+ }
+ },
+ onContextMenu: event => {
+ BDFDB.ContextMenuUtils.open(this, event, BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuGroup, {
+ children: category.predefined ? BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, {
+ label: this.labels.context_disablepredefined,
+ id: BDFDB.ContextMenuUtils.createItemId(this.name, "disable-predefined"),
+ action: _ => {
+ if (!this.settings.preCategories[category.id]) return;
+ this.settings.preCategories[category.id].enabled = false;
+ BDFDB.DataUtils.save(this.settings.preCategories, this, "preCategories");
+ this.updateContainer("channelList");
+ }
+ }) : [
+ BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, {
+ label: BDFDB.LanguageUtils.LanguageStrings.CATEGORY_SETTINGS,
+ id: BDFDB.ContextMenuUtils.createItemId(this.name, "category-settings"),
+ action: _ => {
+ this.openCategorySettingsModal(category, "channelList");
+ }
+ }),
+ BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, {
+ label: BDFDB.LanguageUtils.LanguageStrings.DELETE_CATEGORY,
+ id: BDFDB.ContextMenuUtils.createItemId(this.name, "remove-category"),
+ color: BDFDB.LibraryComponents.MenuItems.Colors.DANGER,
+ action: _ => {
+ let newData = this.getPinnedChannels("channelList");
+ delete newData[category.id];
+ this.savePinnedChannels(newData, "channelList");
+ this.updateContainer("channelList");
+ }
+ })
+ ]
+ }));
+ },
+ children: [
+ BDFDB.ObjectUtils.is(color) ? BDFDB.ReactUtils.createElement("span", {
+ className: BDFDB.disCN.dmchannelheadertext,
+ children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TextGradientElement, {
+ gradient: BDFDB.ColorUtils.createGradient(color),
+ children: category.name
+ })
+ }) : BDFDB.ReactUtils.createElement("span", {
+ className: BDFDB.disCN.dmchannelheadertext,
+ style: {color: color},
+ children: category.name,
+ }),
+ unreadAmount ? BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Badges.NumberBadge, {
+ className: BDFDB.disCN._pindmspinnedchannelsheaderamount,
+ count: unreadAmount
+ }) : null,
+ this.settings.general.channelAmount ? BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Badges.NumberBadge, {
+ className: BDFDB.disCN._pindmspinnedchannelsheaderamount,
+ count: foundDMs.length,
+ disableColor: true
+ }) : null,
+ BDFDB.ReactUtils.createElement("div", {
+ className: BDFDB.disCNS._pindmspinnedchannelsheaderarrow + BDFDB.disCNS.channelheadericonwrapper + BDFDB.disCN.channelheadericonclickable,
+ children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, {
+ className: BDFDB.disCNS._pindmspinnedchannelsheaderarrow + BDFDB.disCN.channelheadericon,
+ nativeClass: true,
+ iconSVG: ``
+ })
+ })
+ ].filter(n => n)
+ }),
+ hoveredChannel == "header_" + category.id && BDFDB.ReactUtils.createElement("div", {
+ className: BDFDB.disCNS.dmchannel + BDFDB.disCNS._pindmsdmchannelpinned + BDFDB.disCNS._pindmsdmchannelplaceholder + BDFDB.disCN.namecontainernamecontainer,
+ children: BDFDB.ReactUtils.createElement("div", {
+ className: BDFDB.disCN.namecontainerlayout
+ })
+ })
+ ].filter(n => n);
+ }
+ else return null;
+ }
+ else return renderSection(...args);
+ };
+ }
+ else if (typeof returnvalue.props.children == "function") {
+ let childrenRender = returnvalue.props.children;
+ returnvalue.props.children = BDFDB.TimeUtils.suppress((...args) => {
+ let children = childrenRender(...args);
+ this.injectCategories(instance, children, categories);
+ return children;
+ }, "Error in Children Render of PrivateChannelList!", this);
+ }
+ else if (BDFDB.ArrayUtils.is(returnvalue)) {
+ for (let child of returnvalue) this.injectCategories(instance, child, categories);
+ }
+ else this.injectCategories(instance, returnvalue.props.children, categories);
+ }
+
+ processPrivateChannel (e) {
+ if (e.instance.props.channel && !this.getPredefinedCategory(e.instance.props.channel.id)) {
+ let category = this.getChannelListCategory(e.instance.props.channel.id);
+ if (category) {
+ if (e.node) {
+ BDFDB.DOMUtils.addClass(e.node, BDFDB.disCN._pindmsdmchannelpinned);
+ e.node.removeEventListener("mousedown", e.node.PinDMsMouseDownListener);
+ if (!this.settings.recentOrder.channelList) {
+ e.node.setAttribute("draggable", false);
+ e.node.PinDMsMouseDownListener = event => {
+ if (!this.started) e.node.removeEventListener("mousedown", e.node.PinDMsMouseDownListener);
+ else {
+ event = event.nativeEvent || event;
+ let mouseMove = event2 => {
+ if (Math.sqrt((event.pageX - event2.pageX)**2) > 20 || Math.sqrt((event.pageY - event2.pageY)**2) > 20) {
+ BDFDB.ListenerUtils.stopEvent(event);
+ draggedChannel = e.instance.props.channel.id;
+ this.updateContainer("channelList");
+ let dragPreview = this.createDragPreview(e.node, event2);
+ document.removeEventListener("mousemove", mouseMove);
+ document.removeEventListener("mouseup", mouseUp);
+ let dragging = event3 => {
+ this.updateDragPreview(dragPreview, event3);
+ let maybeHoveredChannel = null;
+ let categoryNode = BDFDB.DOMUtils.getParent(BDFDB.dotCN._pindmspinnedchannelsheadercontainer, event3.target);
+ if (categoryNode) {
+ let hoveredCategoryId = categoryNode.getAttribute("categoryid");
+ if (hoveredCategoryId && hoveredCategoryId == category.id) maybeHoveredChannel = "header_" + category.id;
+ }
+ else {
+ let placeholder = BDFDB.DOMUtils.getParent(BDFDB.dotCN._pindmsdmchannelplaceholder, event3.target);
+ maybeHoveredChannel = (BDFDB.ReactUtils.findValue(BDFDB.DOMUtils.getParent(BDFDB.dotCN._pindmsdmchannelpinned, placeholder ? placeholder.previousSibling : event3.target), "channel", {up: true}) || {}).id;
+ let maybeHoveredCategory = maybeHoveredChannel && this.getChannelListCategory(maybeHoveredChannel);
+ if (!maybeHoveredCategory || maybeHoveredCategory.id != category.id) maybeHoveredChannel = null;
+ };
+ let update = maybeHoveredChannel != hoveredChannel;
+ if (maybeHoveredChannel) hoveredChannel = maybeHoveredChannel;
+ else hoveredChannel = null;
+ if (update) this.updateContainer("channelList");
+ };
+ let releasing = event3 => {
+ BDFDB.DOMUtils.remove(dragPreview);
+ if (hoveredChannel) releasedChannel = hoveredChannel;
+ else draggedChannel = null;
+ hoveredChannel = null;
+ this.updateContainer("channelList");
+ document.removeEventListener("mousemove", dragging);
+ document.removeEventListener("mouseup", releasing);
+ };
+ document.addEventListener("mousemove", dragging);
+ document.addEventListener("mouseup", releasing);
+ }
+ };
+ let mouseUp = _ => {
+ document.removeEventListener("mousemove", mouseMove);
+ document.removeEventListener("mouseup", mouseUp);
+ };
+ document.addEventListener("mousemove", mouseMove);
+ document.addEventListener("mouseup", mouseUp);
+ }
+ };
+ e.node.addEventListener("mousedown", e.node.PinDMsMouseDownListener);
+ }
+ }
+ if (e.returnvalue) {
+ let wrapper = e.returnvalue && e.returnvalue.props.children && e.returnvalue.props.children.props && typeof e.returnvalue.props.children.props.children == "function" ? e.returnvalue.props.children : e.returnvalue;
+ if (typeof wrapper.props.children == "function") {
+ let childrenRender = wrapper.props.children;
+ wrapper.props.children = BDFDB.TimeUtils.suppress((...args) => {
+ let children = childrenRender(...args);
+ this._processPrivateChannel(e.instance, children, category);
+ return children;
+ }, "Error in Children Render of PrivateChannel!", this);
+ }
+ else this._processPrivateChannel(e.instance, wrapper, category);
+ }
+ }
+ }
+ }
+
+ _processPrivateChannel (instance, returnvalue, category) {
+ let [children, index] = BDFDB.ReactUtils.findParent(returnvalue, {name: "CloseButton"});
+ if (index > -1) children.splice(index, 0, BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TooltipContainer, {
+ text: BDFDB.LanguageUtils.LanguageStrings.UNPIN,
+ children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Clickable, {
+ className: BDFDB.disCN._pindmsunpinbutton,
+ onClick: event => {
+ BDFDB.ListenerUtils.stopEvent(event);
+ this.removeFromCategory(instance.props.channel.id, category, "channelList");
+ },
+ children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, {
+ className: BDFDB.disCN._pindmsunpinicon,
+ name: BDFDB.LibraryComponents.SvgIcon.Names.PIN
+ })
+ })
+ }));
+ }
+
+ processDirectMessage (e) {
+ if (e.instance.props.channel) {
+ if (e.node) {
+ if (e.methodname == "componentDidMount") {
+ BDFDB.DOMUtils.removeClass(e.node, BDFDB.disCN._pindmsrecentpinned);
+ if (this.isPinnedInGuilds(e.instance.props.channel.id)) BDFDB.DOMUtils.addClass(e.node, BDFDB.disCN._pindmsrecentpinned);
+ }
+ if (e.methodname == "componentWillUnmount") {
+ if (this.getChannelListCategory(e.instance.props.channel.id)) this.updateContainer("channelList");
+ }
+ }
+ if (e.returnvalue) {
+ if (this.settings.general.pinIcon && this.isPinnedInGuilds(e.instance.props.channel.id)) {
+ let [children, index] = BDFDB.ReactUtils.findParent(e.returnvalue, {name: "BlobMask"});
+ if (index > -1) children[index].props.upperLeftBadge = BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Badges.IconBadge, {
+ className: BDFDB.disCN.guildiconbadge,
+ disableColor: true,
+ style: {transform: "scale(-1, 1)"},
+ icon: BDFDB.LibraryComponents.SvgIcon.Names.NOVA_PIN
+ });
+ }
+ if (this.getChannelListCategory(e.instance.props.channel.id)) this.updateContainer("channelList");
+ }
+ }
+ }
+
+ getPinnedChannels (type) {
+ return ((BDFDB.DataUtils.load(this, "pinned", BDFDB.UserUtils.me.id) || {})[type] || {});
+ }
+
+ savePinnedChannels (channels, type) {
+ let pinned = BDFDB.DataUtils.load(this, "pinned", BDFDB.UserUtils.me.id) || {};
+ if (BDFDB.ObjectUtils.is(channels) && !BDFDB.ObjectUtils.isEmpty(channels)) pinned[type] = channels;
+ else delete pinned[type];
+ if (!BDFDB.ObjectUtils.isEmpty(pinned)) BDFDB.DataUtils.save(pinned, this, "pinned", BDFDB.UserUtils.me.id);
+ else BDFDB.DataUtils.remove(this, "pinned", BDFDB.UserUtils.me.id);
+ }
+
+ generateID (type) {
+ if (!type) return null;
+ let categories = this.getPinnedChannels(type);
+ let id = Math.round(Math.random() * 10000000000000000);
+ return categories[id] ? this.generateID() : id;
+ }
+
+ filterDMs (dms, removePredefined) {
+ return dms.filter(id => BDFDB.LibraryModules.ChannelStore.getChannel(id) && !(removePredefined && this.getPredefinedCategory(id)));
+ }
+
+ addToCategory (id, category, type) {
+ if (!id || !category || !type) return;
+ let wasEmpty = !this.filterDMs(category.dms).length;
+ if (!category.dms.includes(id)) category.dms.unshift(id);
+ this.savePinnedChannels(Object.assign({}, this.getPinnedChannels(type), {[category.id]: category}), type);
+ if (wasEmpty && category.dms.length) category.collapsed = false;
+ this.updateContainer(type);
+ }
+
+ removeFromCategory (id, category, type) {
+ if (!id || !category || !type) return;
+ BDFDB.ArrayUtils.remove(category.dms, id, true);
+ if (!this.filterDMs(category.dms).length) category.collapsed = true;
+ this.savePinnedChannels(Object.assign({}, this.getPinnedChannels(type), {[category.id]: category}), type);
+ this.updateContainer(type);
+ }
+
+ getChannelListCategory (id) {
+ if (!id) return null;
+ let categories = this.getPinnedChannels("channelList");
+ for (let catId in categories) if (categories[catId].dms.includes(id)) return categories[catId];
+ return null;
+ }
+
+ getPredefinedCategory (id) {
+ if (!id) return "";
+ let channel = BDFDB.LibraryModules.ChannelStore.getChannel(id);
+ if (!channel) return "";
+ else if (this.settings.preCategories.friends.enabled && channel.isDM() && BDFDB.LibraryModules.RelationshipStore.isFriend(channel.recipients[0])) return "friends";
+ else if (this.settings.preCategories.blocked.enabled && channel.isDM() && BDFDB.LibraryModules.RelationshipStore.isBlocked(channel.recipients[0])) return "blocked";
+ else if (this.settings.preCategories.bots.enabled && channel.isDM() && (BDFDB.LibraryModules.UserStore.getUser(channel.recipients[0]) || {}).bot) return "bots";
+ else if (this.settings.preCategories.groups.enabled && channel.isGroupDM()) return "groups";
+ return "";
+ }
+
+ sortAndUpdateCategories (type, reverse) {
+ let data = BDFDB.ObjectUtils.sort(this.getPinnedChannels(type), "pos"), newData = {};
+ let sorted = [], pos = 0, sort = id => {
+ if (sorted[pos] === undefined) {
+ newData[id] = Object.assign({}, data[id], {pos});
+ sorted[pos] = newData[id];
+ }
+ else {
+ pos++;
+ sort(id);
+ }
+ };
+ for (let id in data) sort(id);
+ if (!BDFDB.equals(data, newData)) this.savePinnedChannels(newData, type);
+ if (type == "channelList" && Object.keys(this.settings.preCategories).some(type => this.settings.preCategories[type].enabled)) {
+ let predefinedDMs = {};
+ for (let channelId of BDFDB.LibraryModules.DirectMessageStore.getPrivateChannelIds()) {
+ let category = this.getPredefinedCategory(channelId);
+ if (category) {
+ if (!predefinedDMs[category]) predefinedDMs[category] = [];
+ predefinedDMs[category].push(channelId);
+ }
+ }
+ for (let type in predefinedDMs) if (predefinedDMs[type].length) sorted.unshift({
+ predefined: true,
+ collapsed: this.settings.preCategories[type].collapsed,
+ color: null,
+ dms: predefinedDMs[type],
+ id: type,
+ name: BDFDB.LanguageUtils.LanguageStringsCheck[this.defaults.preCategories[type].description] ? BDFDB.LanguageUtils.LanguageStrings[this.defaults.preCategories[type].description] : this.defaults.preCategories[type].description
+ });
+ }
+ return (reverse ? sorted.reverse() : sorted).filter(n => n);
+ }
+
+ sortDMsByTime (dms, type) {
+ if (dms.length > 1 && this.settings.recentOrder[type]) {
+ let timestamps = BDFDB.LibraryModules.DirectMessageStore.getPrivateChannelIds().reduce((newObj, channelId) => (newObj[channelId] = BDFDB.LibraryModules.UnreadChannelUtils.lastMessageId(channelId), newObj), {});
+ return [].concat(dms).sort(function (x, y) {
+ const xT = parseFloat(timestamps[x]), yT = parseFloat(timestamps[y]);
+ return xT > yT ? -1 : xT < yT ? 1 : 0;
+ });
+ }
+ else return dms;
+ }
+
+ openCategorySettingsModal (data, type, isNew) {
+ if (!BDFDB.ObjectUtils.is(data) || !type) return;
+ let newData = Object.assign({}, data);
+
+ BDFDB.ModalUtils.open(this, {
+ size: "MEDIUM",
+ header: BDFDB.LanguageUtils.LanguageStrings.CATEGORY_SETTINGS,
+ subHeader: data.name,
+ children: [
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormItem, {
+ title: BDFDB.LanguageUtils.LanguageStrings.CATEGORY_NAME,
+ className: BDFDB.disCN.marginbottom20 + " input-categoryname",
+ children: [
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TextInput, {
+ value: data.name,
+ placeholder: data.name,
+ autoFocus: true,
+ onChange: value => {newData.name = value;}
+ }),
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormDivider, {
+ className: BDFDB.disCN.dividerdefault
+ })
+ ]
+ }),
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormItem, {
+ title: this.labels.modal_colorpicker1,
+ className: BDFDB.disCN.marginbottom20,
+ children: [
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.ColorSwatches, {
+ color: data.color,
+ onColorChange: value => {newData.color = value;}
+ })
+ ]
+ })
+ ],
+ buttons: [{
+ contents: isNew ? BDFDB.LanguageUtils.LanguageStrings.CREATE : BDFDB.LanguageUtils.LanguageStrings.SAVE,
+ color: "BRAND",
+ close: true,
+ onClick: _ => {
+ if (newData.color != null && !BDFDB.ObjectUtils.is(newData.color)) {
+ if (newData.color[0] < 30 && newData.color[1] < 30 && newData.color[2] < 30) newData.color = BDFDB.ColorUtils.change(newData.color, 30);
+ else if (newData.color[0] > 225 && newData.color[1] > 225 && newData.color[2] > 225) newData.color = BDFDB.ColorUtils.change(newData.color, -30);
+ }
+
+ this.savePinnedChannels(Object.assign({}, this.getPinnedChannels(type), {[data.id]: newData}), type);
+
+ this.updateContainer(type);
+ }
+ }]
+ });
+ }
+
+ addPin (id, type) {
+ if (!id) return;
+ let channels = this.getPinnedChannels(type);
+ for (let i in channels) channels[i] = channels[i] + 1;
+ channels[id] = 0;
+ this.savePinnedChannels(channels, type);
+ this.updateContainer(type);
+ }
+
+ removePin (id, type) {
+ if (!id) return;
+ let channels = this.getPinnedChannels(type);
+ delete channels[id];
+ this.savePinnedChannels(channels, type);
+ this.updateContainer(type);
+ }
+
+ isPinnedInGuilds (id) {
+ return this.getPinnedChannels("guildList")[id] != undefined;
+ }
+
+ updateContainer (type) {
+ switch (type) {
+ case "channelList":
+ BDFDB.PatchUtils.forceAllUpdates(this, "PrivateChannelsList");
+ if (!Object.keys(this.settings.preCategories).every(type => this.settings.preCategories[type].enabled) && BDFDB.ObjectUtils.isEmpty(this.getPinnedChannels(type))) this.forceUpdateAll();
+ break;
+ case "guildList":
+ BDFDB.GuildUtils.rerenderAll(true);
+ break;
+ }
+ }
+
+ sortAndUpdate (type) {
+ let data = this.getPinnedChannels(type), newData = {};
+ delete data[""];
+ delete data["null"];
+ let sortedDMs = [], existingDMs = [], sortDM = (id, pos) => {
+ if (sortedDMs[pos] === undefined) sortedDMs[pos] = id;
+ else sortDM(id, pos + 1);
+ };
+ for (let id in data) sortDM(id, data[id]);
+ sortedDMs = sortedDMs.filter(n => n);
+ for (let pos in sortedDMs) {
+ newData[sortedDMs[pos]] = parseInt(pos);
+ if (BDFDB.LibraryModules.ChannelStore.getChannel(sortedDMs[pos])) existingDMs.push(sortedDMs[pos]);
+ }
+ if (!BDFDB.equals(data, newData)) this.savePinnedChannels(newData, this);
+ return this.sortDMsByTime(existingDMs, type);
+ }
+
+ createDragPreview (div, event) {
+ if (!Node.prototype.isPrototypeOf(div)) return;
+ let dragPreview = div.cloneNode(true);
+ BDFDB.DOMUtils.addClass(dragPreview, BDFDB.disCN._pindmsdragpreview);
+ BDFDB.DOMUtils.remove(dragPreview.querySelector(BDFDB.dotCNC.guildlowerbadge + BDFDB.dotCNC.guildupperbadge + BDFDB.dotCN.guildpillwrapper));
+ document.querySelector(BDFDB.dotCN.appmount).appendChild(dragPreview);
+ let rects = BDFDB.DOMUtils.getRects(dragPreview);
+ BDFDB.DOMUtils.hide(dragPreview);
+ dragPreview.style.setProperty("pointer-events", "none", "important");
+ dragPreview.style.setProperty("left", event.clientX - (rects.width/2) + "px", "important");
+ dragPreview.style.setProperty("top", event.clientY - (rects.height/2) + "px", "important");
+ return dragPreview;
+ }
+
+ updateDragPreview (dragPreview, event) {
+ if (!Node.prototype.isPrototypeOf(dragPreview)) return;
+ BDFDB.DOMUtils.show(dragPreview);
+ let rects = BDFDB.DOMUtils.getRects(dragPreview);
+ dragPreview.style.setProperty("left", event.clientX - (rects.width/2) + "px", "important");
+ dragPreview.style.setProperty("top", event.clientY - (rects.height/2) + "px", "important");
+ }
+
+ setLabelsByLanguage () {
+ switch (BDFDB.LanguageUtils.getLanguage().id) {
+ case "bg": // Bulgarian
+ return {
+ context_addtonewcategory: "Добавяне към нова категория",
+ context_disablepredefined: "Деактивирайте предварително определена категория",
+ context_inpredefined: "Фиксирано в предварително дефинирана категория",
+ context_pinchannel: "Фиксиране към списъка с канали",
+ context_pindm: "Закачете директно съобщение",
+ context_pinguild: "Фиксиране към списъка със сървъри",
+ context_unpinchannel: "Отделяне от списъка с канали",
+ context_unpinguild: "Отделете от списъка със сървъри",
+ header_pinneddms: "Закачени директни съобщения",
+ modal_colorpicker1: "Категория цвят"
+ };
+ case "cs": // Czech
+ return {
+ context_addtonewcategory: "Přidat do nové kategorie",
+ context_disablepredefined: "Deaktivovat předdefinovanou kategorii",
+ context_inpredefined: "Připnuté v předdefinované katrgorii",
+ context_pinchannel: "Připnout do seznamu kanálů",
+ context_pindm: "Připnout do PZ",
+ context_pinguild: "Připnout do seznamu serverů",
+ context_unpinchannel: "Odepnout ze seznamu kanálů",
+ context_unpinguild: "Odepnout ze seznamu serverů",
+ header_pinneddms: "Připnuté přímé zprávy",
+ modal_colorpicker1: "Barva kategorie"
+ };
+ case "da": // Danish
+ return {
+ context_addtonewcategory: "Føj til ny kategori",
+ context_disablepredefined: "Deaktiver foruddefineret kategori",
+ context_inpredefined: "Fastgjort i en foruddefineret kategori",
+ context_pinchannel: "Fastgør til kanallisten",
+ context_pindm: "Pin direkte besked",
+ context_pinguild: "Fastgør til serverlisten",
+ context_unpinchannel: "Fjern fra kanallisten",
+ context_unpinguild: "Fjern fra serverlisten",
+ header_pinneddms: "Fastgjorte direkte beskeder",
+ modal_colorpicker1: "Kategorifarve"
+ };
+ case "de": // German
+ return {
+ context_addtonewcategory: "Zur neuen Kategorie hinzufügen",
+ context_disablepredefined: "Vordefinierte Kategorie deaktivieren",
+ context_inpredefined: "In vordefinierter Kategorie angeheftet",
+ context_pinchannel: "An Kanalliste anheften",
+ context_pindm: "Direktnachricht anheften",
+ context_pinguild: "An Serverliste anheften",
+ context_unpinchannel: "Von Kanalliste loslösen",
+ context_unpinguild: "Von Serverliste loslösen",
+ header_pinneddms: "Gepinnte Direktnachrichten",
+ modal_colorpicker1: "Kategoriefarbe"
+ };
+ case "el": // Greek
+ return {
+ context_addtonewcategory: "Προσθήκη σε νέα κατηγορία",
+ context_disablepredefined: "Απενεργοποίηση προκαθορισμένης κατηγορίας",
+ context_inpredefined: "Καρφιτσώθηκε σε μια προκαθορισμένη κατηγορία",
+ context_pinchannel: "Καρφίτσωμα στη λίστα καναλιών",
+ context_pindm: "Καρφιτσώστε το άμεσο μήνυμα",
+ context_pinguild: "Καρφίτσωμα στη λίστα διακομιστών",
+ context_unpinchannel: "Αποσύνδεση από τη λίστα καναλιών",
+ context_unpinguild: "Αποσύνδεση από τη λίστα διακομιστών",
+ header_pinneddms: "Καρφιτσωμένα άμεσα μηνύματα",
+ modal_colorpicker1: "Χρώμα κατηγορίας"
+ };
+ case "es": // Spanish
+ return {
+ context_addtonewcategory: "Agregar a una nueva categoría",
+ context_disablepredefined: "Desactivar categoría predefinida",
+ context_inpredefined: "Anclado en una categoría predefinida",
+ context_pinchannel: "Anclar a la lista de canales",
+ context_pindm: "Pin de mensaje directo",
+ context_pinguild: "Anclar a la lista de servidores",
+ context_unpinchannel: "Separar de la lista de canales",
+ context_unpinguild: "Separar de la lista de servidores",
+ header_pinneddms: "Mensajes directos fijados",
+ modal_colorpicker1: "Color de categoría"
+ };
+ case "fi": // Finnish
+ return {
+ context_addtonewcategory: "Lisää uuteen luokkaan",
+ context_disablepredefined: "Poista ennalta määritetty luokka käytöstä",
+ context_inpredefined: "Kiinnitetty ennalta määritettyyn luokkaan",
+ context_pinchannel: "Kiinnitä kanavaluetteloon",
+ context_pindm: "Kiinnitä suora viesti",
+ context_pinguild: "Kiinnitä palvelinluetteloon",
+ context_unpinchannel: "Irrota kanavaluettelosta",
+ context_unpinguild: "Irrota palvelinluettelosta",
+ header_pinneddms: "Kiinnitetyt suorat viestit",
+ modal_colorpicker1: "Luokan väri"
+ };
+ case "fr": // French
+ return {
+ context_addtonewcategory: "Ajouter à une nouvelle catégorie",
+ context_disablepredefined: "Désactiver la catégorie prédéfinie",
+ context_inpredefined: "Épinglé dans une catégorie prédéfinie",
+ context_pinchannel: "Épingler à la liste des salons",
+ context_pindm: "Épingler le message privé",
+ context_pinguild: "Épingler à la liste des serveurs",
+ context_unpinchannel: "Détacher de la liste des salons",
+ context_unpinguild: "Détacher de la liste des serveurs",
+ header_pinneddms: "Messages privés épinglés",
+ modal_colorpicker1: "Couleur de la catégorie"
+ };
+ case "hi": // Hindi
+ return {
+ context_addtonewcategory: "नई श्रेणी में जोड़ें",
+ context_disablepredefined: "पूर्वनिर्धारित श्रेणी को निष्क्रिय करें",
+ context_inpredefined: "एक पूर्वनिर्धारित श्रेणी में पिन किया गया",
+ context_pinchannel: "चैनल सूची में पिन करें",
+ context_pindm: "पिन डीएम",
+ context_pinguild: "सर्वर सूची में पिन करें",
+ context_unpinchannel: "चैनल सूची से अलग करें",
+ context_unpinguild: "सर्वर सूची से अलग करें",
+ header_pinneddms: "पिन किए गए सीधे संदेश",
+ modal_colorpicker1: "श्रेणी रंग"
+ };
+ case "hr": // Croatian
+ return {
+ context_addtonewcategory: "Dodaj u novu kategoriju",
+ context_disablepredefined: "Deaktivirajte unaprijed definiranu kategoriju",
+ context_inpredefined: "Prikvačeno u unaprijed definiranoj kategoriji",
+ context_pinchannel: "Prikvači na popis kanala",
+ context_pindm: "Prikvači izravnu poruku",
+ context_pinguild: "Prikvači na popis poslužitelja",
+ context_unpinchannel: "Odvojite se od popisa kanala",
+ context_unpinguild: "Odvojite se od popisa poslužitelja",
+ header_pinneddms: "Prikvačene izravne poruke",
+ modal_colorpicker1: "Boja kategorije"
+ };
+ case "hu": // Hungarian
+ return {
+ context_addtonewcategory: "Hozzáadás új kategóriához",
+ context_disablepredefined: "Deaktiválja az előre definiált kategóriát",
+ context_inpredefined: "Előre meghatározott kategóriában rögzítve",
+ context_pinchannel: "Rögzítés a csatornalistához",
+ context_pindm: "Közvetlen üzenet rögzítése",
+ context_pinguild: "Rögzítés a kiszolgáló listához",
+ context_unpinchannel: "Leválasztás a csatornalistáról",
+ context_unpinguild: "Leválasztás a kiszolgáló listáról",
+ header_pinneddms: "Rögzített közvetlen üzenetek",
+ modal_colorpicker1: "Kategória színe"
+ };
+ case "it": // Italian
+ return {
+ context_addtonewcategory: "Aggiungi a una nuova categoria",
+ context_disablepredefined: "Disattiva la categoria predefinita",
+ context_inpredefined: "Bloccato in una categoria predefinita",
+ context_pinchannel: "Fissa all'elenco dei canali",
+ context_pindm: "Metti il messaggio diretto",
+ context_pinguild: "Aggiungi all'elenco dei server",
+ context_unpinchannel: "Scollega dall'elenco dei canali",
+ context_unpinguild: "Scollega dall'elenco dei server",
+ header_pinneddms: "Messaggi diretti appuntati",
+ modal_colorpicker1: "Colore della categoria"
+ };
+ case "ja": // Japanese
+ return {
+ context_addtonewcategory: "新しいカテゴリに追加",
+ context_disablepredefined: "事前定義されたカテゴリを非アクティブ化",
+ context_inpredefined: "事前定義されたカテゴリに固定",
+ context_pinchannel: "チャネルリストに固定",
+ context_pindm: "ダイレクトメッセージを固定する",
+ context_pinguild: "サーバーリストに固定する",
+ context_unpinchannel: "チャネルリストから切り離す",
+ context_unpinguild: "サーバーリストから切り離す",
+ header_pinneddms: "固定されたダイレクトメッセージ",
+ modal_colorpicker1: "カテゴリカラー"
+ };
+ case "ko": // Korean
+ return {
+ context_addtonewcategory: "새 카테고리에 추가",
+ context_disablepredefined: "사전 정의 된 카테고리 비활성화",
+ context_inpredefined: "사전 정의 된 카테고리에 고정됨",
+ context_pinchannel: "채널 목록에 고정",
+ context_pindm: "개인 메시지 고정",
+ context_pinguild: "서버 목록에 고정",
+ context_unpinchannel: "채널 목록에서 분리",
+ context_unpinguild: "서버 목록에서 분리",
+ header_pinneddms: "고정 된 개인 메시지",
+ modal_colorpicker1: "카테고리 색상"
+ };
+ case "lt": // Lithuanian
+ return {
+ context_addtonewcategory: "Pridėti prie naujos kategorijos",
+ context_disablepredefined: "Išjunkite iš anksto nustatytą kategoriją",
+ context_inpredefined: "Prisegta iš anksto nustatytoje kategorijoje",
+ context_pinchannel: "Prisegti prie kanalų sąrašo",
+ context_pindm: "Prisegti tiesioginį pranešimą",
+ context_pinguild: "Prisegti prie serverio sąrašo",
+ context_unpinchannel: "Atsijungti nuo kanalų sąrašo",
+ context_unpinguild: "Atsijungti nuo serverio sąrašo",
+ header_pinneddms: "Prisegti tiesioginiai pranešimai",
+ modal_colorpicker1: "Kategorijos spalva"
+ };
+ case "nl": // Dutch
+ return {
+ context_addtonewcategory: "Toevoegen aan nieuwe categorie",
+ context_disablepredefined: "Schakel de voorgedefinieerde categorie uit",
+ context_inpredefined: "Vastgezet in een vooraf gedefinieerde categorie",
+ context_pinchannel: "Vastzetten op kanalenlijst",
+ context_pindm: "Direct bericht vastzetten",
+ context_pinguild: "Vastzetten op serverlijst",
+ context_unpinchannel: "Maak los van de zenderlijst",
+ context_unpinguild: "Maak los van de serverlijst",
+ header_pinneddms: "Vastgezette directe berichten",
+ modal_colorpicker1: "Categorie kleur"
+ };
+ case "no": // Norwegian
+ return {
+ context_addtonewcategory: "Legg til i ny kategori",
+ context_disablepredefined: "Deaktiver forhåndsdefinert kategori",
+ context_inpredefined: "Festet i en forhåndsdefinert kategori",
+ context_pinchannel: "Fest til kanallisten",
+ context_pindm: "Fest direkte melding",
+ context_pinguild: "Fest til serverlisten",
+ context_unpinchannel: "Koble fra kanallisten",
+ context_unpinguild: "Koble fra serverlisten",
+ header_pinneddms: "Festede direktemeldinger",
+ modal_colorpicker1: "Kategorifarge"
+ };
+ case "pl": // Polish
+ return {
+ context_addtonewcategory: "Dodaj do nowej kategorii",
+ context_disablepredefined: "Dezaktywuj predefiniowaną kategorię",
+ context_inpredefined: "Przypięty w predefiniowanej kategorii",
+ context_pinchannel: "Przypnij do listy kanałów",
+ context_pindm: "Przypnij bezpośrednią wiadomość",
+ context_pinguild: "Przypnij do listy serwerów",
+ context_unpinchannel: "Odłącz od listy kanałów",
+ context_unpinguild: "Odłącz od listy serwerów",
+ header_pinneddms: "Przypięte czaty",
+ modal_colorpicker1: "Kolor kategorii"
+ };
+ case "pt-BR": // Portuguese (Brazil)
+ return {
+ context_addtonewcategory: "Adicionar à nova categoria",
+ context_disablepredefined: "Desativar categoria predefinida",
+ context_inpredefined: "Fixado em uma categoria predefinida",
+ context_pinchannel: "Fixar na lista de canais",
+ context_pindm: "Fixar mensagem direta",
+ context_pinguild: "Fixar na lista de servidores",
+ context_unpinchannel: "Desanexar da lista de canais",
+ context_unpinguild: "Desanexar da lista de servidores",
+ header_pinneddms: "Mensagens diretas fixadas",
+ modal_colorpicker1: "Cor da categoria"
+ };
+ case "ro": // Romanian
+ return {
+ context_addtonewcategory: "Adăugați la o nouă categorie",
+ context_disablepredefined: "Dezactivați categoria predefinită",
+ context_inpredefined: "Fixat într-o categorie predefinită",
+ context_pinchannel: "Fixați în lista de canale",
+ context_pindm: "Fixați mesajul direct",
+ context_pinguild: "Fixați pe lista serverului",
+ context_unpinchannel: "Desprindeți din lista de canale",
+ context_unpinguild: "Desprindeți din lista serverelor",
+ header_pinneddms: "Mesaje directe fixate",
+ modal_colorpicker1: "Culoarea categoriei"
+ };
+ case "ru": // Russian
+ return {
+ context_addtonewcategory: "Добавить в новую категорию",
+ context_disablepredefined: "Отключить предопределенную категорию",
+ context_inpredefined: "Закреплено в предопределенной категории",
+ context_pinchannel: "Закрепить в списке каналов",
+ context_pindm: "Закрепить прямую переписку",
+ context_pinguild: "Закрепить в списке серверов",
+ context_unpinchannel: "Отключить от списка каналов",
+ context_unpinguild: "Отключить от списка серверов",
+ header_pinneddms: "Закрепленные личные сообщения",
+ modal_colorpicker1: "Цвет категории"
+ };
+ case "sv": // Swedish
+ return {
+ context_addtonewcategory: "Lägg till i ny kategori",
+ context_disablepredefined: "Inaktivera fördefinierad kategori",
+ context_inpredefined: "Fästs i en fördefinierad kategori",
+ context_pinchannel: "Fäst i kanallistan",
+ context_pindm: "Fäst direktmeddelande",
+ context_pinguild: "Fäst i serverlistan",
+ context_unpinchannel: "Ta bort från kanallistan",
+ context_unpinguild: "Ta bort från serverlistan",
+ header_pinneddms: "Fästa direktmeddelanden",
+ modal_colorpicker1: "Kategorifärg"
+ };
+ case "th": // Thai
+ return {
+ context_addtonewcategory: "เพิ่มในหมวดหมู่ใหม่",
+ context_disablepredefined: "ปิดใช้งานหมวดหมู่ที่กำหนดไว้ล่วงหน้า",
+ context_inpredefined: "ตรึงไว้ในหมวดหมู่ที่กำหนดไว้ล่วงหน้า",
+ context_pinchannel: "ตรึงในรายการช่อง",
+ context_pindm: "ตรึงข้อความโดยตรง",
+ context_pinguild: "ปักหมุดรายการเซิร์ฟเวอร์",
+ context_unpinchannel: "แยกออกจากรายการช่อง",
+ context_unpinguild: "แยกออกจากรายการเซิร์ฟเวอร์",
+ header_pinneddms: "ข้อความโดยตรงที่ตรึงไว้",
+ modal_colorpicker1: "สีหมวดหมู่"
+ };
+ case "tr": // Turkish
+ return {
+ context_addtonewcategory: "Yeni kategoriye ekle",
+ context_disablepredefined: "Önceden tanımlanmış kategoriyi devre dışı bırakın",
+ context_inpredefined: "Önceden tanımlanmış bir kategoriye sabitlenmiş",
+ context_pinchannel: "Kanal listesine sabitle",
+ context_pindm: "Doğrudan mesajı sabitle",
+ context_pinguild: "Sunucu listesine sabitle",
+ context_unpinchannel: "Kanal listesinden çıkar",
+ context_unpinguild: "Sunucu listesinden ayır",
+ header_pinneddms: "Sabitlenmiş doğrudan mesajlar",
+ modal_colorpicker1: "Kategori rengi"
+ };
+ case "uk": // Ukrainian
+ return {
+ context_addtonewcategory: "Додати до нової категорії",
+ context_disablepredefined: "Вимкнути заздалегідь визначену категорію",
+ context_inpredefined: "Закріплено в наперед визначеній категорії",
+ context_pinchannel: "Закріпити в списку каналів",
+ context_pindm: "Закріпити пряме повідомлення",
+ context_pinguild: "Закріпити на списку серверів",
+ context_unpinchannel: "Від’єднати від списку каналів",
+ context_unpinguild: "Від'єднати від списку серверів",
+ header_pinneddms: "Закріплені прямі повідомлення",
+ modal_colorpicker1: "Колір категорії"
+ };
+ case "vi": // Vietnamese
+ return {
+ context_addtonewcategory: "Thêm vào danh mục mới",
+ context_disablepredefined: "Hủy kích hoạt danh mục xác định trước",
+ context_inpredefined: "Được ghim trong một danh mục xác định trước",
+ context_pinchannel: "Ghim vào danh sách kênh",
+ context_pindm: "Ghim tin nhắn trực tiếp",
+ context_pinguild: "Ghim vào danh sách máy chủ",
+ context_unpinchannel: "Tách khỏi danh sách kênh",
+ context_unpinguild: "Tách khỏi danh sách máy chủ",
+ header_pinneddms: "Tin nhắn trực tiếp được ghim",
+ modal_colorpicker1: "Màu sắc"
+ };
+ case "zh-CN": // Chinese (China)
+ return {
+ context_addtonewcategory: "添加到新类别",
+ context_disablepredefined: "停用预定义类别",
+ context_inpredefined: "固定在预定义的类别中",
+ context_pinchannel: "固定到频道列表",
+ context_pindm: "固定直接讯息",
+ context_pinguild: "固定到服务器列表",
+ context_unpinchannel: "从频道列表中分离",
+ context_unpinguild: "从服务器列表中分离",
+ header_pinneddms: "固定直接讯息",
+ modal_colorpicker1: "类别颜色"
+ };
+ case "zh-TW": // Chinese (Taiwan)
+ return {
+ context_addtonewcategory: "添加到新類別",
+ context_disablepredefined: "停用預定義類別",
+ context_inpredefined: "固定在預定義的類別中",
+ context_pinchannel: "固定到頻道列表",
+ context_pindm: "固定直接訊息",
+ context_pinguild: "固定到服務器列表",
+ context_unpinchannel: "從頻道列表中分離",
+ context_unpinguild: "從服務器列表中分離",
+ header_pinneddms: "固定直接訊息",
+ modal_colorpicker1: "類別顏色"
+ };
+ default: // English
+ return {
+ context_addtonewcategory: "Add to new Category",
+ context_disablepredefined: "Deactivate predefined Category",
+ context_inpredefined: "Pinned in a predefined Category",
+ context_pinchannel: "Pin to Channel List",
+ context_pindm: "Pin DM",
+ context_pinguild: "Pin to Server List",
+ context_unpinchannel: "Detach from Channel List",
+ context_unpinguild: "Detach from Server List",
+ header_pinneddms: "Pinned Direct Messages",
+ modal_colorpicker1: "Category Color"
+ };
+ }
+ }
+ };
+ })(window.BDFDB_Global.PluginUtils.buildPlugin(config));
+})();
diff --git a/.config/BetterDiscord/plugins/PluginRepo.config.json b/.config/BetterDiscord/plugins/PluginRepo.config.json
new file mode 100755
index 0000000..b64c3e6
--- /dev/null
+++ b/.config/BetterDiscord/plugins/PluginRepo.config.json
@@ -0,0 +1,16 @@
+{
+ "all": {
+ "cached": "2 7 8 9 11 14 15 17 28 29 30 31 59 60 61 62 63 64 65 66 67 68 69 70 71 73 74 75 76 77 78 80 81 82 83 84 85 86 87 88 89 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 116 120 122 126 127 131 132 133 134 137 138 139 146 157 158 159 160 162 164 171 173 176 179 181 182 183 184 185 186 188 189 190 192 193 195 196 197 200 201 220 228 234 236 237 238 240 245 247 253 262 272 274 278 284 287 291 292 293 295 301 306 312 314 317 318 323 331 336 337 338 340 344 349 350 351 352 353 354 356 361 364 366 368 370 377 379 381 382 383 390 395 398 401 404 407 420 421 429 438 441 442 476 479 489 509 518 519 520 521 523 525 535 538 539 547 554 557 566 577 579 589 592 593 598 599 606 608 611 614 620 621 627 638 644 645 670 671 686 692 693 694",
+ "filters": {
+ "updated": true,
+ "outdated": true,
+ "downloadable": true
+ },
+ "general": {
+ "notifyOutdated": true,
+ "notifyNewEntries": false,
+ "startDownloaded": false,
+ "startUpdated": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/.config/BetterDiscord/plugins/PluginRepo.plugin.js b/.config/BetterDiscord/plugins/PluginRepo.plugin.js
new file mode 100755
index 0000000..b8ac056
--- /dev/null
+++ b/.config/BetterDiscord/plugins/PluginRepo.plugin.js
@@ -0,0 +1,1128 @@
+/**
+ * @name PluginRepo
+ * @author DevilBro
+ * @authorId 278543574059057154
+ * @version 2.3.5
+ * @description Allows you to download all Plugins from BD's Website within Discord
+ * @invite Jx3TjNS
+ * @donate https://www.paypal.me/MircoWittrien
+ * @patreon https://www.patreon.com/MircoWittrien
+ * @website https://mwittrien.github.io/
+ * @source https://github.com/mwittrien/BetterDiscordAddons/tree/master/Plugins/PluginRepo/
+ * @updateUrl https://mwittrien.github.io/BetterDiscordAddons/Plugins/PluginRepo/PluginRepo.plugin.js
+ */
+
+module.exports = (_ => {
+ const config = {
+ "info": {
+ "name": "PluginRepo",
+ "author": "DevilBro",
+ "version": "2.3.5",
+ "description": "Allows you to download all Plugins from BD's Website within Discord"
+ }
+ };
+
+ return !window.BDFDB_Global || (!window.BDFDB_Global.loaded && !window.BDFDB_Global.started) ? class {
+ getName () {return config.info.name;}
+ getAuthor () {return config.info.author;}
+ getVersion () {return config.info.version;}
+ getDescription () {return `The Library Plugin needed for ${config.info.name} is missing. Open the Plugin Settings to download it. \n\n${config.info.description}`;}
+
+ downloadLibrary () {
+ require("request").get("https://mwittrien.github.io/BetterDiscordAddons/Library/0BDFDB.plugin.js", (e, r, b) => {
+ if (!e && b && r.statusCode == 200) require("fs").writeFile(require("path").join(BdApi.Plugins.folder, "0BDFDB.plugin.js"), b, _ => BdApi.showToast("Finished downloading BDFDB Library", {type: "success"}));
+ else BdApi.alert("Error", "Could not download BDFDB Library Plugin. Try again later or download it manually from GitHub: https://mwittrien.github.io/downloader/?library");
+ });
+ }
+
+ load () {
+ if (!window.BDFDB_Global || !Array.isArray(window.BDFDB_Global.pluginQueue)) window.BDFDB_Global = Object.assign({}, window.BDFDB_Global, {pluginQueue: []});
+ if (!window.BDFDB_Global.downloadModal) {
+ window.BDFDB_Global.downloadModal = true;
+ BdApi.showConfirmationModal("Library Missing", `The Library Plugin needed for ${config.info.name} is missing. Please click "Download Now" to install it.`, {
+ confirmText: "Download Now",
+ cancelText: "Cancel",
+ onCancel: _ => {delete window.BDFDB_Global.downloadModal;},
+ onConfirm: _ => {
+ delete window.BDFDB_Global.downloadModal;
+ this.downloadLibrary();
+ }
+ });
+ }
+ if (!window.BDFDB_Global.pluginQueue.includes(config.info.name)) window.BDFDB_Global.pluginQueue.push(config.info.name);
+ }
+ start () {this.load();}
+ stop () {}
+ getSettingsPanel () {
+ let template = document.createElement("template");
+ template.innerHTML = `The Library Plugin needed for ${config.info.name} is missing.\nPlease click
Download Now to install it.
`;
+ template.content.firstElementChild.querySelector("a").addEventListener("click", this.downloadLibrary);
+ return template.content.firstElementChild;
+ }
+ } : (([Plugin, BDFDB]) => {
+ var _this;
+
+ var list, header;
+
+ var loading, cachedPlugins, grabbedPlugins, updateInterval;
+ var searchString, searchTimeout, forcedSort, forcedOrder, showOnlyOutdated;
+
+ var favorites = [];
+
+ const pluginStates = {
+ INSTALLED: 0,
+ OUTDATED: 1,
+ DOWNLOADABLE: 2
+ };
+ const buttonData = {
+ INSTALLED: {
+ backgroundColor: "var(--bdfdb-green)",
+ icon: "CHECKMARK",
+ text: "installed"
+ },
+ OUTDATED: {
+ backgroundColor: "var(--bdfdb-red)",
+ icon: "CLOSE",
+ text: "outdated"
+ },
+ DOWNLOADABLE: {
+ backgroundColor: "var(--bdfdb-blurple)",
+ icon: "DOWNLOAD",
+ text: "download"
+ }
+ };
+ const reverseSorts = [
+ "RELEASEDATE", "DOWNLOADS", "LIKES", "FAV"
+ ];
+ const sortKeys = {
+ NAME: "Name",
+ AUTHORNAME: "Author",
+ VERSION: "Version",
+ DESCRIPTION: "Description",
+ RELEASEDATE: "Release Date",
+ STATE: "Update State",
+ DOWNLOADS: "Downloads",
+ LIKES: "Likes",
+ FAV: "Favorites"
+ };
+ const orderKeys = {
+ ASC: "ascending",
+ DESC: "descending"
+ };
+
+ const pluginRepoIcon = ``;
+
+ const RepoListComponent = class PluginList extends BdApi.React.Component {
+ componentDidMount() {
+ list = this;
+ BDFDB.TimeUtils.timeout(_ => {
+ forcedSort = null;
+ forcedOrder = null;
+ showOnlyOutdated = false;
+ }, 5000);
+ }
+ componentWillUnmount() {
+ list = null;
+ }
+ filterPlugins() {
+ let plugins = grabbedPlugins.map(plugin => {
+ const installedPlugin = _this.getInstalledPlugin(plugin);
+ const state = installedPlugin ? (plugin.version && _this.compareVersions(plugin.version, _this.getString(installedPlugin.version)) ? pluginStates.OUTDATED : pluginStates.INSTALLED) : pluginStates.DOWNLOADABLE;
+ return Object.assign(plugin, {
+ search: [plugin.name, plugin.version, plugin.authorname, plugin.description, plugin.tags].flat(10).filter(n => typeof n == "string").join(" ").toUpperCase(),
+ description: plugin.description || "No Description found",
+ fav: favorites.includes(plugin.id) && 1,
+ new: state == pluginStates.DOWNLOADABLE && !cachedPlugins.includes(plugin.id) && 1,
+ state: state
+ });
+ });
+ if (!this.props.updated) plugins = plugins.filter(plugin => plugin.state != pluginStates.INSTALLED);
+ if (!this.props.outdated) plugins = plugins.filter(plugin => plugin.state != pluginStates.OUTDATED);
+ if (!this.props.downloadable) plugins = plugins.filter(plugin => plugin.state != pluginStates.DOWNLOADABLE);
+ if (searchString) {
+ let usedSearchString = searchString.toUpperCase();
+ let spacelessUsedSearchString = usedSearchString.replace(/\s/g, "");
+ plugins = plugins.filter(plugin => plugin.search.indexOf(usedSearchString) > -1 || plugin.search.indexOf(spacelessUsedSearchString) > -1);
+ }
+
+ BDFDB.ArrayUtils.keySort(plugins, this.props.sortKey.toLowerCase());
+ if (this.props.orderKey == "DESC") plugins.reverse();
+ if (reverseSorts.includes(this.props.sortKey)) plugins.reverse();
+ return plugins;
+ }
+ render() {
+ if (!this.props.tab) this.props.tab = "Plugins";
+
+ this.props.entries = (!loading.is && grabbedPlugins.length ? this.filterPlugins() : []).map(plugin => BDFDB.ReactUtils.createElement(RepoCardComponent, {
+ data: plugin
+ })).filter(n => n);
+
+ BDFDB.TimeUtils.timeout(_ => {
+ if (!loading.is && header && this.props.entries.length != header.props.amount) {
+ header.props.amount = this.props.entries.length;
+ BDFDB.ReactUtils.forceUpdate(header);
+ }
+ });
+
+ return [
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.ModalComponents.ModalTabContent, {
+ tab: "Plugins",
+ open: this.props.tab == "Plugins",
+ render: false,
+ children: loading.is ? BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex, {
+ direction: BDFDB.LibraryComponents.Flex.Direction.VERTICAL,
+ justify: BDFDB.LibraryComponents.Flex.Justify.CENTER,
+ style: {marginTop: "50%"},
+ children: [
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Spinner, {
+ type: BDFDB.LibraryComponents.Spinner.Type.WANDERING_CUBES
+ }),
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TextElement, {
+ className: BDFDB.disCN.margintop20,
+ style: {textAlign: "center"},
+ children: `${BDFDB.LanguageUtils.LibraryStringsFormat("loading", "Plugin Repo")} - ${BDFDB.LanguageUtils.LibraryStrings.please_wait}`
+ })
+ ]
+ }) : BDFDB.ReactUtils.createElement("div", {
+ className: BDFDB.disCN.discoverycards,
+ children: this.props.entries
+ })
+ }),
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.ModalComponents.ModalTabContent, {
+ tab: BDFDB.LanguageUtils.LanguageStrings.SETTINGS,
+ open: this.props.tab == BDFDB.LanguageUtils.LanguageStrings.SETTINGS,
+ render: false,
+ children: [
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsPanelList, {
+ title: "Show following Plugins",
+ children: Object.keys(_this.defaults.filters).map(key => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsSaveItem, {
+ type: "Switch",
+ plugin: _this,
+ keys: ["filters", key],
+ label: _this.defaults.filters[key].description,
+ value: _this.settings.filters[key],
+ onChange: value => {
+ this.props[key] = _this.settings.filters[key] = value;
+ BDFDB.ReactUtils.forceUpdate(this);
+ }
+ }))
+ }),
+ Object.keys(_this.defaults.general).map(key => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsSaveItem, {
+ type: "Switch",
+ plugin: _this,
+ keys: ["general", key],
+ label: _this.defaults.general[key].description,
+ value: _this.settings.general[key],
+ onChange: value => {
+ _this.settings.general[key] = value;
+ BDFDB.ReactUtils.forceUpdate(this);
+ }
+ }))
+ ].flat(10).filter(n => n)
+ })
+ ];
+ }
+ };
+
+ const RepoCardComponent = class PluginCard extends BdApi.React.Component {
+ render() {
+ if (this.props.data.thumbnailUrl && !this.props.data.thumbnailChecked) {
+ if (!window.Buffer) this.props.data.thumbnailChecked = true;
+ else BDFDB.LibraryRequires.request(this.props.data.thumbnailUrl, {encoding: null}, (error, response, body) => {
+ if (response && response.headers["content-type"] && response.headers["content-type"] == "image/gif") {
+ const throwAwayImg = new Image(), instance = this;
+ throwAwayImg.onload = function() {
+ const canvas = document.createElement("canvas");
+ canvas.getContext("2d").drawImage(throwAwayImg, 0, 0, canvas.width = this.width, canvas.height = this.height);
+ try {
+ const oldUrl = instance.props.data.thumbnailUrl;
+ instance.props.data.thumbnailUrl = canvas.toDataURL("image/png");
+ instance.props.data.thumbnailGifUrl = oldUrl;
+ instance.props.data.thumbnailChecked = true;
+ BDFDB.ReactUtils.forceUpdate(instance);
+ }
+ catch (err) {
+ instance.props.data.thumbnailChecked = true;
+ BDFDB.ReactUtils.forceUpdate(instance);
+ }
+ };
+ throwAwayImg.onerror = function() {
+ instance.props.data.thumbnailChecked = true;
+ BDFDB.ReactUtils.forceUpdate(instance);
+ };
+ throwAwayImg.src = "data:" + response.headers["content-type"] + ";base64," + (new Buffer(body).toString("base64"));
+ }
+ else {
+ this.props.data.thumbnailChecked = true;
+ BDFDB.ReactUtils.forceUpdate(this);
+ }
+ });
+ }
+ return BDFDB.ReactUtils.createElement("div", {
+ className: BDFDB.disCN.discoverycard,
+ children: [
+ BDFDB.ReactUtils.createElement("div", {
+ className: BDFDB.disCN.discoverycardheader,
+ children: [
+ BDFDB.ReactUtils.createElement("div", {
+ className: BDFDB.disCN.discoverycardcoverwrapper,
+ children: [
+ this.props.data.thumbnailUrl && this.props.data.thumbnailChecked && BDFDB.ReactUtils.createElement("img", {
+ className: BDFDB.disCN.discoverycardcover,
+ src: this.props.data.thumbnailUrl,
+ loading: "lazy",
+ onMouseEnter: this.props.data.thumbnailGifUrl && (e => e.target.src = this.props.data.thumbnailGifUrl),
+ onMouseLeave: this.props.data.thumbnailGifUrl && (e => e.target.src = this.props.data.thumbnailUrl),
+ onClick: _ => {
+ const url = this.props.data.thumbnailGifUrl || this.props.data.thumbnailUrl;
+ const img = document.createElement("img");
+ img.addEventListener("load", function() {
+ BDFDB.LibraryModules.ModalUtils.openModal(modalData => {
+ return BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.ModalComponents.ModalRoot, Object.assign({
+ className: BDFDB.disCN.imagemodal
+ }, modalData, {
+ size: BDFDB.LibraryComponents.ModalComponents.ModalSize.DYNAMIC,
+ "aria-label": BDFDB.LanguageUtils.LanguageStrings.IMAGE,
+ children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.ImageModal, {
+ animated: false,
+ src: url,
+ original: url,
+ width: this.width,
+ height: this.height,
+ className: BDFDB.disCN.imagemodalimage,
+ shouldAnimate: true,
+ renderLinkComponent: props => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Anchor, props)
+ })
+ }), true);
+ });
+ });
+ img.src = url;
+ }
+ }),
+ this.props.data.new && BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Badges.TextBadge, {
+ className: BDFDB.disCN.discoverycardcoverbadge,
+ style: {
+ borderRadius: 3,
+ textTransform: "uppercase",
+ background: BDFDB.DiscordConstants.Colors.STATUS_YELLOW
+ },
+ text: BDFDB.LanguageUtils.LanguageStrings.NEW
+ })
+ ]
+ }),
+ BDFDB.ReactUtils.createElement(class extends BDFDB.ReactUtils.Component {
+ render() {
+ return BDFDB.ReactUtils.createElement("div", {
+ className: BDFDB.disCN.discoverycardiconwrapper,
+ children: this.props.data.author && this.props.data.author.discord_avatar_hash && this.props.data.author.discord_snowflake && !this.props.data.author.discord_avatar_failed ? BDFDB.ReactUtils.createElement("img", {
+ className: BDFDB.DOMUtils.formatClassName(BDFDB.disCN.discoverycardicon, !this.props.data.author.discord_avatar_loaded && BDFDB.disCN.discoverycardiconloading),
+ src: `https://cdn.discordapp.com/avatars/${this.props.data.author.discord_snowflake}/${this.props.data.author.discord_avatar_hash}.webp?size=128`,
+ loading: "lazy",
+ onLoad: _ => {
+ this.props.data.author.discord_avatar_loaded = true;
+ BDFDB.ReactUtils.forceUpdate(this);
+ },
+ onError: _ => {
+ this.props.data.author.discord_avatar_failed = true;
+ BDFDB.ReactUtils.forceUpdate(this);
+ }
+ }) : BDFDB.ReactUtils.createElement("div", {
+ className: BDFDB.disCN.discoverycardicon,
+ children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, {
+ nativeClass: true,
+ iconSVG: ``
+ })
+ })
+ });
+ }
+ }, this.props)
+ ]
+ }),
+ BDFDB.ReactUtils.createElement("div", {
+ className: BDFDB.disCN.discoverycardinfo,
+ children: [
+ BDFDB.ReactUtils.createElement("div", {
+ className: BDFDB.disCN.discoverycardtitle,
+ children: [
+ BDFDB.ReactUtils.createElement("div", {
+ className: BDFDB.disCN.discoverycardname,
+ children: this.props.data.name
+ }),
+ this.props.data.latestSourceUrl &&
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TooltipContainer, {
+ text: BDFDB.LanguageUtils.LanguageStrings.SCREENSHARE_SOURCE,
+ children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Clickable, {
+ className: BDFDB.disCN.discoverycardtitlebutton,
+ children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, {
+ nativeClass: true,
+ width: 16,
+ height: 16,
+ name: BDFDB.LibraryComponents.SvgIcon.Names.GITHUB
+ })
+ }),
+ onClick: _ => BDFDB.DiscordUtils.openLink(this.props.data.latestSourceUrl)
+ }),
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FavButton, {
+ className: BDFDB.disCN.discoverycardtitlebutton,
+ isFavorite: this.props.data.fav,
+ onClick: value => {
+ this.props.data.fav = value && 1;
+ if (value) favorites.push(this.props.data.id);
+ else BDFDB.ArrayUtils.remove(favorites, this.props.data.id, true);
+ BDFDB.DataUtils.save(BDFDB.ArrayUtils.numSort(favorites).join(" "), _this, "favorites");
+ }
+ })
+ ]
+ }),
+ BDFDB.ReactUtils.createElement("div", {
+ className: BDFDB.disCN.discoverycardauthor,
+ children: `by ${this.props.data.authorname}`
+ }),
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Scrollers.Thin, {
+ className: BDFDB.disCN.discoverycarddescription,
+ children: this.props.data.description
+ }),
+ BDFDB.ReactUtils.createElement("div", {
+ className: BDFDB.disCN.discoverycardfooter,
+ children: [
+ BDFDB.ArrayUtils.is(this.props.data.tags) && this.props.data.tags.length && BDFDB.ReactUtils.createElement("div", {
+ className: BDFDB.disCN.discoverycardtags,
+ children: this.props.data.tags.map(tag => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Badges.TextBadge, {
+ className: BDFDB.disCN.discoverycardtag,
+ style: {background: "var(--background-accent)"},
+ text: tag
+ }))
+ }),
+ BDFDB.ReactUtils.createElement("div", {
+ className: BDFDB.disCN.discoverycardcontrols,
+ children: [
+ BDFDB.ReactUtils.createElement("div", {
+ className: BDFDB.disCN.discoverycardstats,
+ children: [
+ BDFDB.ReactUtils.createElement("div", {
+ className: BDFDB.disCN.discoverycardstat,
+ children: [
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, {
+ className: BDFDB.disCN.discoverycardstaticon,
+ width: 16,
+ height: 16,
+ name: BDFDB.LibraryComponents.SvgIcon.Names.DOWNLOAD
+ }),
+ this.props.data.downloads
+ ]
+ }),
+ BDFDB.ReactUtils.createElement("div", {
+ className: BDFDB.disCN.discoverycardstat,
+ children: [
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, {
+ className: BDFDB.disCN.discoverycardstaticon,
+ width: 16,
+ height: 16,
+ name: BDFDB.LibraryComponents.SvgIcon.Names.HEART
+ }),
+ this.props.data.likes
+ ]
+ })
+ ]
+ }),
+ BDFDB.ReactUtils.createElement(RepoCardDownloadButtonComponent, {
+ ...buttonData[(Object.entries(pluginStates).find(n => n[1] == this.props.data.state) || [])[0]],
+ installed: this.props.data.state == pluginStates.INSTALLED,
+ outdated: this.props.data.state == pluginStates.OUTDATED,
+ onDownload: _ => {
+ if (this.props.downloading) return;
+ this.props.downloading = true;
+ let loadingToast = BDFDB.NotificationUtils.toast(`${BDFDB.LanguageUtils.LibraryStringsFormat("loading", this.props.data.name)} - ${BDFDB.LanguageUtils.LibraryStrings.please_wait}`, {timeout: 0, ellipsis: true});
+ let autoloadKey = this.props.data.state == pluginStates.OUTDATED ? "startUpdated" : "startDownloaded";
+ BDFDB.LibraryRequires.request(this.props.data.rawSourceUrl, (error, response, body) => {
+ if (error) {
+ delete this.props.downloading;
+ loadingToast.close();
+ BDFDB.NotificationUtils.toast(BDFDB.LanguageUtils.LibraryStringsFormat("download_fail", `Plugin "${this.props.data.name}"`), {type: "danger"});
+ }
+ else {
+ BDFDB.LibraryRequires.fs.writeFile(BDFDB.LibraryRequires.path.join(BDFDB.BDUtils.getPluginsFolder(), this.props.data.rawSourceUrl.split("/").pop()), body, error2 => {
+ delete this.props.downloading;
+ loadingToast.close();
+ if (error2) BDFDB.NotificationUtils.toast(BDFDB.LanguageUtils.LibraryStringsFormat("save_fail", `Plugin "${this.props.data.name}"`), {type: "danger"});
+ else {
+ BDFDB.NotificationUtils.toast(BDFDB.LanguageUtils.LibraryStringsFormat("save_success", `Plugin "${this.props.data.name}"`), {type: "success"});
+ if (_this.settings.general[autoloadKey]) BDFDB.TimeUtils.timeout(_ => {
+ if (this.props.data.state == pluginStates.INSTALLED && BDFDB.BDUtils.isPluginEnabled(this.props.data.name) == false) {
+ BDFDB.BDUtils.enablePlugin(this.props.data.name, false);
+ BDFDB.LogUtils.log(BDFDB.LanguageUtils.LibraryStringsFormat("toast_plugin_started", this.props.data.name), _this);
+ }
+ }, 3000);
+ this.props.data.state = pluginStates.INSTALLED;
+ BDFDB.ReactUtils.forceUpdate(this);
+ }
+ });
+ }
+ });
+ },
+ onDelete: _ => {
+ if (this.props.deleting) return;
+ this.props.deleting = true;
+ BDFDB.LibraryRequires.fs.unlink(BDFDB.LibraryRequires.path.join(BDFDB.BDUtils.getPluginsFolder(), this.props.data.rawSourceUrl.split("/").pop()), error => {
+ delete this.props.deleting;
+ if (error) BDFDB.NotificationUtils.toast(BDFDB.LanguageUtils.LibraryStringsFormat("delete_fail", `Plugin "${this.props.data.name}"`), {type: "danger"});
+ else {
+ BDFDB.NotificationUtils.toast(BDFDB.LanguageUtils.LibraryStringsFormat("delete_success", `Plugin "${this.props.data.name}"`));
+ this.props.data.state = pluginStates.DOWNLOADABLE;
+ BDFDB.ReactUtils.forceUpdate(this);
+ }
+ });
+ }
+ })
+ ]
+ })
+ ]
+ })
+ ]
+ })
+ ]
+ });
+ }
+ };
+
+ const RepoCardDownloadButtonComponent = class PluginCardDownloadButton extends BdApi.React.Component {
+ render() {
+ const backgroundColor = this.props.doDelete ? buttonData.OUTDATED.backgroundColor : this.props.doUpdate ? buttonData.INSTALLED.backgroundColor : this.props.backgroundColor;
+ return BDFDB.ReactUtils.createElement("button", {
+ className: BDFDB.disCN.discoverycardbutton,
+ style: {backgroundColor: BDFDB.DiscordConstants.Colors[backgroundColor] || backgroundColor},
+ children: [
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, {
+ className: BDFDB.disCN.discoverycardstaticon,
+ width: 16,
+ height: 16,
+ name: this.props.doDelete ? BDFDB.LibraryComponents.SvgIcon.Names.TRASH : this.props.doUpdate ? BDFDB.LibraryComponents.SvgIcon.Names.DOWNLOAD : BDFDB.LibraryComponents.SvgIcon.Names[this.props.icon]
+ }),
+ this.props.doDelete ? BDFDB.LanguageUtils.LanguageStrings.APPLICATION_CONTEXT_MENU_UNINSTALL : this.props.doUpdate ? BDFDB.LanguageUtils.LanguageStrings.GAME_ACTION_BUTTON_UPDATE : (BDFDB.LanguageUtils.LibraryStringsCheck[this.props.text] ? BDFDB.LanguageUtils.LibraryStrings[this.props.text] : BDFDB.LanguageUtils.LanguageStrings[this.props.text])
+ ],
+ onClick: _ => {
+ if (this.props.doDelete) typeof this.props.onDelete == "function" && this.props.onDelete();
+ else if (!this.props.installed) typeof this.props.onDownload == "function" && this.props.onDownload();
+ },
+ onMouseEnter: this.props.installed ? (_ => {
+ this.props.doDelete = true;
+ BDFDB.ReactUtils.forceUpdate(this);
+ }) : this.props.outdated ? (_ => {
+ this.props.doUpdate = true;
+ BDFDB.ReactUtils.forceUpdate(this);
+ }) : (_ => {}),
+ onMouseLeave: this.props.installed ? (_ => {
+ this.props.doDelete = false;
+ BDFDB.ReactUtils.forceUpdate(this);
+ }) : this.props.outdated ? (_ => {
+ this.props.doUpdate = false;
+ BDFDB.ReactUtils.forceUpdate(this);
+ }) : (_ => {})
+ });
+ }
+ };
+
+ const RepoListHeaderComponent = class PluginListHeader extends BdApi.React.Component {
+ componentDidMount() {
+ header = this;
+ }
+ render() {
+ if (!this.props.tab) this.props.tab = "Plugins";
+ return BDFDB.ReactUtils.createElement("div", {
+ className: BDFDB.disCN._repolistheader,
+ children: [
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex, {
+ className: BDFDB.disCN.marginbottom4,
+ align: BDFDB.LibraryComponents.Flex.Align.CENTER,
+ children: [
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, {
+ grow: 1,
+ shrink: 0,
+ children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormTitle, {
+ tag: BDFDB.LibraryComponents.FormComponents.FormTitle.Tags.H2,
+ className: BDFDB.disCN.marginreset,
+ children: `Plugin Repo — ${loading.is ? 0 : this.props.amount || 0}/${loading.is ? 0 : grabbedPlugins.length}`
+ })
+ }),
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, {
+ children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SearchBar, {
+ autoFocus: true,
+ query: searchString,
+ onChange: (value, instance) => {
+ if (loading.is) return;
+ BDFDB.TimeUtils.clear(searchTimeout);
+ searchTimeout = BDFDB.TimeUtils.timeout(_ => {
+ searchString = value.replace(/[<|>]/g, "");
+ BDFDB.ReactUtils.forceUpdate(this, list);
+ }, 1000);
+ },
+ onClear: instance => {
+ if (loading.is) return;
+ searchString = "";
+ BDFDB.ReactUtils.forceUpdate(this, list);
+ }
+ })
+ }),
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Button, {
+ size: BDFDB.LibraryComponents.Button.Sizes.TINY,
+ children: BDFDB.LanguageUtils.LibraryStrings.check_for_updates,
+ onClick: _ => {
+ if (loading.is) return;
+ loading = {is: false, timeout: null, amount: 0};
+ _this.loadPlugins();
+ }
+ })
+ ]
+ }),
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex, {
+ className: BDFDB.disCNS.tabbarcontainer + BDFDB.disCN.tabbarcontainerbottom,
+ align: BDFDB.LibraryComponents.Flex.Align.CENTER,
+ children: [
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, {
+ children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TabBar, {
+ className: BDFDB.disCN.tabbar,
+ itemClassName: BDFDB.disCN.tabbaritem,
+ type: BDFDB.LibraryComponents.TabBar.Types.TOP,
+ selectedItem: this.props.tab,
+ items: [{value: "Plugins"}, {value: BDFDB.LanguageUtils.LanguageStrings.SETTINGS}],
+ onItemSelect: value => {
+ this.props.tab = list.props.tab = value;
+ BDFDB.ReactUtils.forceUpdate(list);
+ }
+ })
+ }),
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, {
+ children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.QuickSelect, {
+ label: BDFDB.LanguageUtils.LibraryStrings.sort_by + ":",
+ value: {
+ label: sortKeys[this.props.sortKey],
+ value: this.props.sortKey
+ },
+ options: Object.keys(sortKeys).map(key => ({
+ label: sortKeys[key],
+ value: key
+ })),
+ onChange: key => {
+ this.props.sortKey = list.props.sortKey = key;
+ BDFDB.ReactUtils.forceUpdate(this, list);
+ }
+ })
+ }),
+ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, {
+ children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.QuickSelect, {
+ label: BDFDB.LanguageUtils.LibraryStrings.order + ":",
+ value: {
+ label: BDFDB.LanguageUtils.LibraryStrings[orderKeys[this.props.orderKey]],
+ value: this.props.orderKey
+ },
+ options: Object.keys(orderKeys).map(key => ({
+ label: BDFDB.LanguageUtils.LibraryStrings[orderKeys[key]],
+ value: key
+ })),
+ onChange: key => {
+ this.props.orderKey = list.props.orderKey = key;
+ BDFDB.ReactUtils.forceUpdate(this, list);
+ }
+ })
+ })
+ ]
+ })
+ ]
+ });
+ }
+ };
+
+ return class PluginRepo extends Plugin {
+ onLoad () {
+ _this = this;
+
+ loading = {is: false, timeout: null, amount: 0};
+
+ cachedPlugins = [];
+ grabbedPlugins = [];
+ searchString = "";
+
+ this.defaults = {
+ general: {
+ notifyOutdated: {value: true, autoload: false, description: "Get a Notification when one of your Plugins is outdated"},
+ notifyNewEntries: {value: true, autoload: false, description: "Get a Notification when there are new Entries in the Repo"},
+ startDownloaded: {value: false, autoload: true, description: "Start new Plugins after Download"},
+ startUpdated: {value: false, autoload: true, description: "Start updated Plugins after Download"}
+ },
+ filters: {
+ updated: {value: true, description: "Updated"},
+ outdated: {value: true, description: "Outdated"},
+ downloadable: {value: true, description: "Downloadable"},
+ }
+ };
+
+ this.patchedModules = {
+ before: {
+ SettingsView: ["render", "componentWillUnmount"]
+ },
+ after: {
+ StandardSidebarView: "render"
+ }
+ };
+
+ }
+
+ onStart () {
+ this.forceUpdateAll();
+
+ this.loadPlugins();
+
+ updateInterval = BDFDB.TimeUtils.interval(_ => this.checkForNewPlugins(), 1000*60*30);
+ }
+
+ onStop () {
+ BDFDB.TimeUtils.clear(updateInterval);
+ BDFDB.TimeUtils.clear(loading.timeout);
+
+ this.forceUpdateAll();
+
+ BDFDB.DOMUtils.remove(BDFDB.dotCN._pluginreponotice, BDFDB.dotCN._pluginrepoloadingicon);
+ }
+
+ onSettingsClosed () {
+ if (this.SettingsUpdated) {
+ delete this.SettingsUpdated;
+ this.forceUpdateAll();
+ }
+ }
+
+ forceUpdateAll () {
+ favorites = BDFDB.DataUtils.load(this, "favorites");
+ favorites = (typeof favorites == "string" ? favorites.split(" ") : []).map(n => parseInt(n)).filter(n => !isNaN(n));
+
+ BDFDB.PatchUtils.forceAllUpdates(this);
+ }
+
+ onUserSettingsCogContextMenu (e) {
+ BDFDB.TimeUtils.timeout(_ => {
+ let [children, index] = BDFDB.ReactUtils.findParent(e.returnvalue, {props: [["label", ["BandagedBD", "BetterDiscord"]]]});
+ if (index > -1 && BDFDB.ArrayUtils.is(children[index].props.children)) children[index].props.children.push(BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, {
+ label: "Plugin Repo",
+ id: BDFDB.ContextMenuUtils.createItemId(this.name, "repo"),
+ action: _ => {
+ BDFDB.LibraryModules.UserSettingsUtils.open("pluginrepo");
+ }
+ }));
+ });
+ }
+
+ processSettingsView (e) {
+ if (e.node) searchString = "";
+ else {
+ if (!BDFDB.PatchUtils.isPatched(this, e.component, "getPredicateSections")) BDFDB.PatchUtils.patch(this, e.component, "getPredicateSections", {after: e2 => {
+ if (BDFDB.ArrayUtils.is(e2.returnValue) && e2.returnValue.findIndex(n => n.section && (n.section.toLowerCase() == "changelog" || n.section == BDFDB.DiscordConstants.UserSettingsSections.CHANGE_LOG || n.section.toLowerCase() == "logout" || n.section == BDFDB.DiscordConstants.UserSettingsSections.LOGOUT))) {
+ e2.returnValue = e2.returnValue.filter(n => n.section != "pluginrepo");
+ let index = e2.returnValue.indexOf(e2.returnValue.find(n => n.section == "themes") || e2.returnValue.find(n => n.section == BDFDB.DiscordConstants.UserSettingsSections.DEVELOPER_OPTIONS) || e2.returnValue.find(n => n.section == BDFDB.DiscordConstants.UserSettingsSections.HYPESQUAD_ONLINE));
+ if (index > -1) {
+ e2.returnValue.splice(index + 1, 0, {
+ section: "pluginrepo",
+ label: "Plugin Repo",
+ element: _ => {
+ let options = Object.assign({}, this.settings.filters);
+ options.updated = options.updated && !showOnlyOutdated;
+ options.outdated = options.outdated || showOnlyOutdated;
+ options.downloadable = options.downloadable && !showOnlyOutdated;
+ options.sortKey = forcedSort || Object.keys(sortKeys)[0];
+ options.orderKey = forcedOrder || Object.keys(orderKeys)[0];
+
+ return BDFDB.ReactUtils.createElement(RepoListComponent, options, true);
+ }
+ });
+ if (!e2.returnValue.find(n => n.section == "plugins")) e2.returnValue.splice(index + 1, 0, {section: "DIVIDER"});
+ }
+ }
+ }});
+ }
+ }
+
+ processStandardSidebarView (e) {
+ if (e.instance.props.section == "pluginrepo") {
+ let content = BDFDB.ReactUtils.findChild(e.returnvalue, {props: [["className", BDFDB.disCN.settingswindowcontentregion]]});
+ if (content) content.props.className = BDFDB.DOMUtils.formatClassName(BDFDB.disCN._repolistwrapper, content.props.className);
+ let [children, index] = BDFDB.ReactUtils.findParent(e.returnvalue, {props: [["className", BDFDB.disCN.settingswindowcontentregionscroller]]});
+ if (index > -1) {
+ let options = {};
+ options.sortKey = forcedSort || Object.keys(sortKeys)[0];
+ options.orderKey = forcedOrder || Object.keys(orderKeys)[0];
+ children[index] = [
+ BDFDB.ReactUtils.createElement(RepoListHeaderComponent, options, true),
+ children[index]
+ ];
+ }
+ }
+ }
+
+ loadPlugins () {
+ BDFDB.DOMUtils.remove(BDFDB.dotCN._pluginrepoloadingicon);
+ cachedPlugins = BDFDB.DataUtils.load(this, "cached");
+ cachedPlugins = (typeof cachedPlugins == "string" ? cachedPlugins.split(" ") : []).map(n => parseInt(n)).filter(n => !isNaN(n));
+
+ let loadingIcon;
+ let newEntries = 0, outdatedEntries = 0, checkIndex = 0, checksRunning = 0, callbackCalled = false;
+
+ const checkPlugin = _ => {
+ if (checksRunning > 20) return;
+ else if (grabbedPlugins.every(p => p.loaded || (!p.latestSourceUrl && !p.latest_source_url)) || !this.started || !loading.is) {
+ if (!callbackCalled) {
+ callbackCalled = true;
+ if (!this.started) return BDFDB.TimeUtils.clear(loading.timeout);
+ BDFDB.TimeUtils.clear(loading.timeout);
+ BDFDB.DOMUtils.remove(loadingIcon, BDFDB.dotCN._pluginrepoloadingicon);
+ loading = {is: false, timeout: null, amount: loading.amount};
+
+ BDFDB.LogUtils.log("Finished fetching Plugins", this);
+ BDFDB.ReactUtils.forceUpdate(list);
+
+ if (this.settings.general.notifyOutdated && outdatedEntries > 0) {
+ let notice = document.querySelector(BDFDB.dotCN._pluginrepooutdatednotice);
+ if (notice) notice.close();
+ BDFDB.NotificationUtils.notice(this.labels.notice_outdated_plugins.replace("{{var0}}", outdatedEntries), {
+ type: "danger",
+ className: BDFDB.disCNS._pluginreponotice + BDFDB.disCN._pluginrepooutdatednotice,
+ customIcon: pluginRepoIcon.replace(/COLOR_[0-9]+/gi, "currentColor"),
+ buttons: [{
+ contents: BDFDB.LanguageUtils.LanguageStrings.OPEN,
+ close: true,
+ onClick: _ => {
+ showOnlyOutdated = true;
+ BDFDB.LibraryModules.UserSettingsUtils.open("pluginrepo");
+ }
+ }]
+ });
+ }
+
+ if (this.settings.general.notifyNewEntries && newEntries > 0) {
+ let notice = document.querySelector(BDFDB.dotCN._pluginreponewentriesnotice);
+ if (notice) notice.close();
+ BDFDB.NotificationUtils.notice(this.labels.notice_new_plugins.replace("{{var0}}", newEntries), {
+ type: "success",
+ className: BDFDB.disCNS._pluginreponotice + BDFDB.disCN._pluginreponewentriesnotice,
+ customIcon: pluginRepoIcon.replace(/COLOR_[0-9]+/gi, "currentColor"),
+ buttons: [{
+ contents: BDFDB.LanguageUtils.LanguageStrings.OPEN,
+ close: true,
+ onClick: _ => {
+ forcedSort = "RELEASEDATE";
+ forcedOrder = "ASC";
+ BDFDB.LibraryModules.UserSettingsUtils.open("pluginrepo");
+ }
+ }]
+ });
+ }
+ }
+ return;
+ }
+ else if (checkIndex > grabbedPlugins.length) return;
+
+ const plugin = grabbedPlugins[checkIndex++];
+ if (!plugin || (!plugin.latestSourceUrl && !plugin.latest_source_url)) checkPlugin();
+ else {
+ checksRunning++;
+ plugin.releasedate = new Date(plugin.releaseDate || plugin.release_date || 0).getTime();
+ plugin.latestSourceUrl = plugin.latestSourceUrl || plugin.latest_source_url;
+ plugin.rawSourceUrl = plugin.latestSourceUrl.replace("https://github.com/", "https://raw.githubusercontent.com/").replace(/\/blob\/(.{32,})/i, "/$1");
+ plugin.thumbnailUrl = plugin.thumbnailUrl || plugin.thumbnail_url;
+ plugin.thumbnailUrl = plugin.thumbnailUrl ? (plugin.thumbnailUrl.startsWith("https://") ? plugin.thumbnailUrl : `https://betterdiscord.app${plugin.thumbnailUrl}`) : "";
+ delete plugin.release_date;
+ delete plugin.latest_source_url;
+ delete plugin.thumbnail_url;
+ BDFDB.LibraryRequires.request(plugin.rawSourceUrl, (error, response, body) => {
+ if (body && body.indexOf("404: Not Found") != 0 && response.statusCode == 200) {
+ plugin.name = BDFDB.LibraryModules.StringUtils.upperCaseFirstChar((/@name\s+([^\t^\r^\n]+)|\/\/\**META.*["']name["']\s*:\s*["'](.+?)["']/i.exec(body) || []).filter(n => n)[1] || plugin.name || "");
+ plugin.authorname = (/@author\s+(.+)|\/\/\**META.*["']author["']\s*:\s*["'](.+?)["']/i.exec(body) || []).filter(n => n)[1] || plugin.author.display_name || plugin.author;
+ const version = (/@version\s+(.+)|\/\/\**META.*["']version["']\s*:\s*["'](.+?)["']/i.exec(body) || []).filter(n => n)[1];
+ if (version) {
+ plugin.version = version;
+ const installedPlugin = this.getInstalledPlugin(plugin);
+ if (installedPlugin && this.compareVersions(version, this.getString(installedPlugin.version))) outdatedEntries++;
+ }
+ }
+ if (!cachedPlugins.includes(plugin.id)) newEntries++;
+
+ plugin.loaded = true;
+
+ let loadingTooltip = document.querySelector(BDFDB.dotCN._pluginrepoloadingtooltip);
+ if (loadingTooltip) loadingTooltip.update(this.getLoadingTooltipText());
+
+ checksRunning--;
+ checkPlugin();
+ });
+ }
+ };
+
+ BDFDB.LibraryRequires.request("https://api.betterdiscord.app/v1/store/plugins", (error, response, body) => {
+ if (!error && body && response.statusCode == 200) try {
+ grabbedPlugins = BDFDB.ArrayUtils.keySort(JSON.parse(body).filter(n => n), "name");
+ BDFDB.DataUtils.save(BDFDB.ArrayUtils.numSort(grabbedPlugins.map(n => n.id)).join(" "), this, "cached");
+
+ loading = {is: true, timeout: BDFDB.TimeUtils.timeout(_ => {
+ BDFDB.TimeUtils.clear(loading.timeout);
+ if (this.started) {
+ if (loading.is && loading.amount < 4) BDFDB.TimeUtils.timeout(_ => this.loadPlugins(), 10000);
+ loading = {is: false, timeout: null, amount: loading.amount};
+ }
+ }, 1200000), amount: loading.amount + 1};
+
+ loadingIcon = BDFDB.DOMUtils.create(pluginRepoIcon.replace(/COLOR_1/gi, "var(--bdfdb-blurple)").replace(/COLOR_2/gi, "#72767d"));
+ BDFDB.DOMUtils.addClass(loadingIcon, BDFDB.disCN._pluginrepoloadingicon);
+ loadingIcon.addEventListener("mouseenter", _ => {
+ BDFDB.TooltipUtils.create(loadingIcon, this.getLoadingTooltipText(), {
+ type: "left",
+ className: BDFDB.disCN._pluginrepoloadingtooltip,
+ delay: 500,
+ style: "max-width: unset;"
+ });
+ });
+ BDFDB.PluginUtils.addLoadingIcon(loadingIcon);
+
+ BDFDB.ReactUtils.forceUpdate(list, header);
+
+ for (let i = 0; i <= 20; i++) checkPlugin();
+ }
+ catch (err) {BDFDB.NotificationUtils.toast("Failed to load Plugin Store", {type: "danger"});}
+ if (response && response.statusCode == 403) BDFDB.NotificationUtils.toast("Failed to fetch Plugin Store from the Website Api due to DDoS Protection", {type: "danger"});
+ else if (response && response.statusCode == 404) BDFDB.NotificationUtils.toast("Failed to fetch Plugin Store from the Website Api due to Connection Issue", {type: "danger"});
+ });
+ }
+
+ getLoadingTooltipText () {
+ return BDFDB.LanguageUtils.LibraryStringsFormat("loading", `Plugin Repo - [${grabbedPlugins.filter(n => n.loaded).length}/${grabbedPlugins.length}]`);
+ }
+
+ getString (obj) {
+ let string = "";
+ if (typeof obj == "string") string = obj;
+ else if (obj && obj.props) {
+ if (typeof obj.props.children == "string") string = obj.props.children;
+ else if (Array.isArray(obj.props.children)) for (let c of obj.props.children) string += typeof c == "string" ? c : this.getString(c);
+ }
+ return string;
+ }
+
+ compareVersions (v1, v2) {
+ return !(v1 == v2 || !BDFDB.NumberUtils.compareVersions(v1, v2));
+ }
+
+ getInstalledPlugin (plugin) {
+ if (!plugin || typeof plugin.authorname != "string") return;
+ const iPlugin = BDFDB.BDUtils.getPlugin(plugin.name, false, true);
+ if (iPlugin && plugin.authorname.toUpperCase() == this.getString(iPlugin.author).toUpperCase()) return iPlugin;
+ else if (plugin.rawSourceUrl && window.BdApi && BdApi.Plugins && typeof BdApi.Plugins.getAll == "function") {
+ const filename = plugin.rawSourceUrl.split("/").pop();
+ for (let p of BdApi.Plugins.getAll()) if (p.filename == filename && plugin.authorname.toUpperCase() == this.getString(p.author).toUpperCase()) return p;
+ }
+ }
+
+ checkForNewPlugins () {
+ BDFDB.LibraryRequires.request("https://api.betterdiscord.app/v1/store/plugins", (error, response, body) => {
+ if (!error && body) try {
+ if (JSON.parse(body).filter(n => n).length != grabbedPlugins.length) {
+ loading = {is: false, timeout: null, amount: 0};
+ this.loadPlugins();
+ }
+ }
+ catch (err) {BDFDB.NotificationUtils.toast("Failed to load Plugin Store", {type: "danger"});}
+ });
+ }
+
+ setLabelsByLanguage () {
+ switch (BDFDB.LanguageUtils.getLanguage().id) {
+ case "bg": // Bulgarian
+ return {
+ list: "Списък",
+ notice_failed_plugins: "Някои Plugins [{{var0}}] не можаха да бъдат заредени",
+ notice_new_plugins: "Новите Plugins [{{var0}}] бяха добавени към Plugin Repo",
+ notice_outdated_plugins: "Някои Plugins [{{var0}}] са остарели"
+ };
+ case "da": // Danish
+ return {
+ list: "Liste",
+ notice_failed_plugins: "Nogle Plugins [{{var0}}] kunne ikke indlæses",
+ notice_new_plugins: "Nye Plugins [{{var0}}] er blevet føjet til Plugin Repo",
+ notice_outdated_plugins: "Nogle Plugins [{{var0}}] er forældede"
+ };
+ case "de": // German
+ return {
+ list: "Liste",
+ notice_failed_plugins: "Einige Plugins [{{var0}}] konnten nicht geladen werden",
+ notice_new_plugins: "Neue Plugins [{{var0}}] wurden zur Plugin Repo hinzugefügt",
+ notice_outdated_plugins: "Einige Plugins [{{var0}}] sind veraltet"
+ };
+ case "el": // Greek
+ return {
+ list: "Λίστα",
+ notice_failed_plugins: "Δεν ήταν δυνατή η φόρτωση ορισμένων Plugins [{{var0}}] ",
+ notice_new_plugins: "Προστέθηκαν νέα Plugins [{{var0}}] στο Plugin Repo",
+ notice_outdated_plugins: "Ορισμένα Plugins [{{var0}}] είναι παλιά"
+ };
+ case "es": // Spanish
+ return {
+ list: "Lista",
+ notice_failed_plugins: "Algunos Plugins [{{var0}}] no se pudieron cargar",
+ notice_new_plugins: "Se han agregado nuevos Plugins [{{var0}}] a Plugin Repo",
+ notice_outdated_plugins: "Algunas Plugins [{{var0}}] están desactualizadas"
+ };
+ case "fi": // Finnish
+ return {
+ list: "Lista",
+ notice_failed_plugins: "Joitain kohdetta Plugins [{{var0}}] ei voitu ladata",
+ notice_new_plugins: "Uusi Plugins [{{var0}}] on lisätty Plugin Repo",
+ notice_outdated_plugins: "Jotkut Plugins [{{var0}}] ovat vanhentuneita"
+ };
+ case "fr": // French
+ return {
+ list: "Liste",
+ notice_failed_plugins: "Certains Plugins [{{var0}}] n'ont pas pu être chargés",
+ notice_new_plugins: "De nouveaux Plugins [{{var0}}] ont été ajoutés à Plugin Repo",
+ notice_outdated_plugins: "Certains Plugins [{{var0}}] sont obsolètes"
+ };
+ case "hr": // Croatian
+ return {
+ list: "Popis",
+ notice_failed_plugins: "Neke datoteke Plugins [{{var0}}] nije moguće učitati",
+ notice_new_plugins: "Novi Plugins [{{var0}}] dodani su u Plugin Repo",
+ notice_outdated_plugins: "Neki su Plugins [{{var0}}] zastarjeli"
+ };
+ case "hu": // Hungarian
+ return {
+ list: "Lista",
+ notice_failed_plugins: "Néhány Plugins [{{var0}}] nem sikerült betölteni",
+ notice_new_plugins: "Új Plugins [{{var0}}] hozzáadva a következőhöz: Plugin Repo",
+ notice_outdated_plugins: "Néhány Plugins [{{var0}}] elavult"
+ };
+ case "it": // Italian
+ return {
+ list: "Elenco",
+ notice_failed_plugins: "Impossibile caricare alcuni Plugins [{{var0}}] ",
+ notice_new_plugins: "Il nuovo Plugins [{{var0}}] è stato aggiunto a Plugin Repo",
+ notice_outdated_plugins: "Alcuni Plugins [{{var0}}] non sono aggiornati"
+ };
+ case "ja": // Japanese
+ return {
+ list: "リスト",
+ notice_failed_plugins: "一部の Plugins [{{var0}}] を読み込めませんでした",
+ notice_new_plugins: "新しい Plugins [{{var0}}] が Plugin Repo に追加されました",
+ notice_outdated_plugins: "一部の Plugins [{{var0}}] は古くなっています"
+ };
+ case "ko": // Korean
+ return {
+ list: "명부",
+ notice_failed_plugins: "일부 Plugins [{{var0}}] 을 (를)로드 할 수 없습니다.",
+ notice_new_plugins: "새 Plugins [{{var0}}] 이 Plugin Repo 에 추가되었습니다.",
+ notice_outdated_plugins: "일부 Plugins [{{var0}}] 이 오래되었습니다."
+ };
+ case "lt": // Lithuanian
+ return {
+ list: "Sąrašas",
+ notice_failed_plugins: "Kai kurių Plugins [{{var0}}] nepavyko įkelti",
+ notice_new_plugins: "Naujas Plugins [{{var0}}] pridėtas prie Plugin Repo",
+ notice_outdated_plugins: "Kai kurie Plugins [{{var0}}] yra pasenę"
+ };
+ case "nl": // Dutch
+ return {
+ list: "Lijst",
+ notice_failed_plugins: "Sommige Plugins [{{var0}}] konden niet worden geladen",
+ notice_new_plugins: "Nieuwe Plugins [{{var0}}] zijn toegevoegd aan de Plugin Repo",
+ notice_outdated_plugins: "Sommige Plugins [{{var0}}] zijn verouderd"
+ };
+ case "no": // Norwegian
+ return {
+ list: "Liste",
+ notice_failed_plugins: "Noen Plugins [{{var0}}] kunne ikke lastes inn",
+ notice_new_plugins: "Nye Plugins [{{var0}}] er lagt til i Plugin Repo",
+ notice_outdated_plugins: "Noen Plugins [{{var0}}] er utdaterte"
+ };
+ case "pl": // Polish
+ return {
+ list: "Lista",
+ notice_failed_plugins: "Nie można załadować niektórych Plugins [{{var0}}] ",
+ notice_new_plugins: "Nowe Plugins [{{var0}}] zostały dodane do Plugin Repo",
+ notice_outdated_plugins: "Niektóre Plugins [{{var0}}] są nieaktualne"
+ };
+ case "pt-BR": // Portuguese (Brazil)
+ return {
+ list: "Lista",
+ notice_failed_plugins: "Algum Plugins [{{var0}}] não pôde ser carregado",
+ notice_new_plugins: "Novo Plugins [{{var0}}] foi adicionado ao Plugin Repo",
+ notice_outdated_plugins: "Alguns Plugins [{{var0}}] estão desatualizados"
+ };
+ case "ro": // Romanian
+ return {
+ list: "Listă",
+ notice_failed_plugins: "Unele Plugins [{{var0}}] nu au putut fi încărcate",
+ notice_new_plugins: "Plugins [{{var0}}] nou au fost adăugate la Plugin Repo",
+ notice_outdated_plugins: "Unele Plugins [{{var0}}] sunt învechite"
+ };
+ case "ru": // Russian
+ return {
+ list: "Список",
+ notice_failed_plugins: "Не удалось загрузить некоторые Plugins [{{var0}}] ",
+ notice_new_plugins: "Новые Plugins [{{var0}}] добавлены в Plugin Repo",
+ notice_outdated_plugins: "Некоторые Plugins [{{var0}}] устарели"
+ };
+ case "sv": // Swedish
+ return {
+ list: "Lista",
+ notice_failed_plugins: "Vissa Plugins [{{var0}}] kunde inte laddas",
+ notice_new_plugins: "Nya Plugins [{{var0}}] har lagts till i Plugin Repo",
+ notice_outdated_plugins: "Vissa Plugins [{{var0}}] är föråldrade"
+ };
+ case "th": // Thai
+ return {
+ list: "รายการ",
+ notice_failed_plugins: "ไม่สามารถโหลด Plugins [{{var0}}] บางรายการได้",
+ notice_new_plugins: "เพิ่ม Plugins [{{var0}}] ใหม่ใน Plugin Repo แล้ว",
+ notice_outdated_plugins: "Plugins [{{var0}}] บางรายการล้าสมัย"
+ };
+ case "tr": // Turkish
+ return {
+ list: "Liste",
+ notice_failed_plugins: "Bazı Plugins [{{var0}}] yüklenemedi",
+ notice_new_plugins: "Yeni Plugins [{{var0}}], Plugin Repo 'ye eklendi",
+ notice_outdated_plugins: "Bazı Plugins [{{var0}}] güncel değil"
+ };
+ case "uk": // Ukrainian
+ return {
+ list: "Список",
+ notice_failed_plugins: "Деякі Plugins [{{var0}}] не вдалося завантажити",
+ notice_new_plugins: "Нові Plugins [{{var0}}] були додані до Plugin Repo",
+ notice_outdated_plugins: "Деякі Plugins [{{var0}}] застарілі"
+ };
+ case "vi": // Vietnamese
+ return {
+ list: "Danh sách",
+ notice_failed_plugins: "Không thể tải một số Plugins [{{var0}}] ",
+ notice_new_plugins: "Plugins [{{var0}}] mới đã được thêm vào Plugin Repo",
+ notice_outdated_plugins: "Một số Plugins [{{var0}}] đã lỗi thời"
+ };
+ case "zh-CN": // Chinese (China)
+ return {
+ list: "清单",
+ notice_failed_plugins: "某些 Plugins [{{var0}}] 无法加载",
+ notice_new_plugins: "新的 Plugins [{{var0}}] 已添加到 Plugin Repo",
+ notice_outdated_plugins: "一些 Plugins [{{var0}}] 已过时"
+ };
+ case "zh-TW": // Chinese (Taiwan)
+ return {
+ list: "清單",
+ notice_failed_plugins: "某些 Plugins [{{var0}}] 無法加載",
+ notice_new_plugins: "新的 Plugins [{{var0}}] 已添加到 Plugin Repo",
+ notice_outdated_plugins: "一些 Plugins [{{var0}}] 已過時"
+ };
+ default: // English
+ return {
+ list: "List",
+ notice_failed_plugins: "Some Plugins [{{var0}}] could not be loaded",
+ notice_new_plugins: "New Plugins [{{var0}}] have been added to the Plugin Repo",
+ notice_outdated_plugins: "Some Plugins [{{var0}}] are outdated"
+ };
+ }
+ }
+ };
+ })(window.BDFDB_Global.PluginUtils.buildPlugin(config));
+})();
diff --git a/.config/BetterDiscord/plugins/ZeresPluginLibrary.config.json b/.config/BetterDiscord/plugins/ZeresPluginLibrary.config.json
new file mode 100755
index 0000000..8a41c1e
--- /dev/null
+++ b/.config/BetterDiscord/plugins/ZeresPluginLibrary.config.json
@@ -0,0 +1,6 @@
+{
+ "currentVersionInfo": {
+ "version": "2.0.4",
+ "hasShownChangelog": true
+ }
+}
\ No newline at end of file
diff --git a/.config/BetterDiscord/plugins/noDoubleBack.plugin.js b/.config/BetterDiscord/plugins/noDoubleBack.plugin.js
new file mode 100755
index 0000000..b1a1394
--- /dev/null
+++ b/.config/BetterDiscord/plugins/noDoubleBack.plugin.js
@@ -0,0 +1,9 @@
+/**
+ * @name No_Double_Back
+ */
+const {ipcRenderer} = require('electron')
+module.exports = class noDoubleBack {
+ load() { this.DISCORD_NAVIGATE_BACK = ipcRenderer._events.DISCORD_NAVIGATE_BACK}
+ start() { ipcRenderer.removeListener('DISCORD_NAVIGATE_BACK', ipcRenderer._events.DISCORD_NAVIGATE_BACK) }
+ stop() { ipcRenderer.addListener('DISCORD_NAVIGATE_BACK', this.DISCORD_NAVIGATE_BACK) }
+}
\ No newline at end of file