Repository: Metalloriff/BetterDiscordPlugins
Branch: master
Commit: 8bc613c09900
Files: 73
Total size: 442.3 KB
Directory structure:
gitextract_pvv8lvhu/
├── Assets/
│ ├── AC/
│ │ └── Preview/
│ │ └── README.md
│ ├── AIC/
│ │ └── Preview/
│ │ └── README.md
│ ├── BDEA/
│ │ └── Preview/
│ │ └── README.md
│ ├── BES/
│ │ └── Preview/
│ │ └── README.md
│ ├── BPECC/
│ │ └── Preview/
│ │ └── README.md
│ ├── CJS/
│ │ └── Preview/
│ │ └── README.md
│ ├── DST/
│ │ └── Preview/
│ │ └── README.md
│ ├── FMC/
│ │ └── Preview/
│ │ └── README.md
│ ├── GC/
│ │ └── Preview/
│ │ └── README.md
│ ├── GFRA/
│ │ └── Preview/
│ │ └── README.md
│ ├── IB/
│ │ └── Preview/
│ │ └── README.md
│ ├── MA/
│ │ └── Preview/
│ │ └── README.md
│ ├── ML/
│ │ └── Preview/
│ │ └── README.md
│ ├── OLID/
│ │ └── Preview/
│ │ └── README.md
│ ├── PCC/
│ │ └── Preview/
│ │ └── README.md
│ ├── PPT/
│ │ └── Preview/
│ │ └── README.md
│ ├── RA/
│ │ └── Preview/
│ │ └── README.md
│ ├── SB/
│ │ └── Preview/
│ │ └── README.md
│ ├── SBDE/
│ │ └── Preview/
│ │ └── README.md
│ ├── SLD/
│ │ └── Preview/
│ │ └── README.md
│ ├── ST/
│ │ └── Preview/
│ │ └── README.md
│ ├── TB/
│ │ └── Preview/
│ │ └── README.md
│ ├── UCB/
│ │ └── Preview/
│ │ └── README.md
│ ├── VCN/
│ │ └── Preview/
│ │ └── README.md
│ └── VGR/
│ └── Preview/
│ └── README.md
├── AutoCorrect.plugin.js
├── AutoRefreshSettingsPanel.plugin.js
├── AvatarIconViewer.plugin.js
├── BDEmoteAutocomplete.plugin.js
├── BetterEmoteSizes.plugin.js
├── Bruh.plugin.js
├── CustomJs.plugin.js
├── CustomizableAvatarDPI.plugin.js
├── DetailedServerTooltips.plugin.js
├── DoubleClickVoiceChannels.plugin.js
├── FormattableMessageCopier.plugin.js
├── GuildAndFriendRemovalAlerts/
│ ├── GuildAndFriendRemovalAlerts.plugin.js
│ ├── README.md
│ └── src/
│ ├── components/
│ │ ├── item.jsx
│ │ ├── item.scss
│ │ └── settings.jsx
│ ├── index.js
│ ├── modules/
│ │ └── settings.js
│ ├── package.json
│ └── styles.scss
├── GuildAndFriendRemovalAlerts.plugin.js
├── GuildCounter.plugin.js
├── IdleGuildlistScroller.plugin.js
├── ImageBrowser.plugin.js
├── Lib/
│ └── NeatoBurritoLibrary.js
├── MentionAliases.plugin.js
├── NateUtilities.plugin.js
├── OpenLinksInDiscord.plugin.js
├── PinCollapsedChannels.plugin.js
├── PinPluginsAndThemes.plugin.js
├── PreventSpotifyAutoPause.plugin.js
├── README.md
├── ReactionImages.plugin.js
├── SaveTo.plugin.js
├── SelectedChannelNotifications.plugin.js
├── SendBDEmotes.plugin.js
├── SendLinksDirectly.plugin.js
├── ShareButton.plugin.js
├── SuppressUserMentions.plugin.js
├── TheClapBestClapPluginClapEver.plugin.js
├── TransitioningBackgrounds.plugin.js
├── UnreadCountBadges.plugin.js
├── UserBirthdays.plugin.js
├── VCMuteSounds.plugin.js
├── VideoExamples/
│ └── README.md
├── ViewGuildRelationships.plugin.js
├── VoiceChatNotifications.plugin.js
└── VoiceChatPanel.plugin.js
================================================
FILE CONTENTS
================================================
================================================
FILE: Assets/AC/Preview/README.md
================================================



================================================
FILE: Assets/AIC/Preview/README.md
================================================





================================================
FILE: Assets/BDEA/Preview/README.md
================================================


================================================
FILE: Assets/BES/Preview/README.md
================================================


================================================
FILE: Assets/BPECC/Preview/README.md
================================================


================================================
FILE: Assets/CJS/Preview/README.md
================================================


================================================
FILE: Assets/DST/Preview/README.md
================================================



================================================
FILE: Assets/FMC/Preview/README.md
================================================




================================================
FILE: Assets/GC/Preview/README.md
================================================

================================================
FILE: Assets/GFRA/Preview/README.md
================================================



================================================
FILE: Assets/IB/Preview/README.md
================================================

================================================
FILE: Assets/MA/Preview/README.md
================================================



================================================
FILE: Assets/ML/Preview/README.md
================================================




================================================
FILE: Assets/OLID/Preview/README.md
================================================

================================================
FILE: Assets/PCC/Preview/README.md
================================================



================================================
FILE: Assets/PPT/Preview/README.md
================================================



================================================
FILE: Assets/RA/Preview/README.md
================================================


================================================
FILE: Assets/SB/Preview/README.md
================================================




================================================
FILE: Assets/SBDE/Preview/README.md
================================================


================================================
FILE: Assets/SLD/Preview/README.md
================================================

================================================
FILE: Assets/ST/Preview/README.md
================================================






================================================
FILE: Assets/TB/Preview/README.md
================================================




================================================
FILE: Assets/UCB/Preview/README.md
================================================



================================================
FILE: Assets/VCN/Preview/README.md
================================================




================================================
FILE: Assets/VGR/Preview/README.md
================================================


================================================
FILE: AutoCorrect.plugin.js
================================================
/**
* @name AutoCorrect
* @invite yNqzuJa
* @authorLink https://github.com/Metalloriff
* @donate https://www.paypal.me/israelboone
* @website https://metalloriff.github.io/toms-discord-stuff/
* @source https://github.com/Metalloriff/BetterDiscordPlugins/blob/master/AutoCorrect.plugin.js
*/
/* BUGS AND TODO
Implement DB's SpellChecker.
*/
module.exports = (() =>
{
const config =
{
info:
{
name: "AutoCorrect",
authors:
[
{
name: "Metalloriff",
discord_id: "264163473179672576",
github_username: "metalloriff",
twitter_username: "Metalloriff"
}
],
version: "2.0.1",
description: "Gives you the options to automatically replace all mis-spelled words with Discord's first correction, automatically punctuate and/or capitalize your sentences, and set up override aliases for typing.",
github: "https://github.com/Metalloriff/BetterDiscordPlugins/blob/master/AutoCorrect.plugin.js",
github_raw: "https://raw.githubusercontent.com/Metalloriff/BetterDiscordPlugins/master/AutoCorrect.plugin.js"
},
changelog:
[
{
title: "2.0 Rewrite",
type: "fixed",
items: [
"AutoCorrect has been completely rewritten from the ground up, new bugs that I missed during development are likely to occur. If you experience any bugs, please report them to me.",
"Fixed a settings save/load issue.",
"Fixed spelling errors not auto replacing, I did big dumb."
]
}
]
};
return !global.ZeresPluginLibrary ? class
{
constructor() { this._config = config; }
getName = () => config.info.name;
getAuthor = () => config.info.description;
getVersion = () => 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 (err, res, body) =>
{
if (err) 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 { WebpackModules, DiscordModules, ReactComponents, Patcher, PluginUtilities, Settings, Utilities } = Api;
const { React } = DiscordModules;
const process = require("process");
const SlateModule = WebpackModules.find(m => m.deserialize && !m.add);
const SpellChecker = WebpackModules.getByProps("isMisspelled");
const ContextMenu = WebpackModules.getByProps("MenuItem");
const Button = WebpackModules.find(m => m.Link && m.Link.displayName == "ButtonLink");
const getSelectiontext = WebpackModules.getByProps("getSelectionText").getSelectionText;
const formatToDict = s =>
{
if (s == null || s.toLowerCase == null)
return "";
const match = s.toLowerCase().match(/([a-z]|[0-9])/gi);
return match == null ? "" : match.join("");
};
let lastRender = new Date();
let lastError;
let lastLength;
let lastTextAreaEvent;
return class AutoCorrect extends Plugin
{
constructor()
{
super();
this.defaultSettings =
{
autoReplace: true,
punctuate: true,
capitalize: true,
bsUndo: true,
overrides:
[
{
key: "idk",
value: "I don't know"
},
{
key: "discord",
value: "shitcord"
}
],
dictionary: []
};
}
async showChangelog(footer)
{
try { footer = (await WebpackModules.getByProps("getUser", "acceptAgreements").getUser("264163473179672576")).tag + " | https://discord.gg/yNqzuJa"; }
finally { super.showChangelog(footer); }
}
loadSettings = () => this.settings = PluginUtilities.loadSettings(config.info.name, this.defaultSettings);
saveSettings = () => PluginUtilities.saveSettings(config.info.name, this.settings);
createSettingsSwitch(title, desc, setting)
{
return new Settings.Switch(title, desc, this.settings[setting], value =>
{
this.settings[setting] = value;
this.saveSettings();
}).getElement();
}
createSettingsOverrideItem(index)
{
const item = new Settings.SettingGroup("Override Item #" + (parseInt(index) + 1)).append(
new Settings.Textbox("Key", null, this.settings.overrides[index].key, key =>
{
this.settings.overrides[index].key = key;
this.saveSettings();
}).getElement(),
new Settings.Textbox("Value", null, this.settings.overrides[index].value, value =>
{
this.settings.overrides[index].value = value;
this.saveSettings();
}).getElement(),
new Settings.SettingField(null, null, null, Button,
{
children: "Remove Override",
onClick: () =>
{
this.settings.overrides.splice(index);
this.saveSettings();
document.getElementById("ac-oi-" + index).remove();
}
}).getElement()
).getElement();
item.id = "ac-oi-" + index;
return item;
}
getSettingsPanel()
{
this.loadSettings();
let overrideItems = [];
for (let i in this.settings.overrides)
overrideItems.push(this.createSettingsOverrideItem(i));
return new Settings.SettingPanel(null,
this.createSettingsSwitch("Auto Replace Spelling Errors", "Automatically replaces any spelling mistakes you make with the first available correction, if any.", "autoReplace"),
this.createSettingsSwitch("Auto Punctuate", "Automatically punctuates your messages when you send them.", "punctuate"),
this.createSettingsSwitch("Auto Capitalize", "Automatically capitlizes the first words after punctuations and at the start of your messages.", "capitalize"),
this.createSettingsSwitch("Backspace Undo Correction", "Hitting backspace after a correction will undo it to the errored word.", "bsUndo"),
new Settings.SettingGroup("Overrides").append(
...overrideItems,
new Settings.SettingField(null, null, null, Button,
{
children: "Add Override",
onClick: () =>
{
this.settings.overrides.push(
{
key: "ex",
value: "Example."
});
this.saveSettings();
const parent = document.querySelector("div.plugin-settings div.plugin-inputs");
parent.insertBefore(this.createSettingsOverrideItem(this.settings.overrides.length - 1), parent.lastChild);
}
}).getElement()
).getElement()
).getElement();
}
async onStart()
{
this.loadSettings();
const TextArea = await ReactComponents.getComponentByName("ChannelEditorContainer", "*");
const SlateTextAreaContextMenu = WebpackModules.find(m => m.default && m.default.displayName == "SlateTextAreaContextMenu");
SpellChecker.setLearnedWords(new Set(this.settings.dictionary));
Patcher.after(SpellChecker, "setLearnedWords", (_, [set]) =>
{
for (let word of set)
{
if (this.settings.dictionary.indexOf(formatToDict(word)) == -1)
{
this.learnWord(formatToDict(word));
}
}
});
Patcher.after(SlateTextAreaContextMenu, "default", (_, [], re) =>
{
if (getSelectiontext().length == 0)
return;
let index = -1;
for (let i = 0; i < this.settings.dictionary.length; i++)
if (this.settings.dictionary[i] == formatToDict(getSelectiontext()))
index = i;
if (index == -1)
return;
const spellCheckGroup = Utilities.getNestedProp(re, "props.children.1.props.children.props.children");
if (spellCheckGroup != null)
spellCheckGroup.unshift(
React.createElement(
ContextMenu.MenuItem,
{
label: "Remove from Dictionary",
id: "ac-removefromdict",
action: () => this.forgetWord(index)
}
)
);
else
console.warn("AutoCorrect: SpellCheckGroup nested prop could not be found!");
});
Patcher.instead(DiscordModules.MessageActions, "sendMessage", (_, args, sendMessage) =>
{
this.tryCorrect(args[1].content + " ", true).then(correction =>
{
args[1].content = correction;
sendMessage(...args);
lastLength = -1;
process.nextTick(() => this.setText(null, ""));
});
});
Patcher.after(TextArea.component.prototype, "render", e =>
{
const now = new Date();
if (now - lastRender > 10)
{
this.tryCorrect(e.props.textValue, false).then(correction =>
{
if (e.props.textValue != correction)
{
this.setText(e, correction);
}
});
}
lastRender = now;
});
}
async tryCorrect(textValue, sending)
{
const words = textValue.split(" ");
let lastWordIndex = words.length - 2;
for (let i = words.length - 1; i > -1; i--)
{
if (words[i].trim().length > 0)
{
lastWordIndex = i;
break;
}
}
if (words.join(" ").length < lastLength)
{
if (this.settings.bsUndo)
words[lastWordIndex] = lastError;
lastLength = words.join(" ").length;
return words.join(" ");
}
if (this.settings.autoReplace && (sending || textValue.endsWith(" ")) && words[lastWordIndex] != lastError)
{
let hasOverride = false;
for (let o of this.settings.overrides)
if (o.value.toLowerCase() == words[lastWordIndex].toLowerCase())
hasOverride = true;
const isMisspelled = !hasOverride && this.settings.dictionary.indexOf(formatToDict(words[lastWordIndex])) == -1 && await SpellChecker.isMisspelled(words[lastWordIndex]);
if (isMisspelled)
{
const corrections = await SpellChecker.getCorrections(words[lastWordIndex]);
if (corrections && corrections.length > 0)
{
words[lastWordIndex] = corrections[0];
lastError = words[lastWordIndex];
}
}
}
else
await Promise.resolve();
if (words[lastWordIndex] != null)
{
for (let override of this.settings.overrides)
{
if (override.key.toLowerCase() == words[lastWordIndex].toLowerCase()
&& override.value.toLowerCase() != words[lastWordIndex].toLowerCase() && words[lastWordIndex] != lastError)
{
lastError = words[lastWordIndex];
words[lastWordIndex] = override.value;
}
}
}
let wasPunctuated = false;
if (this.settings.capitalize && (sending || textValue.endsWith(" ")))
{
if (words[lastWordIndex] == "i")
words[lastWordIndex] = "I";
if (words[0].charAt(0) != words[0].charAt(0).toUpperCase() && !words[0].trim().startsWith("http"))
words[0] = words[0].charAt(0).toUpperCase() + words[0].slice(1);
for (let i = 0; i < words.length; i++)
{
if (wasPunctuated && words[i].trim().length > 1 && !words[i].trim().startsWith("http"))
{
words[i] = words[i].charAt(0).toUpperCase() + words[i].slice(1);
wasPunctuated = false;
}
if (words[i].trim().match(/[.!?\\-]$/g))
wasPunctuated = true;
}
}
if (this.settings.punctuate && (sending || textValue.endsWith(" ")))
{
for (let i = words.length - 1; i > -1; i--)
{
if (words[i].trim().match(/[.!?\\-]$/g))
{
i = -1;
continue;
}
if (words[i].trim().endsWith(">")
|| words[i].trim().match(/(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])/g) || words[i].trim().startsWith("http"))
continue;
if (i > -1 && words[i].trim().length > 0)
{
words[i] += ".";
break;
}
}
}
return words.join(" ");
}
setText(_e, text)
{
const e = _e == null ? lastTextAreaEvent : _e;
if (e && e.ref.current)
{
e.ref.current.setValue(SlateModule.deserialize(text));
e.focus();
e.ref.current.editorRef.moveFocusToEndOfText();
e.ref.current.editorRef.moveToFocus();
lastTextAreaEvent = e;
}
}
learnWord(word)
{
if (word.length == 0)
return;
this.settings.dictionary.push(formatToDict(word));
this.saveSettings();
SpellChecker.setLearnedWords(new Set(this.settings.dictionary));
}
forgetWord(index)
{
if (index != -1)
{
this.settings.dictionary.splice(index);
this.saveSettings();
}
SpellChecker.setLearnedWords(new Set(this.settings.dictionary));
}
onStop()
{
Patcher.unpatchAll();
}
};
}
return plugin(Plugin, Api);
})(global.ZeresPluginLibrary.buildPlugin(config));
})();
================================================
FILE: AutoRefreshSettingsPanel.plugin.js
================================================
//META{"name":"AutoRefreshSettingsPanel"}*//
class AutoRefreshSettingsPanel {
getName() { return "AutoRefreshSettingsPanel"; }
getDescription() { return "Automatically refreshes the BetterDiscord plugin settings panel when clicking into Discord, making developing settings menus 1,000x faster if you have a lot of plugins."; }
getVersion() { return "0.0.1"; }
getAuthor() { return "Metalloriff"; }
load() {}
start() {
this.refreshPluginSettings = () => {
let panel = document.getElementsByClassName("plugin-settings")[0];
if(panel) panel.innerHTML = BdApi.getPlugin(panel.id.substring(panel.id.lastIndexOf("-") + 1, panel.id.length)).getSettingsPanel();
};
window.addEventListener("focus", this.refreshPluginSettings);
}
stop() {
window.removeEventListener("focus", this.refreshPluginSettings);
}
}
================================================
FILE: AvatarIconViewer.plugin.js
================================================
/**
* @name AvatarIconViewer
* @invite yNqzuJa
* @authorLink https://discord.com/users/264163473179672576
* @donate https://www.paypal.me/israelboone
* @website https://kinzoku.one/
* @source https://github.com/Metalloriff/BetterDiscordPlugins/blob/master/AvatarIconViewer.plugin.js
*/
module.exports = (() => {
const config =
{
info:
{
name: "AvatarIconViewer",
authors:
[
{
name: "Metalloriff",
discord_id: "264163473179672576",
github_username: "metalloriff",
twitter_username: "Metalloriff"
}
],
version: "2.0.2",
description: "Allows you to view server icons, user avatars, and emotes in fullscreen via the context menu, or copy the link to them.",
github: "https://github.com/Metalloriff/BetterDiscordPlugins/blob/master/AvatarIconViewer.plugin.js",
github_raw: "https://raw.githubusercontent.com/Metalloriff/BetterDiscordPlugins/master/AvatarIconViewer.plugin.js"
}
};
return !global.ZeresPluginLibrary ? class {
constructor() { this._config = config; }
getName = () => config.info.name;
getAuthor = () => config.info.description;
getVersion = () => 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 (err, res, body) => {
if (err) 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 { DiscordModules, WebpackModules, Patcher } = Api;
const { React } = DiscordModules
const ModalStack = WebpackModules.getByProps("openModal", "hasModalOpen");
const { ModalRoot } = WebpackModules.getByProps("ModalRoot");
const { ModalSize } = WebpackModules.getByProps("ModalSize");
const ImageModal = WebpackModules.getByDisplayName("ImageModal");
const ContextMenu = WebpackModules.getByProps("MenuItem");
const MaskedLink = WebpackModules.getByDisplayName("MaskedLink");
const getChannelIconURL = WebpackModules.getByProps("getChannelIconURL").getChannelIconURL;
const copyToClipboard = require("electron").clipboard.writeText;
const formatURL = url =>
url == null || url.length == 0
? null
: (url.includes("/a_")
? url.replace(".webp", ".gif").replace(".png", ".gif")
: url).split("?")[0] + "?size=2048";
return class AvatarIconViewer extends Plugin {
constructor() {
super();
}
async onStart() {
// I have no idea why, nor do I have the energy to figure out why,
// but shitcord does not load the context menu modules until the context menu is opened.
// Therefore, this shitty workaround was made, which requires the user to
// open the context menu once before the plugin will work.
// UPDATE: It's less spaghetti, but still spaghetti. I'm sorry.
this.patched = [];
document.getElementById("app-mount").addEventListener("contextmenu", this.contextMenuListener = e => {
const modules = WebpackModules.findAll(
m => m.default && m.default.displayName && (
m.default.displayName.endsWith("UserContextMenu") || ~[
"GroupDMContextMenu",
"GuildContextMenu",
"MessageContextMenu"
].indexOf(m.default.displayName)
)
);
for (const m of modules) {
if (~this.patched.indexOf(m.default.displayName)) {
continue;
}
switch (m.default.displayName) {
default: {
Patcher.after(m, "default", (_, [props], re) => {
const type = props.user ? "Avatar" : props.channel && props.channel.type == 3 ? "Icon" : null;
const url = formatURL(type == "Avatar" ? props.user.getAvatarURL() : type == "Icon" ? getChannelIconURL(props.channel) : null);
if (type && url)
re.props.children.props.children.push(this.createContext(url, type));
});
} break;
case "GuildContextMenu": {
Patcher.after(m, "default", (_, [props], re) => {
const url = formatURL(props.guild.getIconURL());
if (url)
re.props.children.push(this.createContext(url, "Icon"));
});
} break;
case "MessageContextMenu": {
Patcher.after(m, "default", (_, [props], re) => {
if (props.target && props.target.src) {
re.props.children.push(this.createContext(props.target.src, "Emoji"));
}
});
} break;
}
this.patched.push(m.default.displayName);
}
});
}
createContext(url, type) {
const ClassModule = WebpackModules.getByProps("modal", "image");
return React.createElement(ContextMenu.MenuGroup,
{
children:
[
React.createElement(
ContextMenu.MenuItem,
{
label: "View " + type,
id: "aiv-view",
action: () =>
ModalStack.openModal(
props => (
React.createElement(
ModalRoot,
{
className: ClassModule.modal,
...props,
size: ModalSize.DYNAMIC
},
React.createElement(
ImageModal,
{
src: url,
placeholder: url,
original: url,
width: 2048,
height: 2048,
onClickUntrusted: e => e.openHref(),
renderLinkComponent: props => React.createElement(MaskedLink, props)
}
)
)
)
)
}
),
React.createElement(
ContextMenu.MenuItem,
{
label: "Copy " + type + " Link",
id: "aiv-copy",
action: () => copyToClipboard(url)
}
)
]
});
}
onStop() {
Patcher.unpatchAll();
document.getElementById("app-mount").removeEventListener("contextmenu", this.contextMenuListener);
}
}
};
return plugin(Plugin, Api);
})(global.ZeresPluginLibrary.buildPlugin(config));
})();
================================================
FILE: BDEmoteAutocomplete.plugin.js
================================================
//META{"name":"BDEmoteAutocomplete","website":"https://github.com/Metalloriff/BetterDiscordPlugins/blob/master/README.md","source":"https://github.com/Metalloriff/BetterDiscordPlugins/blob/master/BDEmoteAutocomplete.plugin.js"}*//
class BDEmoteAutocomplete {
getName() { return "BDEmoteAutocomplete"; }
getDescription() { return "Adds an auto-complete menu for BetterDiscord emotes."; }
getVersion() { return "1.0.4"; }
getAuthor() { return "Metalloriff"; }
getChanges() {
return {
};
}
load() {}
start() {
let libLoadedEvent = () => {
try{ this.onLibLoaded(); }
catch(err) { console.error(this.getName(), "fatal error, plugin could not be started!", err); }
};
let lib = document.getElementById("NeatoBurritoLibrary");
if(lib == undefined) {
lib = document.createElement("script");
lib.setAttribute("id", "NeatoBurritoLibrary");
lib.setAttribute("type", "text/javascript");
lib.setAttribute("src", "https://rawgit.com/Metalloriff/BetterDiscordPlugins/master/Lib/NeatoBurritoLibrary.js");
document.head.appendChild(lib);
}
if(typeof window.NeatoLib !== "undefined") libLoadedEvent();
else lib.addEventListener("load", libLoadedEvent);
}
getSettingsPanel() {
setTimeout(() => {
NeatoLib.Settings.pushElement(NeatoLib.Settings.Elements.createToggleSwitch("Case-sensitive", this.settings.caseSensitive, () => {
this.settings.caseSensitive = !this.settings.caseSensitive;
this.saveSettings();
}), this.getName());
NeatoLib.Settings.pushElement(NeatoLib.Settings.Elements.createToggleGroup("bdea-enabled-channels", "Enabled emote channels", [
{ title : "TwitchGlobal", value : "TwitchGlobal", setValue : this.settings.enabledChannels.includes("TwitchGlobal") },
{ title : "TwitchSubscriber", value : "TwitchSubscriber", setValue : this.settings.enabledChannels.includes("TwitchSubscriber") },
{ title : "BTTV", value : "BTTV", setValue : this.settings.enabledChannels.includes("BTTV") },
{ title : "FrankerFaceZ", value : "FrankerFaceZ", setValue : this.settings.enabledChannels.includes("FrankerFaceZ") },
{ title : "BTTV2", value : "BTTV2", setValue : this.settings.enabledChannels.includes("BTTV2") }
], choice => {
if(this.settings.enabledChannels.includes(choice.value)) this.settings.enabledChannels.splice(this.settings.enabledChannels.indexOf(choice.value, 1));
else this.settings.enabledChannels.push(choice.value);
this.saveSettings();
this.getEmotes();
}), this.getName());
NeatoLib.Settings.pushElement(NeatoLib.Settings.Elements.createTextField("Prefix to display autocomplete", "text", this.settings.prefix, e => {
this.settings.prefix = e.target.value;
this.saveSettings();
}, { tooltip : "If you set this, it will be required before an emote to display the auto-complete menu." }), this.getName());
NeatoLib.Settings.pushElement(NeatoLib.Settings.Elements.createTextField("Auto-complete emote size", "number", this.settings.size, e => {
this.settings.size = e.target.value;
this.saveSettings();
}, { tooltip : "The size in pixels to display the auto-complete emotes." }), this.getName());
NeatoLib.Settings.pushElement(NeatoLib.Settings.Elements.createTextField("Auto-complete results limit", "number", this.settings.resultsCap, e => {
this.settings.resultsCap = e.target.value;
this.saveSettings();
}, { tooltip : "Maximum amount of results to display. The higher this is, the slower larger results will be." }), this.getName());
NeatoLib.Settings.pushElement(NeatoLib.Settings.Elements.createTextField("Auto-complete display delay (ms)", "number", this.settings.autocompleteDelay, e => {
this.settings.autocompleteDelay = e.target.value;
this.saveSettings();
}, { tooltip : "Delay in millseconds to display the auto-complete menu after pressing a key." }), this.getName());
NeatoLib.Settings.pushChangelogElements(this);
}, 0);
return NeatoLib.Settings.Elements.pluginNameLabel(this.getName());
}
saveSettings() {
NeatoLib.Settings.save(this);
}
updateSelected() {
let items = document.getElementById("bdea-autocomplete-list").getElementsByClassName("selector-2IcQBU selectable-3dP3y-");
for(let i = 0; i < items.length; i++) {
if(i == this.selectedIDX) items[i].classList.add("selectorSelected-1_M1WV");
else items[i].classList.remove("selectorSelected-1_M1WV");
}
}
onLibLoaded() {
NeatoLib.Updates.check(this);
this.settings = NeatoLib.Settings.load(this, {
displayUpdateNotes : true,
enabledChannels : ["TwitchGlobal", "TwitchSubscriber", "BTTV", "FrankerFaceZ", "BTTV2"],
prefix : "",
size : 16,
caseSensitive : true,
resultsCap : 15,
autocompleteDelay : 750
});
//if(this.settings.displayUpdateNotes) NeatoLib.Changelog.compareVersions(this.getName(), this.getChanges());
this.selectedIDX = 0;
this.results = [];
this.onGlobalKey = e => {
if(e.key == "Tab") {
let list = document.getElementById("bdea-autocomplete-list");
if(list) list.getElementsByClassName("autocompleteRowVertical-q1K4ky autocompleteRow-2OthDa")[this.selectedIDX].click();
}
};
this.onChatInput = (e, fromTimeout) => {
if(this.inputTimeout) clearTimeout(this.inputTimeout);
if(e.key.includes("Arrow")) {
if(e.key.includes("Up") && this.selectedIDX > 0) this.selectedIDX--;
else if(e.key.includes("Down") && this.selectedIDX < this.results.length - 1) this.selectedIDX++;
else return;
this.updateSelected();
return;
}
let chatbox = e.target, autocomplete = document.getElementById("bdea-autocomplete"), words = chatbox.value.split(" "), lastWord = words[words.length - 1];
if(!lastWord || lastWord.length < 4 || (this.settings.prefix && !lastWord.startsWith(this.settings.prefix))) {
if(autocomplete) autocomplete.outerHTML = "";
return;
}
if(this.settings.autocompleteDelay && !fromTimeout) {
this.inputTimeout = setTimeout(() => {
this.onChatInput(e, true);
}, this.settings.autocompleteDelay);
return;
}
if(this.settings.prefix) lastWord = lastWord.substring(this.settings.prefix.length, lastWord.length);
let emotes = [];
let lim = 0;
for(let i = 0; i < this.emotes.length; i++) {
if(lim >= this.settings.resultsCap) break;
if((this.settings.caseSensitive && !this.emotes[i].name.startsWith(lastWord)) || (!this.settings.caseSensitive && !this.emotes[i].name.toLowerCase().startsWith(lastWord.toLowerCase()))) continue;
emotes.push(this.emotes[i]);
lim++;
}
this.results = emotes;
if(emotes.length == 0) {
if(autocomplete) autocomplete.outerHTML = "";
return;
}
if(!autocomplete) {
chatbox.parentElement.insertAdjacentHTML("beforeend", `
Emotes matching ${lastWord}
`);
autocomplete = document.getElementById("bdea-autocomplete");
}
this.selectedIDX = 0;
let list = document.getElementById("bdea-autocomplete-list");
list.innerHTML = "";
for(let i = 0; i < emotes.length; i++) {
list.insertAdjacentHTML("beforeend", `
${emotes[i].name}
${emotes[i].channel}
`);
let items = document.getElementsByClassName("autocompleteRowVertical-q1K4ky autocompleteRow-2OthDa");
items[items.length - 1].addEventListener("click", e => {
words[words.length - 1] = emotes[this.selectedIDX].name + " ";
NeatoLib.Chatbox.setText(words.join(" "));
autocomplete.outerHTML = "";
});
items[items.length - 1].addEventListener("mouseover", e => {
this.selectedIDX = i;
this.updateSelected();
});
}
this.updateSelected();
};
this.getEmotes();
this.initialized = true;
document.addEventListener("keydown", this.onGlobalKey);
NeatoLib.Events.onPluginLoaded(this);
this.switch();
this.switchEvent = () => this.switch();
NeatoLib.Events.attach("switch", this.switchEvent);
}
switch() {
if(this.initialized != true) return;
if(this.emotes.length == 0) this.getEmotes();
let chatbox = NeatoLib.Chatbox.get();
if(chatbox) chatbox.addEventListener("keyup", this.onChatInput);
}
getEmotes() {
let emoteChannels = this.settings.enabledChannels, pushed = {};
this.emotes = [];
let blacklisted = {};
for(let i = 0; i < window.bemotes.length; i++) blacklisted[window.bemotes[i]] = true;
for(let ec of emoteChannels) {
let emoteChannel = window.bdEmotes[ec];
for(let emote in emoteChannel) {
if(emote.length > 2 && !pushed[emote] && !blacklisted[emote]) {
this.emotes.push({ name : emote, url : emoteChannel[emote], channel : ec });
pushed[emote] = true;
}
}
}
this.emotes.sort((a, b) => a.name.length - b.name.length);
}
stop() {
let chatbox = NeatoLib.Chatbox.get();
if(chatbox) chatbox.removeEventListener("keyup", this.onChatInput);
document.removeEventListener("keydown", this.onGlobalKey);
if(this.inputTimeout) clearTimeout(this.inputTimeout);
NeatoLib.Events.detach("switch", this.switchEvent);
}
}
================================================
FILE: BetterEmoteSizes.plugin.js
================================================
//META{"name":"BetterEmoteSizes","website":"https://metalloriff.github.io/toms-discord-stuff/","source":"https://github.com/Metalloriff/BetterDiscordPlugins/blob/master/BetterEmoteSizes.plugin.js"}*//
class BetterEmoteSizes {
getName() { return "Emote Zoom"; }
getDescription() { return "Increases the size of emojis, emotes, and reactions upon hovering over them and allows you to change their default sizes."; }
getVersion() { return "2.4.17"; }
getAuthor() { return "Metalloriff"; }
get settingFields() {
return {
alterSmall: { label: "Affect small emojis", type: "bool" },
smallSize: { label: "Default small emoji size (px)", type: "number" },
alterLarge: { label: "Affect large emojis", type: "bool" },
largeSize: { label: "Default large emoji size (px)", type: "number" },
alterBD: { label: "Affect small BetterDiscord emotes", type: "bool" },
bdSize: { label: "Default small BetterDiscord emote size (px)", type: "number" },
alterLargeBD: { label: "Affect large BetterDiscord emotes", type: "bool" },
largeBdSize: { label: "Default large BetterDiscord emote size (px)", type: "number" },
alterReactions: { label: "Affect reactions", type: "bool" },
reactionSize: { label: "Default reaction size (px)", type: "number" },
hoverSize: { label: "Emoji and BetterDiscord emote hover size multiplier", type: "number" },
reactionHoverSize: { label: "Reaction hover size multiplier", type: "number" },
transitionSpeed: { label: "Transition speed (seconds)", type: "number" },
delayAmount: { label: "Delay amount (seconds)", type: "number" },
equal: { label: "Small and large emote zoom to equal", type: "bool" }
};
}
get defaultSettings() {
return {
displayUpdateNotes: true,
alterSmall: true,
smallSize: 22,
alterLarge: true,
largeSize: 32,
alterBD: true,
bdSize: 28,
alterLargeBD: true,
largeBdSize: 32,
alterReactions: true,
reactionSize: 16,
hoverSize: 3,
transitionSpeed: 0.5,
delayAmount: 0,
reactionHoverSize: 2,
equal: false
};
}
getSettingsPanel() {
return NeatoLib.Settings.createPanel(this);
}
saveSettings() {
NeatoLib.Settings.save(this);
this.update();
}
load() {}
start() {
const libLoadedEvent = () => {
try{ this.onLibLoaded(); }
catch(err) { console.error(this.getName(), "fatal error, plugin could not be started!", err); try { this.stop(); } catch(err) { console.error(this.getName() + ".stop()", err); } }
};
let lib = document.getElementById("NeatoBurritoLibrary");
if (!lib) {
lib = document.createElement("script");
lib.id = "NeatoBurritoLibrary";
lib.type = "text/javascript";
lib.src = "https://rawgit.com/Metalloriff/BetterDiscordPlugins/master/Lib/NeatoBurritoLibrary.js";
document.head.appendChild(lib);
}
this.forceLoadTimeout = setTimeout(libLoadedEvent, 30000);
if (typeof window.NeatoLib !== "undefined") libLoadedEvent();
else lib.addEventListener("load", libLoadedEvent);
}
onLibLoaded() {
this.settings = NeatoLib.Settings.load(this);
NeatoLib.Updates.check(this, "https://raw.githubusercontent.com/Metalloriff/BetterDiscordPlugins/master/BetterEmoteSizes.plugin.js");
if (!NeatoLib.hasRequiredLibVersion(this, "0.7.19")) return;
this.update();
NeatoLib.Events.onPluginLoaded(this);
}
update() {
const markup = NeatoLib.getClass("markup"), markupRtl = NeatoLib.getClass("markupRtl"), messageGroup = NeatoLib.getClass("cozyMessage"), message = NeatoLib.getClass("message"), reaction = NeatoLib.getClass("reaction"), reactionMe = NeatoLib.getClass("reactionMe");
if (this.style) this.style.destroy();
this.style = NeatoLib.injectCSS(`.${messageGroup} { overflow: visible; }`);
if (this.settings.alterSmall) {
this.style.append(`
#app-mount .${markup} > .emoji:not(.jumboable),
#app-mount .${markupRtl} > .emoji:not(.jumboable) {
height: ${this.settings.smallSize}px;
width: auto;
transform: scale(1);
transition: transform ${this.settings.transitionSpeed}s;
transition-delay: 0s;
}
#app-mount .${markup} > .emoji:not(.jumboable):hover,
#app-mount .${markupRtl} > .emoji:not(.jumboable):hover {
transform: scale(${this.settings.equal ? ((this.settings.largeSize / this.settings.smallSize) * this.settings.hoverSize) : this.settings.hoverSize});
position: relative;
z-index: 1;
transition-delay: ${this.settings.delayAmount}s;
}
#app-mount .${messageGroup}:last-child .${message}:nth-last-child(2) .${markup} .emoji:not(.jumboable):hover,
#app-mount .${messageGroup}:last-child .${message}:nth-last-child(2) .${markupRtl} .emoji:not(.jumboable):hover {
transform: scale(${this.settings.equal ? ((this.settings.largeSize / this.settings.smallSize) * this.settings.hoverSize) : this.settings.hoverSize}) translateY(-35%);
}
`);
}
if (this.settings.alterLarge) {
this.style.append(`
#app-mount .${markup} > .emoji.jumboable,
#app-mount .${markupRtl} > .emoji.jumboable {
height: ${this.settings.largeSize}px;
width: auto;
transform: scale(1);
transition: transform ${this.settings.transitionSpeed}s;
transition-delay: 0s;
}
#app-mount .${markup} > .emoji.jumboable:hover,
#app-mount .${markupRtl} > .emoji.jumboable:hover {
transform: scale(${this.settings.hoverSize});
position: relative;
z-index: 1;
transition-delay: ${this.settings.delayAmount}s;
}
#app-mount .${messageGroup}:last-child .${message}:nth-last-child(2) .${markup} .emoji.jumboable:hover,
#app-mount .${messageGroup}:last-child .${message}:nth-last-child(2) .${markupRtl} .emoji.jumboable:hover {
transform: scale(${this.settings.hoverSize}) translateY(-35%);
}
`);
}
if (this.settings.alterBD) {
this.style.append(`
#app-mount .emote:not(.jumboable) {
height: ${this.settings.bdSize}px;
width: auto;
max-height: ${this.settings.bdSize}px !important;
transform: scale(1);
transition: transform ${this.settings.transitionSpeed}s;
transition-delay: 0s;
}
#app-mount .emote:not(.emoteshake):not(.emoteshake2):not(.emoteshake3):not(.jumboable):hover {
transform: scale(${this.settings.hoverSize});
position: relative;
z-index: 1;
transition-delay: ${this.settings.delayAmount}s;
}
#app-mount .${messageGroup}:last-child .${message}:nth-last-child(2) .emote:not(.emoteshake):not(.emoteshake2):not(.emoteshake3):not(.jumboable):hover {
transform: scale(${this.settings.hoverSize}) translateY(-35%);
}
`);
}
if (this.settings.alterLargeBD) {
this.style.append(`
#app-mount .emote.jumboable {
height: ${this.settings.largeBdSize}px;
width: auto;
max-height: ${this.settings.largeBdSize}px !important;
transform: scale(1);
transition: transform ${this.settings.transitionSpeed}s;
transition-delay: 0s;
}
#app-mount .emote.jumboable:not(.emoteshake):not(.emoteshake2):not(.emoteshake3):hover {
transform: scale(${this.settings.hoverSize});
position: relative;
z-index: 1;
transition-delay: ${this.settings.delayAmount}s;
}
#app-mount .${messageGroup}:last-child .${message}:nth-last-child(2) .emote.jumboable:not(.emoteshake2):not(.emoteshake3):hover {
transform: scale(${this.settings.hoverSize}) translateY(-35%);
}
`);
}
if (this.settings.alterReactions) {
this.style.append(`
#app-mount .${reaction} .emoji, .${reaction}.${reactionMe} .emoji {
height: ${this.settings.reactionSize}px;
width: auto;
}
#app-mount .${reaction} {
transition: transform ${this.settings.transitionSpeed}s;
transition-delay: 0s;
}
#app-mount .${reaction}:hover {
transform: scale(${this.settings.reactionHoverSize}) !important;
z-index: 1000;
transition-delay: ${this.settings.delayAmount}s;
}
`);
}
}
stop() {
if (this.style) this.style.destroy();
}
}
================================================
FILE: Bruh.plugin.js
================================================
/**
* @name Bruh
* @invite yNqzuJa
* @authorLink https://github.com/Metalloriff
* @donate https://www.paypal.me/israelboone
* @website https://metalloriff.github.io/toms-discord-stuff/
* @source https://github.com/Metalloriff/BetterDiscordPlugins/blob/master/Bruh.plugin.js
*/
module.exports = (() => {
const config =
{
info: {
name: "Bruh",
authors: [{
name: "Metalloriff",
discord_id: "264163473179672576",
github_username: "metalloriff",
twitter_username: "Metalloriff"
}],
version: "0.0.2",
description: "bruh",
github: "https://github.com/Metalloriff/BetterDiscordPlugins/blob/master/Bruh.plugin.js",
github_raw: "https://raw.githubusercontent.com/Metalloriff/BetterDiscordPlugins/master/Bruh.plugin.js"
},
defaultConfig: [{
id: "general",
name: "general settings",
type: "category",
collapsible: true,
shown: false,
settings: [{
id: "onlyCur",
name: "Current channel only",
note: "When this is enabled, the bruh sound effect will only play when a bruh is found in the selected channel.",
type: "switch",
value: true
}, {
id: "delay",
name: "Delay between each bruh (ms)",
note: "The amount of milliseconds to wait between each bruh when multiple bruhs are found within the same message.",
type: "slider",
value: 200,
min: 10,
max: 1000,
renderValue: v => Math.round(v) + "ms"
}]
}]
};
return !global.ZeresPluginLibrary ? class {
constructor() { this._config = config; }
getName = () => config.info.name;
getAuthor = () => config.info.description;
getVersion = () => 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 (err, res, body) => {
if (err) 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) => { try {
const {
DiscordModules: { Dispatcher, SelectedChannelStore }
} = Api;
const audio = new Audio();
return class Bruh extends Plugin {
constructor() {
super();
}
getSettingsPanel() {
return this.buildSettingsPanel().getElement();
}
onStart() {
Dispatcher.subscribe("MESSAGE_CREATE", this.messageEvent);
}
messageEvent = async ({ channelId, message, optimistic }) => {
if (this.settings.general.onlyCur && channelId != SelectedChannelStore.getChannelId())
return;
if (!optimistic) {
const count = (message.content.match(/bruh/gmi) || []).length;
for (let i = 0; i < count; i++) {
this.playBruh();
await new Promise(r => setTimeout(r, this.settings.general.delay));
}
}
};
playBruh() {
audio.src = "https://www.myinstants.com/media/sounds/movie_1.mp3";
audio.play();
}
onStop() {
Dispatcher.unsubscribe("MESSAGE_CREATE", this.messageEvent);
}
}
} catch (e) { console.error(e); }};
return plugin(Plugin, Api);
})(global.ZeresPluginLibrary.buildPlugin(config));
})();
================================================
FILE: CustomJs.plugin.js
================================================
//META{"name":"CustomJs","website":"https://metalloriff.github.io/toms-discord-stuff/","source":"https://github.com/Metalloriff/BetterDiscordPlugins/blob/master/CustomJs.plugin.js"}*//
class CustomJs {
getName() { return "CustomJs"; }
getDescription() { return "Allows you to specify a custom JavaScript file similar to custom CSS."; }
getVersion() { return "0.0.1"; }
getAuthor() { return "Metalloriff"; }
getChanges() {
return {
};
}
load() {}
start() {
let libLoadedEvent = () => {
try{ this.onLibLoaded(); }
catch(err) { console.error(this.getName(), "fatal error, plugin could not be started!", err); try { this.stop(); } catch(err) { console.error(this.getName() + ".stop()", err); } }
};
let lib = document.getElementById("NeatoBurritoLibrary");
if(!lib) {
lib = document.createElement("script");
lib.id = "NeatoBurritoLibrary";
lib.type = "text/javascript";
lib.src = "https://rawgit.com/Metalloriff/BetterDiscordPlugins/master/Lib/NeatoBurritoLibrary.js";
document.head.appendChild(lib);
}
this.forceLoadTimeout = setTimeout(libLoadedEvent, 30000);
if(typeof window.NeatoLib !== "undefined") libLoadedEvent();
else lib.addEventListener("load", libLoadedEvent);
}
getSettingsPanel() {
const fs = require("fs");
setTimeout(() => {
NeatoLib.Settings.pushElement(NeatoLib.Settings.Elements.createNewTextField("Custom JS file path", this.settings.filepath, e => {
if(e.target.value && !fs.existsSync(e.target.value)) return NeatoLib.showToast("File does not exist", "error");
this.settings.filepath = e.target.value;
this.saveSettings();
}), this.getName());
NeatoLib.Settings.pushElement(NeatoLib.Settings.Elements.createButton("Browse", () => {
NeatoLib.browseForFile(filepath => {
if(!filepath) return NeatoLib.showToast("No file selected", "error");
this.settings.filepath = filepath;
this.saveSettings();
})
}), this.getName());
NeatoLib.Settings.pushElement(NeatoLib.Settings.Elements.createButton("Reload", () => this.reloadJs(), "float:right"), this.getName());
NeatoLib.Settings.pushElement(NeatoLib.DOM.createElement({ innerHTML : `
Example #1
return new class {
start() {
console.log("start");
document.addEventListener("click", this.clickEvent = e => this.onClick(e));
}
onClick(e) {
console.log("clicked on", e.target);
}
stop() {
document.removeEventListener("click", this.clickEvent);
console.log("stop");
}
onSwitch() {
console.log(NeatoLib.getSelectedTextChannel());
}
}
`, style : "color:white" }, { type : "p" }), this.getName());
NeatoLib.Settings.pushElement(NeatoLib.DOM.createElement({ innerHTML : `
Example #2
return {
clickEvent : function(e) {
console.log("clicked on", e.target);
},
start : function() {
console.log("start");
document.addEventListener("click", this.clickEvent);
},
stop : function() {
document.removeEventListener("click", this.clickEvent);
console.log("stop");
},
onSwitch() {
console.log(NeatoLib.getSelectedTextChannel());
}
}
`, style : "color:white" }, { type : "p" }), this.getName());
NeatoLib.Settings.pushElement(NeatoLib.DOM.createElement({ innerHTML : `
Events and variables
start() - Called when the script is loaded. Use this for initialization.
stop() - Called before the script is stopped. Use this for de-initialization.
onSwitch() - Called when switching servers and channels.
loader - This plugin object. Example usage: loader.reloadJs()
`, style : "color:white" }, { type : "p" }), this.getName());
NeatoLib.Settings.pushElement(NeatoLib.Settings.Elements.createHint("Note: My library, 'NeatoLib', is included. You can use this for various things in your script. As of now, there are no docs for it, but you can enter 'NeatoLib' into the console (Ctrl/Cmd + Shift + I) to view all of its functions."), this.getName());
NeatoLib.Settings.pushChangelogElements(this);
}, 0);
return NeatoLib.Settings.Elements.pluginNameLabel(this.getName());
}
saveSettings() {
this.rewatch();
NeatoLib.Settings.save(this);
}
onLibLoaded() {
this.settings = NeatoLib.Settings.load(this, {
displayUpdateNotes : true,
filepath : ""
});
NeatoLib.Updates.check(this);
//if(this.settings.displayUpdateNotes) NeatoLib.Changelog.compareVersions(this.getName(), this.getChanges());
NeatoLib.Events.onPluginLoaded(this);
this.reloadJs();
this.rewatch();
NeatoLib.Events.attach("switch", this.switchEvent = () => {
if(this.script && typeof this.script.onSwitch == "function") {
try { this.script.onSwitch(); }
catch(err) {
console.error(err);
NeatoLib.showToast("[Custom JS]: error in onSwitch(), check the console for more details.");
}
}
});
}
rewatch() {
const fs = require("fs");
if(this.watcher) this.watcher.close();
if(this.settings.filepath) this.watcher = fs.watch(this.settings.filepath, () => {
const current = fs.readFileSync(this.settings.filepath, "utf-8");
if(!current) return;
if(this.last != current) this.reloadJs();
});
}
reloadJs() {
if(this.script && typeof this.script.stop == "function") this.script.stop();
this.script = null;
const fs = require("fs");
if(!this.settings.filepath) return;
else if(!fs.existsSync(this.settings.filepath)) return NeatoLib.showToast("[Custom JS]: file not found", "error");
try {
const js = this.script = eval(`(function(){${this.last = fs.readFileSync(this.settings.filepath, "utf-8")}})();`);
js.loader = this;
try { if(typeof js.start == "function") js.start(); }
catch(err) {
console.error(err);
NeatoLib.showToast("[Custom JS]: error in start(), check the console for more details.");
}
} catch(err) {
console.error(err);
NeatoLib.showToast("[Custom JS]: error evaluating JS, " + err);
}
}
stop() {
if(this.script && typeof this.script.stop == "function") {
try { this.script.stop(); }
catch(err) {
console.error(err);
NeatoLib.showToast("[Custom JS]: error in stop(), check the console for more details.");
}
}
if(this.watcher) this.watcher.close();
NeatoLib.Events.detach("switch", this.switchEvent);
}
}
================================================
FILE: CustomizableAvatarDPI.plugin.js
================================================
//META{"name":"CustomizableAvatarDPI","website":"https://github.com/Metalloriff/BetterDiscordPlugins/blob/master/README.md","source":"https://github.com/Metalloriff/BetterDiscordPlugins/blob/master/CustomizableAvatarDPI.plugin.js"}*//
class CustomizableAvatarDPI {
getName() { return "Customizable Avatar DPI"; }
getDescription() { return "Allows you to change the DPI of user avatars, to reduce bluriness with themes that increase the size of them."; }
getVersion() { return "1.0.6"; }
getAuthor() { return "Metalloriff"; }
getChanges() {
return {
"1.0.6":
`
Just temporary fix until Metalloriff hopefully rewrites this plugin aroung christmas time
`
}
}
load() {}
start() {
let libLoadedEvent = () => {
try{ this.onLibLoaded(); }
catch(err) { console.error(this.getName(), "fatal error, plugin could not be started!", err); try { this.stop(); } catch(err) { console.error(this.getName() + ".stop()", err); } }
};
let lib = document.getElementById("NeatoBurritoLibrary");
if(!lib) {
lib = document.createElement("script");
lib.id = "NeatoBurritoLibrary";
lib.type = "text/javascript";
lib.src = "https://rawgit.com/Metalloriff/BetterDiscordPlugins/master/Lib/NeatoBurritoLibrary.js";
document.head.appendChild(lib);
}
this.forceLoadTimeout = setTimeout(libLoadedEvent, 30000);
if(typeof window.NeatoLib !== "undefined") libLoadedEvent();
else lib.addEventListener("load", libLoadedEvent);
}
saveSettings() {
NeatoLib.Settings.save(this);
}
getSettingsPanel() {
setTimeout(() => {
NeatoLib.Settings.pushElement(NeatoLib.Settings.Elements.createNewTextField("Popout avatar size", this.settings.popoutAvatarSize, e => {
this.settings.popoutAvatarSize = e.target.value;
this.saveSettings();
}), this.getName(), { tooltip : "User popouts" });
NeatoLib.Settings.pushElement(NeatoLib.Settings.Elements.createNewTextField("Other avatar size", this.settings.otherAvatarSize, e => {
this.settings.otherAvatarSize = e.target.value;
this.saveSettings();
}), this.getName(), { tooltip : "Everything else" });
NeatoLib.Settings.pushChangelogElements(this);
}, 0);
return NeatoLib.Settings.Elements.pluginNameLabel(this.getName());
}
onLibLoaded(){
NeatoLib.Updates.check(this);
this.settings = NeatoLib.Settings.load(this, {
popoutAvatarSize : 1024,
otherAvatarSize : 256,
displayUpdateNotes : true
});
this.appObserver = new MutationObserver(m => {
for(let i = 0; i < m.length; i++) {
if(!m[i].addedNodes.length) continue;
for(let a = 0; a < m[i].addedNodes.length; a++) {
let added = m[i].addedNodes[a];
if(!(added instanceof Element)) continue;
let imgs = added.getElementsByClassName(NeatoLib.getClass(["avatar", "mask", "pointer", "status"], "avatar"));
for(let img of imgs)
if(img) img.src = img.src.split("?size=")[0] + "?size=" + this.settings.otherAvatarSize;
let popouts = added.classList.contains(NeatoLib.getClass(["body", "footer", "popout", "title"], "popout")) ? [added] : added.getElementsByClassName(NeatoLib.getClass(["body", "footer", "popout", "title"], "popout"));
for(let popout of popouts){
let imgs = popout.getElementsByClassName(NeatoLib.getClass(["avatar", "mask", "pointer", "status"], "avatar"));
for(let img of imgs)
if(img) img.src = img.src.split("?size=")[0] + "?size=" + this.settings.popoutAvatarSize;
}
}
}
});
this.appObserver.observe(document.getElementById("app-mount"), { childList : true, subtree : true });
NeatoLib.Events.onPluginLoaded(this);
NeatoLib.Changelog.compareVersions(this.getName(), this.getChanges());
}
stop() {
this.appObserver.disconnect();
}
}
================================================
FILE: DetailedServerTooltips.plugin.js
================================================
//META{"name":"DetailedServerTooltips","website":"https://metalloriff.github.io/toms-discord-stuff/","source":"https://github.com/Metalloriff/BetterDiscordPlugins/blob/master/DetailedServerTooltips.plugin.js"}*//
class DetailedServerTooltips {
getName() { return "DetailedServerTooltips"; }
getDescription() { return "Displays a more detailed tooltip for servers similar to user popouts. Contains a larger image, owner's tag, date, time and days ago created, date, time and days ago joined, member count, channel count, role count, region, and whether or not the server is partnered."; }
getVersion() { return "0.3.14"; }
getAuthor() { return "Metalloriff"; }
getChanges() {
return {
"0.1.1": `
Added a creation date field.
`,
"0.2.3": `
Fixed tooltip color not changing with some themes.
Fixed the tooltip arrow being offsetted wrong when the tooltip was prevented from going off-screen.
Tooltip guild icons are now full res.
`,
"0.3.4": `
Fixed update notes.
Fixed incompatibility with Zerebos' DoNotTrack plugin. (If you still have issues with tooltips sticking with it, please let me know. I barely tested it.)
Added a minimal mode setting.
`,
"0.3.5": `
Fixed tooltip getting stuck with ServerFolders
`,
"0.3.6": `
Fixed tooltips getting stuck when switching from dm to a server.
`,
"0.3.8": `
Fixed tooltips not showing for servers inside of folders with DevilBro's ServerFolders plugin.
`
};
}
load() {}
start() {
const libLoadedEvent = () => {
try{
if(window.pluginCookie["DoNotTrack"] == true) setTimeout(() => this.onLibLoaded(), 2000);
else this.onLibLoaded();
}
catch(err) { console.error(this.getName(), "fatal error, plugin could not be started!", err); try { this.stop(); } catch(err) { console.error(this.getName() + ".stop()", err); } }
};
let lib = document.getElementById("NeatoBurritoLibrary");
if (!lib) {
lib = document.createElement("script");
lib.id = "NeatoBurritoLibrary";
lib.type = "text/javascript";
lib.src = "https://rawgit.com/Metalloriff/BetterDiscordPlugins/master/Lib/NeatoBurritoLibrary.js";
document.head.appendChild(lib);
}
this.forceLoadTimeout = setTimeout(libLoadedEvent, 30000);
if (typeof window.NeatoLib !== "undefined") libLoadedEvent();
else lib.addEventListener("load", libLoadedEvent);
}
get settingFields() {
return {
tooltipColor: { label: "Tooltip color", type: "color" },
displayDelay: { label: "Tooltip display delay", description: "(ms)", type: "int" },
preview: { type: "custom", html: `
` },
minimalMode: { label: "Minimal mode", type: "bool" },
minimalPreview: { type: "custom", html: `
` }
};
}
get defaultSettings() {
return {
displayUpdateNotes: true,
tooltipColor: "#7289da",
displayDelay: 500,
minimalMode: false
};
}
getSettingsPanel() {
return NeatoLib.Settings.createPanel(this);
}
saveSettings() {
this.applyCSS();
NeatoLib.Settings.save(this);
}
applyCSS() {
if (this.style) this.style.destroy();
this.style = NeatoLib.injectCSS(`
.dst-tooltip {
width: 225px;
max-width: 225px;
text-align: center;
background-color: ${this.settings.tooltipColor} !important;
color: white;
}
.dst-tooltip:after {
border-right-color: ${this.settings.tooltipColor} !important;
top: 25px !important;
}
.dst-tooltip-icon {
width: 200px;
height: 200px;
background-size: cover;
border-radius: 5px;
margin-top: 5px;
flex: 1;
}
.dst-tooltip-label {
color: white;
margin-top: 10px;
font-size: 15px;
}
.dst-min .dst-tooltip-icon{display:none}
.dst-min .dst-tooltip-label{font-size:13px}
.dst-tooltip.dst-min{max-width:200px}
`);
}
onLibLoaded() {
if (!NeatoLib.hasRequiredLibVersion(this, "0.8.20")) return;
NeatoLib.Settings.load(this);
NeatoLib.Updates.check(this);
if (this.settings.displayUpdateNotes) NeatoLib.Changelog.compareVersions(this.getName(), this.getChanges());
this.guildModule = NeatoLib.Modules.get("getGuild");
this.userModule = NeatoLib.Modules.get("getUser");
this.memberModule = NeatoLib.Modules.get("getMembers");
this.channelModule = NeatoLib.Modules.get("getChannel");
this.memberCountModule = NeatoLib.Modules.get("getMemberCount");
this.localUser = NeatoLib.getLocalUser();
this.owners = {};
this.applyCSS();
let tooltip, timeout;
this.dragGuild = () => {
this.mouseLeaveGuild();
let tooltips = document.getElementsByClassName("dst-tooltip");
for (let i = 0; i < tooltips.length; i++) {
if (tooltips[i].updateLoop) clearInterval(tooltips[i].updateLoop);
tooltips[i].remove();
}
};
this.mouseEnterGuild = e => {
timeout = setTimeout(() => {
tooltip = this.tooltip(((e.target.parentElement.href || e.target.href).match(/\d+/) || [])[0], e.target);
if (!tooltip) return;
let tt = document.getElementsByClassName(NeatoLib.getClass("tooltip"))[0];
tt.appendChild(tooltip);
tt.getElementsByClassName(NeatoLib.getClass("tooltip", "tooltipPointer"))[0].style.borderTopColor = this.settings.tooltipColor;
let bottomPos = parseFloat(tooltip.style.top) + tooltip.offsetHeight;
if (bottomPos > window.innerHeight) {
tooltip.style.top = (parseFloat(tooltip.style.top) - (bottomPos - window.innerHeight)) + "px";
tooltip.insertAdjacentHTML("afterbegin", ``);
}
var tooltipObserver = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
var nodes = Array.from(mutation.removedNodes);
var ownMatch = nodes.indexOf(tooltip) > -1;
var directMatch = nodes.indexOf(e.target) > -1;
var parentMatch = nodes.some(parent => parent.contains(e.target));
if (ownMatch || directMatch || parentMatch) {
tooltipObserver.disconnect();
tooltip.remove();
}
});
});
}, this.settings.displayDelay);
};
this.mouseLeaveGuild = () => {
clearTimeout(timeout);
if (tooltip) tooltip.remove();
};
this.switchEvent = () => this.applyToGuilds();
this.guildObserver = new MutationObserver(this.switchEvent);
this.guildObserver.observe(document.getElementsByClassName(NeatoLib.getClass("unreadMentionsBar", "scroller"))[0], { childList: true, subtree: true });
NeatoLib.Events.attach("switch", this.switchEvent);
this.applyToGuilds();
NeatoLib.Events.onPluginLoaded(this);
}
applyToGuilds(detach) {
const guilds = document.getElementsByClassName(NeatoLib.getClass("acronym", "wrapper"));
for (let i = 0; i < guilds.length; i++) {
let reactEvents = NeatoLib.ReactData.getEvents(guilds[i]);
guilds[i].parentElement.removeEventListener("dragstart", this.dragGuild);
guilds[i].parentElement.removeEventListener("dragend", this.dragGuild);
guilds[i].removeEventListener("mouseenter", this.mouseEnterGuild);
guilds[i].removeEventListener("mouseleave", this.mouseLeaveGuild);
if (detach) continue;
guilds[i].parentElement.addEventListener("dragstart", this.dragGuild);
guilds[i].parentElement.addEventListener("dragend", this.dragGuild);
guilds[i].addEventListener("mouseenter", this.mouseEnterGuild);
guilds[i].addEventListener("mouseleave", this.mouseLeaveGuild);
}
}
tooltip(guildId, element) {
if (!guildId || !element || element.getBoundingClientRect().width == 0) return;
let tooltip = document.createElement("div"),
guild = this.guildModule.getGuild(guildId),
owner = this.userModule.getUser(guild.ownerId);
tooltip.className = NeatoLib.getClass("tooltip") + " " + NeatoLib.getClass("tooltip", "tooltipRight") + " dst-tooltip";
if (this.settings.minimalMode) tooltip.classList.add("dst-min");
tooltip.style.top = "0";
tooltip.style.left = "0";
tooltip.style.position = "fixed";
let creationDate = NeatoLib.getSnowflakeCreationDate(guild.id);
var imgURL = guild.getIconURL(guild && guild.icon && guild.icon.startsWith('a_') ? 'gif' : 'webp');
tooltip.innerHTML = `${this.escapeHtml(guild.name)}
Owner: ${owner ? this.escapeHtml(owner.tag) : "unknown"}
Created at: ${creationDate.toLocaleDateString()}, ${creationDate.toLocaleTimeString()} (${Math.round(Math.abs(creationDate.getTime() - new Date().getTime()) / 86400000)} days ago)
${creationDate.toString() == guild.joinedAt.toString() ? "" : `Joined at: ${guild.joinedAt.toLocaleDateString()}, ${guild.joinedAt.toLocaleTimeString()} (${Math.round(Math.abs(guild.joinedAt.getTime() - new Date().getTime()) / 86400000)} days ago)
`}
${this.memberCountModule.getMemberCount(guildId)} members
${Object.values(this.channelModule.getChannels()).filter(c => c.guild_id == guildId).length} channels
${Object.keys(guild.roles).length} roles
Region: ${guild.region}
`;
if(!guild.getIconURL()) tooltip.find(".dst-tooltip-icon").outerHTML = "";
if (guild.features.has("PARTNERED")) tooltip.insertAdjacentHTML("beforeend", ``);
if(owner){
this.owners[guildId] = owner.tag;
}else{
NeatoLib.Modules.get("getAPIBaseURL").get(NeatoLib.Modules.get(["Permissions", "ActivityTypes", "StatusTypes"]).Endpoints.USER(guild.ownerId)).then(result => {
if(!result) return;
let res = JSON.parse(result.text);
this.owners[guildId] = res.username + "#" + res.discriminator;
});
}
let updateMemberCount = false;
if(this.memberCountModule.getMemberCount(guildId) < 500){
NeatoLib.Modules.get("requestMembers").requestMembers(guildId, "", 0);
updateMemberCount = true;
}
const self = setInterval(() => {
if (!Array.from(document.getElementsByClassName(NeatoLib.getClass("tooltip"))).includes(tooltip)) return clearInterval(self);
document.getElementById("dst-tooltip-owner-label").innerHTML = "Owner: " + (this.escapeHtml(this.owners[guildId] || "unknown"));
if(updateMemberCount) document.getElementById("dst-tooltip-member-count-label").innerText = this.memberModule.getMembers(guildId).length + " members";
}, 500);
tooltip.updateLoop = self;
return tooltip;
}
stop() {
let tooltips = document.getElementsByClassName("dst-tooltip");
for (let i = 0; i < tooltips.length; i++) {
if (tooltips[i].updateLoop) clearInterval(tooltips[i].updateLoop);
tooltips[i].remove();
}
this.applyToGuilds(true);
if (this.style) this.style.destroy();
NeatoLib.Events.detach("switch", this.switchEvent);
this.guildObserver.disconnect();
}
escapeHtml(txt){
return txt.replace(/&/g, "&")
.replace(//g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
}
================================================
FILE: DoubleClickVoiceChannels.plugin.js
================================================
/**
* @name DoubleClickVoiceChannels
* @invite yNqzuJa
* @authorLink https://github.com/Metalloriff
* @donate https://www.paypal.me/israelboone
* @website https://metalloriff.github.io/toms-discord-stuff/
* @source https://github.com/Metalloriff/BetterDiscordPlugins/blob/master/DoubleClickVoiceChannels.plugin.js
*/
module.exports = (() => {
const config =
{
info:
{
name: "DoubleClickVoiceChannels",
authors:
[
{
name: "Metalloriff",
discord_id: "264163473179672576",
github_username: "metalloriff",
twitter_username: "Metalloriff"
}
],
version: "2.0.4",
description: "Requires you to double click voice channels to join them.",
github: "https://github.com/Metalloriff/BetterDiscordPlugins/blob/master/DoubleClickVoiceChannels.plugin.js",
github_raw: "https://raw.githubusercontent.com/Metalloriff/BetterDiscordPlugins/master/DoubleClickVoiceChannels.plugin.js"
},
changelog:
[
{
title: "Patched",
type: "fixed",
items: ["Fixed again. Thanks cmd430 for the help!"]
}
]
};
return !global.ZeresPluginLibrary ? class {
constructor() { this._config = config; }
getName = () => config.info.name;
getAuthor = () => config.info.description;
getVersion = () => 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 (err, res, body) => {
if (err) 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 { WebpackModules, Patcher, Utilities, ReactComponents } = Api;
return class DoubleClickVoiceChannels extends Plugin {
constructor() {
super();
}
async showChangelog(footer) {
try { footer = (await WebpackModules.getByProps("getUser", "acceptAgreements").getUser("264163473179672576")).tag + " | https://discord.gg/yNqzuJa"; }
finally { super.showChangelog(footer); }
}
async onStart() {
const { component: ChannelItem } = await ReactComponents.getComponentByName("VoiceChannel", "*");
if (ChannelItem) {
Patcher.after(ChannelItem.prototype, "render", (r, _, el) => {
const children_type0 = Utilities.getNestedProp(el, "props.children.props.children.0.props.children.props.children");
const children_type1 = Utilities.getNestedProp(el, "props.children.0.props.children.props.children");
if (children_type0 || children_type1) {
const handleClick = (children) => {
const c = children();
const handler = c.props.children;
c.props.children = () => {
const h = handler({});
if (!h.props.connected) {
// for whatever reason, onDoubleClick stopped working, so here's a dumb workaround
const onClick = h.props.onClick;
let t = performance.now() - 200;
h.props.onClick = () => {
if (performance.now() - t < 200)
onClick();
t = performance.now();
};
}
return h;
};
return c;
};
if (children_type0) {
el.props.children.props.children[0].props.children.props.children = () => {
return handleClick(children_type0);
};
}
if (children_type1) {
el.props.children[0].props.children.props.children = () => {
return handleClick(children_type1);
};
};
}
else {
console.warn("DoubleClickVoiceChannel: Failed to get nested props!");
}
});
}
}
onStop() {
Patcher.unpatchAll();
}
}
};
return plugin(Plugin, Api);
})(global.ZeresPluginLibrary.buildPlugin(config));
})();
================================================
FILE: FormattableMessageCopier.plugin.js
================================================
//META{"name":"FormattableMessageCopier"}*//
class FormattableMessageCopier {
getSettingsPanel(){
let updateExample = () => {
let hints = document.getElementsByClassName("plugin-settings")[0].getElementsByTagName("p");
for(let i = 0; i < hints.length; i++) hints[i].style.color = `rgb(${this.settings.selectionColor})`;
let time = new Date().toLocaleTimeString("en-us"),
example = document.getElementById("mc-example"),
exampleMessages1 = [], exampleMessages2 = [];
for(let msg of ["Hello.", "These are some messages.", "This is a third message, with a reaction."]) {
exampleMessages1.push(this.settings.messageFormat
.split("$time").join(time)
.split("$messagetext").join(msg));
exampleMessages1.push(this.settings.reactionFormat
.split("$emoji").join("👌")
.split("$count").join("4") + this.settings.reactionFormat
.split("$emoji").join("💯")
.split("$count").join("20"));
exampleMessages2.push(this.settings.messageFormat
.split("$time").join(time)
.split("$messagetext").join("This message has an image attached to it."));
exampleMessages2.push(this.settings.attachmentFormat
.split("$filename").join("LUUL.jpg")
.split("$fileurl").join("https://i.imgur.com/cxWch9R.jpg"));
example.innerText = (this.settings.headerFormat
.split("$channel").join("#general")
.split("$selectionstarttime").join(time)
.split("$selectionendtime").join(time)
.split("$selectiondate").join(new Date().toLocaleDateString("en-us"))
+ "\n" + this.settings.groupFormat
.split("$time").join(time)
.split("$username").join("Metalloriff")
.split("$usertag").join("Metalloriff#2891")
.split("$jumplink").join("https://discordapp.com/channels/serverid/channelid/messageid")
.split("$message").join(exampleMessages1.join("\n"))
+ "\n" + this.settings.groupFormat
.split("$time").join(time)
.split("$username").join("Some Kid Named Nate")
.split("$usertag").join("Some Kid Named Nate#0000")
.split("$jumplink").join("https://discordapp.com/channels/serverid/channelid/messageid")
.split("$message").join(exampleMessages2.join("\n")))
.split("$newline").join("\n")
.split("$tab").join(" ");
}
};
setTimeout(() => {
NeatoLib.Settings.pushElement(NeatoLib.Settings.Elements.createNewTextField("Header format", this.settings.headerFormat, e => {
this.settings.headerFormat = e.target.value;
updateExample();
this.saveSettings();
}), this.getName());
NeatoLib.Settings.pushElement(NeatoLib.Settings.Elements.createHint(`
Header variables:\n\n
$channel - The name of the selected channel\n
$selectionstarttime - The timestamp of the first selected message\n
$selectionendtime - The timestamp of the last selected message\n
$selectiondate - The date of the selected messages\n
$newline - New line\n
$tab - Tab
`), this.getName());
NeatoLib.Settings.pushElement(NeatoLib.Settings.Elements.createNewTextField("Message group format", this.settings.groupFormat, e => {
this.settings.groupFormat = e.target.value;
updateExample();
this.saveSettings();
}), this.getName());
NeatoLib.Settings.pushElement(NeatoLib.Settings.Elements.createHint(`
$time - Message group timestamp\n
$username - Message group sender's username\n
$usertag - Message group sender's username and discriminator\n
$message - Formatted message\n
$newline - New line\n
$tab - Tab
`), this.getName());
NeatoLib.Settings.pushElement(NeatoLib.Settings.Elements.createNewTextField("Message format", this.settings.messageFormat, e => {
this.settings.messageFormat = e.target.value;
updateExample();
this.saveSettings();
}), this.getName());
NeatoLib.Settings.pushElement(NeatoLib.Settings.Elements.createHint(`
$time - Message timestamp\n
$jumplink - A link that redirects the user to the message, if they are in the server it was from, and have access to the channel\n
$messagetext - Message text\n
$newline - New line\n
$tab - Tab
`), this.getName());
NeatoLib.Settings.pushElement(NeatoLib.Settings.Elements.createNewTextField("Attachment format", this.settings.attachmentFormat, e => {
this.settings.attachmentFormat = e.target.value;
updateExample();
this.saveSettings();
}), this.getName());
NeatoLib.Settings.pushElement(NeatoLib.Settings.Elements.createHint(`
$filename - Name of the uploaded file\n
$fileurl - URL of the uploaded file\n
$newline - New line\n
$tab - Tab
`), this.getName());
NeatoLib.Settings.pushElement(NeatoLib.Settings.Elements.createNewTextField("Reaction format", this.settings.reactionFormat, e => {
this.settings.reactionFormat = e.target.value;
updateExample();
this.saveSettings();
}), this.getName());
NeatoLib.Settings.pushElement(NeatoLib.Settings.Elements.createHint(`
$emoji - Reaction's emoji\n
$count - Reaction count\n
$newline - New line\n
$tab - Tab
`), this.getName());
NeatoLib.Settings.pushElement(NeatoLib.Settings.Elements.createNewTextField("Selection color (R, G, B)", this.settings.selectionColor, e => {
this.settings.selectionColor = e.target.value;
updateExample();
this.saveSettings();
}), this.getName());
NeatoLib.Settings.pushHTML(`
`, this.getName());
let fields = document.getElementsByClassName("plugin-settings")[0].getElementsByClassName("input-2YozMi");
updateExample();
}, 0);
return NeatoLib.Settings.Elements.pluginNameLabel(this.getName());
}
saveSettings(){
NeatoLib.Settings.save(this);
}
getName() { return "Formattable Message Copier"; }
getDescription() { return "Allows you to select messages in a chat to copy them in a customizable format. Double click the top of a message group to select it, then shift click to the next point to select all message groups between, or ctrl click another message group to append a single group to the selection."; }
getVersion() { return "0.1.4"; }
getAuthor() { return "Metalloriff"; }
load() {}
start() {
let libLoadedEvent = () => {
try{ this.onLibLoaded(); }
catch(err) { console.error(this.getName(), "fatal error, plugin could not be started!", err); try { this.stop(); } catch(err) { console.error(this.getName() + ".stop()", err); } }
};
let lib = document.getElementById("NeatoBurritoLibrary");
if(lib == undefined) {
lib = document.createElement("script");
lib.setAttribute("id", "NeatoBurritoLibrary");
lib.setAttribute("type", "text/javascript");
lib.setAttribute("src", "https://rawgit.com/Metalloriff/BetterDiscordPlugins/master/Lib/NeatoBurritoLibrary.js");
document.head.appendChild(lib);
}
if(typeof window.NeatoLib !== "undefined") libLoadedEvent();
else lib.addEventListener("load", libLoadedEvent);
}
onLibLoaded(){
this.settings = NeatoLib.Settings.load(this, {
headerFormat : "messages in $channel since $selectionstarttime, until $selectionendtime, on $selectiondate$newline$newline",
groupFormat : "[$time] $username:$newline$newline$message$newline$newline",
messageFormat : "$tab$messagetext",
attachmentFormat : "[$filename]: <$fileurl>",
reactionFormat : " [ $emoji : $count ] ",
selectionColor : "114, 137, 218"
});
NeatoLib.Updates.check(this);
NeatoLib.Events.onPluginLoaded(this);
this.userModule = NeatoLib.Modules.get("getUser");
this.channelModule = NeatoLib.Modules.get("getChannel");
this.selection = [];
this.applyCSS();
this.clickEvent = e => this.onClick(e);
document.addEventListener("dblclick", this.clickEvent);
document.addEventListener("click", this.clickEvent);
}
applyCSS(){
if(this.styles) this.styles.destroy();
this.styles = NeatoLib.injectCSS(`
.mc-message-selected {
margin-left: 0px !important;
margin-right: 0px !important;
padding-left: 20px !important;
padding-right: 6px !important;
border: 2px solid rgb(${this.settings.selectionColor}) !important;
background-color: rgba(${this.settings.selectionColor}, 0.5) !important;
}
#mc-copy-button {
background-color: rgb(${this.settings.selectionColor}) !important;
}
`);
}
onClick(e){
if(!e.target.classList || (!e.target.classList.contains("message-group") && e.target.className != "old-h2")) return;
let messageGroup;
if(e.target.className.includes("message-group")) messageGroup = e.target;
else messageGroup = $(e.target).parents(".message-group")[0];
if(messageGroup) {
if(e.type == "dblclick" || (e.type == "click" && e.ctrlKey)){
messageGroup.classList.add("mc-message-selected");
this.selection.push(messageGroup);
}
if(e.type == "click" && !e.ctrlKey && !e.shiftKey) this.clearSelection();
if(e.type == "click" && e.shiftKey && this.selection.length > 0 && messageGroup != this.selection[0]) {
if(document.selection) document.selection.empty();
else if(window.getSelection) window.getSelection().removeAllRanges();
let messages = document.getElementsByClassName("message-group"), selecting = false, newSelection = [];
let select = (i, reverse) => {
if(messages[i] == (reverse ? messageGroup : this.selection[0])) selecting = true;
if(selecting) {
newSelection.push(messages[i]);
messages[i].classList.add("mc-message-selected");
}
if(messages[i] == (reverse ? this.selection[0] : messageGroup)) selecting = false;
};
if(this.selection.length > 1) {
for(let i = 0; i < this.selection.length; i++) this.selection[i].classList.remove("mc-message-selected");
this.selection.splice(1, this.selection.length);
}
for(let i = 0; i < messages.length; i++) select(i);
if(selecting) {
selecting = false;
newSelection = [];
for(let i = 0; i < messages.length; i++) {
messages[i].classList.remove("mc-message-selected");
select(i, true);
}
}
this.selection = newSelection;
}
}
if(document.getElementById("mc-copy-button")) document.getElementById("mc-copy-button").remove();
if(this.selection.length > 0){
this.selection[this.selection.length - 1].insertAdjacentHTML("beforeend", `Copy Selection (${this.selection.length})
`);
document.getElementById("mc-copy-button").addEventListener("click", () => this.copySelection());
}
}
clearSelection(){
for(let i = 0; i < this.selection.length; i++) this.selection[i].classList.remove("mc-message-selected");
this.selection = [];
if(document.getElementById("mc-copy-button")) document.getElementById("mc-copy-button").remove();
}
copySelection(){
let formatted = [],
firstMessage = NeatoLib.ReactData.getProps(this.selection[0]),
lastGroup = NeatoLib.ReactData.getProps(this.selection[this.selection.length - 1]).messages,
lastMessageTimestamp = lastGroup[lastGroup.length - 1].timestamp,
channelName = firstMessage.channel.name;
if(this.settings.headerFormat != "" && this.selection.length > 1){
formatted.push(this.settings.headerFormat
.split("$channel").join(channelName == "" ? "DM" : ("#" + channelName))
.split("$selectionstarttime").join(firstMessage.messages[0].timestamp.toDate().toLocaleTimeString("en-us"))
.split("$selectionendtime").join(lastMessageTimestamp.toDate().toLocaleTimeString("en-us"))
.split("$selectiondate").join(lastMessageTimestamp.toDate().toLocaleDateString("en-us"))
.split("$newline").join("\n")
.split("$tab").join("\t")
);
}
for(let i = 0; i < this.selection.length; i++) {
let props = NeatoLib.ReactData.getProps(this.selection[i]),
time = props.messages[0].timestamp.toDate().toLocaleTimeString("en-us"),
messages = [];
for(let pi = 0; pi < props.messages.length; pi++) {
let message = props.messages[pi];
for(let mi = 0; mi < message.mentions.length; mi++) message.content = message.content.replace("<@" + message.mentions[i] + ">", "`@" + this.userModule.getUser(message.mentions[i]).tag + "`");
let content = this.settings.messageFormat
.split("$time").join(message.timestamp.toDate().toLocaleTimeString("en-us"))
.split("$newline").join("\n")
.split("$tab").join("\t")
.split("$messagetext").join(message.content);
if(content != "") messages.push(content);
let reactions = [];
for(let ri = 0; ri < message.reactions.length; ri++) {
reactions.push(this.settings.reactionFormat
.split("$emoji").join(message.reactions[ri].emoji.name)
.split("$count").join(message.reactions[ri].count)
.split("$newline").join("\n")
.split("$tab").join("\t")
);
}
if(reactions.length > 0) messages.push(reactions.join(""));
for(let ai = 0; ai < message.attachments.length; ai++) {
messages.push(this.settings.attachmentFormat
.split("$filename").join(message.attachments[ai].filename)
.split("$fileurl").join(message.attachments[ai].url)
.split("$newline").join("\n")
.split("$tab").join("\t")
);
}
}
formatted.push(this.settings.groupFormat
.split("$time").join(time)
.split("$username").join(props.messages[0].author.username)
.split("$usertag").join(props.messages[0].author.tag)
.split("$jumplink").join(props.channel.guild_id ? `` : "")
.split("$message").join(messages.join("\n"))
.split("$newline").join("\n")
.split("$tab").join("\t")
);
}
this.clearSelection();
formatted = formatted.join("\n");
NeatoLib.Modules.get("copy").copy(formatted);
NeatoLib.showToast(formatted.length + " characters copied!", null, { color : `rgb(${this.settings.selectionColor})` });
}
stop() {
document.removeEventListener("dblclick", this.clickEvent);
document.removeEventListener("click", this.clickEvent);
this.styles.destroy();
}
}
================================================
FILE: GuildAndFriendRemovalAlerts/GuildAndFriendRemovalAlerts.plugin.js
================================================
/**
* @name GuildAndFriendRemovalAlerts
* @version 3.2.1
* @description Displays alerts when you are kicked/banned from a server, a server is deleted, and when a friend removes you.
* @author Metalloriff
* @source https://github.com/Metalloriff/BetterDiscordPlugins/GuildAndFriendRemovalAlerts
* @updateUrl https://raw.githubusercontent.com/Metalloriff/BetterDiscordPlugins/master/GuildAndFriendRemovalAlerts/GuildAndFriendRemovalAlerts.plugin.js
* @website https://metalloriff.github.io/toms-discord-stuff/#/
* @donate https://paypal.me/israelboone
* @invite yNqzuJa
*/
/*@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": "GuildAndFriendRemovalAlerts",
"version": "3.2.1",
"description": "Displays alerts when you are kicked/banned from a server, a server is deleted, and when a friend removes you.",
"authors": [{
"name": "Metalloriff",
"discord_id": "264163473179672576",
"github_username": "Metalloriff"
}],
"github": "https://github.com/Metalloriff/BetterDiscordPlugins/GuildAndFriendRemovalAlerts",
"github_raw": "https://raw.githubusercontent.com/Metalloriff/BetterDiscordPlugins/master/GuildAndFriendRemovalAlerts/GuildAndFriendRemovalAlerts.plugin.js",
"website": "https://metalloriff.github.io/toms-discord-stuff/#/",
"donate": "https://paypal.me/israelboone",
"invite": "yNqzuJa"
},
"changelog": [{
"title": "3.0 rewrite",
"type": "fixed",
"items": [
"This plugin has been rewritten. Functionality is simliar, and settings and data should still be valid.",
"If you experience any bugs, please contact me."
]
}],
"build": {
"zlibrary": true,
"copy": true,
"production": false,
"scssHash": false,
"alias": {
"components": "components/index.js"
},
"release": {
"source": true,
"readme": true,
"public": true,
"contributors": null
}
}
};
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.findModuleByProps('DropdownSizes'))
},
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('Text'))
},
get 'Card'() {
return ___createMemoize___(this, 'Card', () => BdApi.findModuleByDisplayName('Card'))
}
},
'@discord/modules': {
get 'Dispatcher'() {
return ___createMemoize___(this, 'Dispatcher', () => BdApi.findModuleByProps('dispatch', 'subscribe'))
},
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__ = {
86: (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__(246);
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, ".GuildAndFriendRemovalAlerts-item-item{display:flex;margin:5px;padding:10px;cursor:pointer;border-radius:5px}.GuildAndFriendRemovalAlerts-item-item:hover{background-color:var(--background-modifier-hover)}.GuildAndFriendRemovalAlerts-item-item .GuildAndFriendRemovalAlerts-item-image{width:60px;height:60px;border-radius:5px}.GuildAndFriendRemovalAlerts-item-item .GuildAndFriendRemovalAlerts-item-inner{display:flex;flex-direction:column;padding:5px;margin-left:10px}.GuildAndFriendRemovalAlerts-item-item .GuildAndFriendRemovalAlerts-item-inner .GuildAndFriendRemovalAlerts-item-title{margin:auto 0;max-width:400px;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;font-weight:bolder;font-size:1.1rem}.GuildAndFriendRemovalAlerts-item-item .GuildAndFriendRemovalAlerts-item-inner .GuildAndFriendRemovalAlerts-item-description{margin:auto 0}", ""]);
___CSS_LOADER_EXPORT___.locals = {
item: "GuildAndFriendRemovalAlerts-item-item",
image: "GuildAndFriendRemovalAlerts-item-image",
inner: "GuildAndFriendRemovalAlerts-item-inner",
title: "GuildAndFriendRemovalAlerts-item-title",
description: "GuildAndFriendRemovalAlerts-item-description"
};
StyleLoader.append(module.id, ___CSS_LOADER_EXPORT___.toString());
const __WEBPACK_DEFAULT_EXPORT__ = Object.assign(___CSS_LOADER_EXPORT___, ___CSS_LOADER_EXPORT___.locals);
},
377: (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__(246);
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, ".GuildAndFriendRemovalAlerts-styles-modal{color:var(--header-primary)}.GuildAndFriendRemovalAlerts-styles-modal .GuildAndFriendRemovalAlerts-styles-itemContainer .GuildAndFriendRemovalAlerts-styles-title{margin:20px;font-size:1.2rem;font-weight:bolder}.GuildAndFriendRemovalAlerts-styles-modal .GuildAndFriendRemovalAlerts-styles-nothingHere{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);font-size:2rem;font-weight:bolder;opacity:.5}.GuildAndFriendRemovalAlerts-styles-modal .GuildAndFriendRemovalAlerts-styles-clearButton{margin-top:20px;background-color:rgba(237,66,69,.1);color:#f66;border-radius:5px;padding:15px 10px;text-align:center;cursor:pointer}.GuildAndFriendRemovalAlerts-styles-floatRight{margin-left:auto}", ""]);
___CSS_LOADER_EXPORT___.locals = {
modal: "GuildAndFriendRemovalAlerts-styles-modal",
itemContainer: "GuildAndFriendRemovalAlerts-styles-itemContainer",
title: "GuildAndFriendRemovalAlerts-styles-title",
nothingHere: "GuildAndFriendRemovalAlerts-styles-nothingHere",
clearButton: "GuildAndFriendRemovalAlerts-styles-clearButton",
floatRight: "GuildAndFriendRemovalAlerts-styles-floatRight"
};
StyleLoader.append(module.id, ___CSS_LOADER_EXPORT___.toString());
const __WEBPACK_DEFAULT_EXPORT__ = Object.assign(___CSS_LOADER_EXPORT___, ___CSS_LOADER_EXPORT___.locals);
},
234: (__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
__webpack_require__.d(__webpack_exports__, {
default: () => GuildAndFriendRemovalAlerts
});
const external_BasePlugin_namespaceObject = BasePlugin;
var external_BasePlugin_default = __webpack_require__.n(external_BasePlugin_namespaceObject);
const external_PluginApi_namespaceObject = PluginApi;
var external_BdApi_React_ = __webpack_require__(832);
var external_BdApi_React_default = __webpack_require__.n(external_BdApi_React_);
var React = __webpack_require__(832);
function _extends() {
_extends = Object.assign || function(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source)
if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
}
return target;
};
return _extends.apply(this, arguments);
}
const createUpdateWrapper = (Component, valueProp = "value", changeProp = "onChange", valueIndex = 0) => props => {
const [value, setValue] = React.useState(props[valueProp]);
return React.createElement(Component, _extends({}, props, {
[valueProp]: value,
[changeProp]: (...args) => {
const value = args[valueIndex];
if ("function" === typeof props[changeProp]) props[changeProp](value);
setValue(value);
}
}));
};
const hooks_createUpdateWrapper = createUpdateWrapper;
const package_namespaceObject = JSON.parse('{"um":{"u2":"GuildAndFriendRemovalAlerts"}}');
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 Settings {}
_defineProperty(Settings, "settings", external_PluginApi_namespaceObject.PluginUtilities.loadSettings(package_namespaceObject.um.u2, {}));
_defineProperty(Settings, "get", ((key, defaultValue) => Settings.settings[key] ?? defaultValue));
_defineProperty(Settings, "set", ((key, value) => {
Settings.settings[key] = value;
Settings.save();
}));
_defineProperty(Settings, "save", (() => external_PluginApi_namespaceObject.PluginUtilities.saveSettings(package_namespaceObject.um.u2, Settings.settings)));
const Switch = hooks_createUpdateWrapper(external_PluginApi_namespaceObject.WebpackModules.getByDisplayName("SwitchItem"));
function SettingsPanel() {
return external_BdApi_React_default().createElement("div", {
className: "gafSettingsPanel"
}, external_BdApi_React_default().createElement(Switch, {
note: "Whether or not to automatically show the modal when a guild/friend is removed.",
value: Settings.get("showModal", true),
onChange: value => Settings.set("showModal", value)
}, "Auto Show Modal"), external_BdApi_React_default().createElement(Switch, {
note: "Whether or not to show desktop notifications when a guild/friend is removed.",
value: Settings.get("showDeskNotifs", false),
onChange: value => Settings.set("showDeskNotifs", value)
}, "Show Desktop Notifications"));
}
const stores_namespaceObject = Modules["@discord/stores"];
var stores_default = __webpack_require__.n(stores_namespaceObject);
const modal_namespaceObject = Modules["@discord/modal"];
const external_StyleLoader_namespaceObject = StyleLoader;
var external_StyleLoader_default = __webpack_require__.n(external_StyleLoader_namespaceObject);
var styles = __webpack_require__(377);
var item = __webpack_require__(86);
const utils_namespaceObject = Modules["@discord/utils"];
const PrivateModule = external_PluginApi_namespaceObject.WebpackModules.getByProps("openPrivateChannel");
function Item({
title,
description,
icon,
clickId,
closeModal
}) {
return external_BdApi_React_default().createElement("div", {
className: (0, utils_namespaceObject.joinClassNames)(item.Z.item, item.Z.userItem),
onClick: () => {
PrivateModule.openPrivateChannel(clickId);
closeModal();
}
}, external_BdApi_React_default().createElement("img", {
className: item.Z.image,
src: icon || "/assets/485a854d5171c8dc98088041626e6fea.png",
alt: "image"
}), external_BdApi_React_default().createElement("div", {
className: item.Z.inner
}, external_BdApi_React_default().createElement("div", {
className: item.Z.title
}, title), description?.length ? external_BdApi_React_default().createElement("div", {
className: item.Z.description
}, description) : null));
}
const contextmenu_namespaceObject = Modules["@discord/contextmenu"];
var contextmenu_default = __webpack_require__.n(contextmenu_namespaceObject);
const constants_namespaceObject = Modules["@discord/constants"];
const modules_namespaceObject = Modules["@discord/modules"];
var GuildAndFriendRemovalAlerts_React = __webpack_require__(832);
function GuildAndFriendRemovalAlerts_extends() {
GuildAndFriendRemovalAlerts_extends = Object.assign || function(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source)
if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
}
return target;
};
return GuildAndFriendRemovalAlerts_extends.apply(this, arguments);
}
function GuildAndFriendRemovalAlerts_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 {
getFriendIDs
} = external_PluginApi_namespaceObject.WebpackModules.getByProps("getFriendIDs");
const HomeButton = external_PluginApi_namespaceObject.WebpackModules.getByProps("HomeButton");
const events = ["GUILD_CREATE", "GUILD_DELETE", "GUILD_UPDATE", "RELATIONSHIP_ADD", "RELATIONSHIP_REMOVE", "RELATIONSHIP_UPDATE", "FRIEND_REQUEST_ACCEPTED"];
class GuildAndFriendRemovalAlerts extends(external_BasePlugin_default()) {
constructor(...args) {
super(...args);
GuildAndFriendRemovalAlerts_defineProperty(this, "history", {
guilds: Settings.get("removedGuildHistory", []),
friends: Settings.get("removedFriendHistory", []),
update: () => {
Settings.set("removedGuildHistory", this.history.guilds);
Settings.set("removedFriendHistory", this.history.friends);
},
clear: () => {
Object.assign(this.history, {
guilds: [],
friends: []
});
this.history.update();
}
});
GuildAndFriendRemovalAlerts_defineProperty(this, "snapshots", {
guilds: Settings.get("guildsSnapshot", []),
friends: Settings.get("friendsSnapshot", []),
update: ({
guilds,
friends
}) => {
Settings.set("guildsSnapshot", this.snapshots.guilds = guilds);
Settings.set("friendsSnapshot", this.snapshots.friends = friends);
}
});
GuildAndFriendRemovalAlerts_defineProperty(this, "getSettingsPanel", (() => GuildAndFriendRemovalAlerts_React.createElement(SettingsPanel, null)));
GuildAndFriendRemovalAlerts_defineProperty(this, "main", (() => {
console.log("main()");
const guilds = Object.keys(stores_default().Guilds.getGuilds()).map((guildId => this.serializeGuild(guildId)));
const friends = getFriendIDs().map((uid => this.serializeUser(uid)));
const removedGuilds = this.snapshots.guilds.filter((snapshot => !guilds.some((guild => snapshot.id === guild.id))));
const removedFriends = this.snapshots.friends.filter((snapshot => !friends.some((friend => snapshot.id === friend.id))));
if (removedGuilds.length || removedFriends.length) {
if (Settings.get("showModal", true)) this.openModal(removedGuilds, removedFriends);
removedGuilds.forEach((guild => this.history.guilds.unshift(guild)));
removedFriends.forEach((friend => this.history.friends.unshift(friend)));
if (Settings.get("showDeskNotifs", false)) {
removedGuilds.forEach((guild => new Notification(guild.name, {
silent: true,
body: "Server removed",
icon: guild.iconUrl
})));
removedFriends.forEach((friend => new Notification(friend.name, {
silent: true,
body: "Friend removed",
icon: friend.avatarUrl
})));
}
}
if (guilds.length !== this.snapshots.guilds.length || friends.length !== this.snapshots.friends.length) {
this.history.update();
this.snapshots.update({
guilds,
friends
});
console.log("update");
}
}));
}
onStart() {
const PatchedHomeButton = ({
originalType,
...props
}) => {
const returnValue = Reflect.apply(originalType, this, [props]);
try {
returnValue.props.onContextMenu = e => {
(0, contextmenu_namespaceObject.openContextMenu)(e, (() => GuildAndFriendRemovalAlerts_React.createElement(contextmenu_default().default, {
navId: package_namespaceObject.um.u2,
onClose: contextmenu_namespaceObject.closeContextMenu
}, GuildAndFriendRemovalAlerts_React.createElement(contextmenu_namespaceObject.MenuItem, {
label: "View GFR Logs",
action: () => this.openModal(this.history.guilds, this.history.friends, true),
id: package_namespaceObject.um.u2 + "-logs"
}), GuildAndFriendRemovalAlerts_React.createElement(contextmenu_namespaceObject.MenuItem, {
label: "View All Guilds and Friends",
action: () => this.openModal(),
id: package_namespaceObject.um.u2 + "-view-all"
}))));
};
} catch (error) {
external_PluginApi_namespaceObject.Logger.error("Error in DefaultHomeButton patch:", error);
}
return returnValue;
};
external_PluginApi_namespaceObject.Patcher.after(HomeButton, "HomeButton", ((_, __, component) => {
const originalType = component.type;
component.type = PatchedHomeButton;
Object.assign(component.props, {
originalType
});
}));
external_StyleLoader_default().inject();
events.forEach((eventType => modules_namespaceObject.Dispatcher.subscribe(eventType, this.main)));
this.main();
}
serializeGuild(guildId) {
const serialized = {
id: guildId,
invalid: true,
name: "Unknown Guild",
iconUrl: "/assets/1531b79c2f2927945582023e1edaaa11.png"
};
try {
const guild = stores_default().Guilds.getGuild(guildId);
if (guild) Object.assign(serialized, {
invalid: false,
name: guild.name,
ownerId: guild.ownerId,
iconUrl: "function" === typeof guild.getIconURL ? guild.getIconURL("webp") : serialized.iconUrl
});
} finally {}
return serialized;
}
serializeUser(userId) {
const serialized = {
id: userId,
invalid: true,
tag: "Unknown User",
avatarURL: "/assets/1cbd08c76f8af6dddce02c5138971129.png"
};
try {
const user = stores_default().Users.getUser(userId);
if (user) Object.assign(serialized, {
invalid: false,
tag: user.tag,
avatarUrl: "function" === typeof user.getAvatarURL ? user.getAvatarURL("webp") : serialized.avatarUrl
});
} finally {}
return serialized;
}
openModal(guilds, friends, showClearButton = false) {
if (!guilds && !friends) {
guilds = this.snapshots.guilds;
friends = this.snapshots.friends;
}
const clearLogs = () => BdApi.showConfirmationModal("Are you sure?", "Do you really want to clear the logs?\nThis action cannot be undone.", {
danger: true,
onConfirm: () => {
this.history.clear();
(0, modal_namespaceObject.closeModal)(modalId);
},
confirmText: "Clear"
});
const modalId = (0, modal_namespaceObject.openModal)((props => GuildAndFriendRemovalAlerts_React.createElement(modal_namespaceObject.ModalRoot, GuildAndFriendRemovalAlerts_extends({}, props, {
size: "large",
className: styles.Z.modal
}), GuildAndFriendRemovalAlerts_React.createElement(modal_namespaceObject.ModalHeader, null, "Guild And Friend Removal Alerts ", GuildAndFriendRemovalAlerts_React.createElement(modal_namespaceObject.ModalCloseButton, {
className: styles.Z.floatRight,
onClick: props.onClose
})), GuildAndFriendRemovalAlerts_React.createElement(modal_namespaceObject.ModalContent, props, guilds?.length || friends?.length ? GuildAndFriendRemovalAlerts_React.createElement(GuildAndFriendRemovalAlerts_React.Fragment, null, showClearButton ? GuildAndFriendRemovalAlerts_React.createElement("div", {
className: styles.Z.clearButton,
onClick: clearLogs
}, "Clear Logs") : null, guilds?.length ? GuildAndFriendRemovalAlerts_React.createElement("div", {
className: styles.Z.itemContainer
}, GuildAndFriendRemovalAlerts_React.createElement("div", {
className: styles.Z.title
}, "Guilds - ", GuildAndFriendRemovalAlerts_React.createElement("span", {
style: {
color: "var(--control-brand-foreground-new)"
}
}, guilds.length)), GuildAndFriendRemovalAlerts_React.createElement("div", {
className: styles.Z.items
}, guilds.map(((guild, i) => GuildAndFriendRemovalAlerts_React.createElement(Item, {
key: i,
title: guild.name,
description: "Owner ID - " + guild.ownerId,
icon: guild.iconUrl,
clickId: guild.ownerId,
closeModal: props.onClose
}))))) : null, friends?.length ? GuildAndFriendRemovalAlerts_React.createElement("div", {
className: styles.Z.itemContainer
}, GuildAndFriendRemovalAlerts_React.createElement("div", {
className: styles.Z.title
}, "Friends - ", GuildAndFriendRemovalAlerts_React.createElement("span", {
style: {
color: "var(--control-brand-foreground-new)"
}
}, friends.length)), GuildAndFriendRemovalAlerts_React.createElement("div", {
className: styles.Z.items
}, friends.map(((friend, i) => GuildAndFriendRemovalAlerts_React.createElement(Item, {
key: i,
title: friend.tag,
icon: friend.avatarUrl,
clickId: friend.id,
closeModal: props.onClose
}))))) : null) : GuildAndFriendRemovalAlerts_React.createElement("div", {
className: styles.Z.nothingHere
}, "No logs to show.")))));
}
onStop() {
external_PluginApi_namespaceObject.Patcher.unpatchAll();
external_StyleLoader_default().remove();
events.forEach((eventType => modules_namespaceObject.Dispatcher.unsubscribe(eventType, this.main)));
}
}
},
246: module => {
module.exports = function(cssWithMappingToString) {
var list = [];
list.toString = function() {
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;
};
},
832: 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__(234);
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@*/
================================================
FILE: GuildAndFriendRemovalAlerts/README.md
================================================
# GuildAndFriendRemovalAlerts
> Displays alerts when you are kicked/banned from a server, a server is deleted, and when a friend removes you.
Made with by BDBuilder
================================================
FILE: GuildAndFriendRemovalAlerts/src/components/item.jsx
================================================
import React from "react";
import styles from "./item.scss";
import { joinClassNames } from "@discord/utils";
import { WebpackModules } from "@zlibrary";
const PrivateModule = WebpackModules.getByProps("openPrivateChannel");
export default function Item({ title, description, icon, clickId, closeModal }) {
const handleClick = () => {
PrivateModule.openPrivateChannel(clickId);
closeModal();
};
return (
{ title }
{ description?.length ?
{ description }
: null }
);
}
================================================
FILE: GuildAndFriendRemovalAlerts/src/components/item.scss
================================================
.item {
display: flex;
margin: 5px;
padding: 10px;
cursor: pointer;
border-radius: 5px;
&:hover {
background-color: var(--background-modifier-hover);
}
.image {
width: 60px;
height: 60px;
border-radius: 5px;
}
.inner {
display: flex;
flex-direction: column;
padding: 5px;
margin-left: 10px;
.title {
margin: auto 0;
max-width: 400px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
font-weight: bolder;
font-size: 1.1rem;
}
.description {
margin: auto 0;
}
}
&.userItem {
}
&.guildItem {
}
}
================================================
FILE: GuildAndFriendRemovalAlerts/src/components/settings.jsx
================================================
import React from "react";
import createUpdateWrapper from "common/hooks/createUpdateWrapper";
import { WebpackModules } from "@zlibrary";
import Settings from "../modules/settings";
const Switch = createUpdateWrapper(WebpackModules.getByDisplayName("SwitchItem"));
export default function SettingsPanel() {
return (
Settings.set("showModal", value)}>Auto Show Modal
Settings.set("showDeskNotifs", value)}>Show Desktop Notifications
);
}
================================================
FILE: GuildAndFriendRemovalAlerts/src/index.js
================================================
import BasePlugin from "@zlibrary/plugin";
import { Logger, Patcher, PluginUtilities, ReactComponents, WebpackModules } from "@zlibrary";
import SettingsPanel from "./components/settings";
import Settings from "./modules/settings";
import Stores from "@discord/stores";
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalRoot, openModal, closeModal } from "@discord/modal";
import stylesheet from "styles";
import styles from "./styles.scss";
import Item from "./components/item";
import ContextMenu, { closeContextMenu, MenuItem, openContextMenu } from "@discord/contextmenu";
import pkg from "./package.json";
import { ActionTypes } from "@discord/constants";
import { Dispatcher } from "@discord/modules";
const { getFriendIDs } = WebpackModules.getByProps("getFriendIDs");
const HomeButton = WebpackModules.getByProps("HomeButton");
const events = [ ActionTypes.GUILD_CREATE, ActionTypes.GUILD_DELETE, ActionTypes.GUILD_UPDATE, ActionTypes.RELATIONSHIP_ADD,
ActionTypes.RELATIONSHIP_REMOVE, ActionTypes.RELATIONSHIP_UPDATE, ActionTypes.FRIEND_REQUEST_ACCEPTED ];
export default class GuildAndFriendRemovalAlerts extends BasePlugin {
history = {
guilds: Settings.get("removedGuildHistory", []),
friends: Settings.get("removedFriendHistory", []),
update: () => {
Settings.set("removedGuildHistory", this.history.guilds);
Settings.set("removedFriendHistory", this.history.friends);
},
clear: () => {
Object.assign(this.history, {
guilds: [],
friends: []
});
this.history.update();
}
};
snapshots = {
guilds: Settings.get("guildsSnapshot", []),
friends: Settings.get("friendsSnapshot", []),
update: ({ guilds, friends }) => {
Settings.set("guildsSnapshot", this.snapshots.guilds = guilds);
Settings.set("friendsSnapshot", this.snapshots.friends = friends);
}
};
getSettingsPanel = () => ;
onStart() {
const PatchedHomeButton = ({originalType, ...props}) => {
const returnValue = Reflect.apply(originalType, this, [props]);
try {
returnValue.props.onContextMenu = e => {
openContextMenu(e, () => (
this.openModal(this.history.guilds, this.history.friends, true)} id={pkg.info.name + "-logs"}/>
this.openModal()} id={pkg.info.name + "-view-all"}/>
));
};
} catch (error) {
Logger.error("Error in DefaultHomeButton patch:", error);
}
return returnValue;
}
Patcher.after(HomeButton, "HomeButton", (_, __, component) => {
const originalType = component.type;
component.type = PatchedHomeButton;
Object.assign(component.props, {originalType: originalType});
});
stylesheet.inject();
events.forEach(eventType => Dispatcher.subscribe(eventType, this.main));
this.main();
}
serializeGuild(guildId) {
const serialized = { id: guildId, invalid: true, name: "Unknown Guild", iconUrl: "/assets/1531b79c2f2927945582023e1edaaa11.png" };
try {
const guild = Stores.Guilds.getGuild(guildId);
if (guild) {
Object.assign(serialized, {
invalid: false,
name: guild.name,
ownerId: guild.ownerId,
iconUrl: typeof(guild.getIconURL) === "function" ? guild.getIconURL("webp") : serialized.iconUrl
});
}
} finally { }
return serialized;
}
serializeUser(userId) {
const serialized = { id: userId, invalid: true, tag: "Unknown User", avatarURL: "/assets/1cbd08c76f8af6dddce02c5138971129.png" };
try {
const user = Stores.Users.getUser(userId);
if (user) {
Object.assign(serialized, {
invalid: false,
tag: user.tag,
avatarUrl: typeof(user.getAvatarURL) === "function" ? user.getAvatarURL("webp") : serialized.avatarUrl
});
}
} finally { }
return serialized;
}
main = () => {
console.log("main()");
const guilds = Object.keys(Stores.Guilds.getGuilds()).map(guildId => this.serializeGuild(guildId));
const friends = getFriendIDs().map(uid => this.serializeUser(uid));
const removedGuilds = this.snapshots.guilds.filter(snapshot => !guilds.some(guild => snapshot.id === guild.id));
const removedFriends = this.snapshots.friends.filter(snapshot => !friends.some(friend => snapshot.id === friend.id));
if (removedGuilds.length || removedFriends.length) {
if (Settings.get("showModal", true))
this.openModal(removedGuilds, removedFriends);
removedGuilds.forEach(guild => this.history.guilds.unshift(guild));
removedFriends.forEach(friend => this.history.friends.unshift(friend));
if (Settings.get("showDeskNotifs", false)) {
removedGuilds.forEach(guild =>
new Notification(guild.name, {
silent: true,
body: "Server removed",
icon: guild.iconUrl
}));
removedFriends.forEach(friend =>
new Notification(friend.name, {
silent: true,
body: "Friend removed",
icon: friend.avatarUrl
}));
}
}
if (guilds.length !== this.snapshots.guilds.length || friends.length !== this.snapshots.friends.length) {
this.history.update();
this.snapshots.update({ guilds, friends });
console.log("update");
}
};
openModal(guilds, friends, showClearButton = false) {
if (!guilds && !friends) {
guilds = this.snapshots.guilds;
friends = this.snapshots.friends;
}
const clearLogs = () =>
BdApi.showConfirmationModal("Are you sure?", "Do you really want to clear the logs?\nThis action cannot be undone.", {
danger: true,
onConfirm: () => {
this.history.clear();
closeModal(modalId);
},
confirmText: "Clear"
});
const modalId = openModal(props => (
Guild And Friend Removal Alerts
{ guilds?.length || friends?.length ? (
{ showClearButton ? (
Clear Logs
) : null }
{ guilds?.length ? (
Guilds - {guilds.length}
{ guilds.map((guild, i) =>
- ) }
) : null }
{ friends?.length ? (
Friends - {friends.length}
{ friends.map((friend, i) =>
- ) }
) : null }
) : (
No logs to show.
) }
));
}
onStop() {
Patcher.unpatchAll();
stylesheet.remove();
events.forEach(eventType => Dispatcher.unsubscribe(eventType, this.main));
}
}
================================================
FILE: GuildAndFriendRemovalAlerts/src/modules/settings.js
================================================
import pkg from "../package.json";
import { PluginUtilities } from "@zlibrary";
export default class Settings {
static settings = PluginUtilities.loadSettings(pkg.info.name, {});
static get = (key, defaultValue) => Settings.settings[key] ?? defaultValue;
static set = (key, value) => {
Settings.settings[key] = value;
Settings.save();
};
static save = () => PluginUtilities.saveSettings(pkg.info.name, Settings.settings);
}
================================================
FILE: GuildAndFriendRemovalAlerts/src/package.json
================================================
{
"info": {
"name": "GuildAndFriendRemovalAlerts",
"version": "3.0.0",
"description": "Displays alerts when you are kicked/banned from a server, a server is deleted, and when a friend removes you.",
"authors": [
{
"name": "Metalloriff",
"discord_id": "264163473179672576",
"github_username": "Metalloriff"
}
],
"github": "https://github.com/Metalloriff/BetterDiscordPlugins/GuildAndFriendRemovalAlerts",
"github_raw": "https://raw.githubusercontent.com/Metalloriff/BetterDiscordPlugins/master/GuildAndFriendRemovalAlerts/GuildAndFriendRemovalAlerts.plugin.js",
"website": "https://metalloriff.github.io/toms-discord-stuff/#/",
"donate": "https://paypal.me/israelboone",
"invite": "yNqzuJa"
},
"changelog": [{
"title": "3.0 rewrite",
"type": "fixed",
"items": [
"This plugin has been rewritten. Functionality is simliar, and settings and data should still be valid.",
"If you experience any bugs, please contact me."
]
}],
"build": {
"zlibrary": true,
"copy": true,
"production": false,
"scssHash": false,
"alias": {
"components": "components/index.js"
},
"release": {
"source": true,
"readme": true,
"public": true,
"contributors": null
}
}
}
================================================
FILE: GuildAndFriendRemovalAlerts/src/styles.scss
================================================
.modal {
color: white;
.itemContainer {
.title {
margin: 20px;
font-size: 1.2rem;
font-weight: bolder;
}
.items {
}
}
.nothingHere {
position: absolute;
top: 50%; left: 50%;
transform: translate(-50%, -50%);
font-size: 2rem;
font-weight: bolder;
opacity: 0.5;
}
.clearButton {
margin-top: 20px;
background-color: rgba(237, 66, 69, 0.1);
color: #ff6666;
border-radius: 5px;
padding: 15px 10px;
text-align: center;
cursor: pointer;
}
}
.floatRight {
margin-left: auto;
}
================================================
FILE: GuildAndFriendRemovalAlerts.plugin.js
================================================
/**
* @name GuildAndFriendRemovalAlerts
* @invite yNqzuJa
* @authorLink https://github.com/Metalloriff
* @donate https://www.paypal.me/israelboone
* @website https://metalloriff.github.io/toms-discord-stuff/
* @source https://github.com/Metalloriff/BetterDiscordPlugins/blob/master/GuildAndFriendRemovalAlerts.plugin.js
*/
const name = "GuildAndFriendRemovalAlerts";
const newUrl = "https://github.com/Metalloriff/BetterDiscordPlugins/tree/master/" + name;
const rawUrl = `https://raw.githubusercontent.com/Metalloriff/BetterDiscordPlugins/master/${name}/${name}.plugin.js`;
module.exports = (() => {
const config = {
info: {
name: "0 GuildAndFriendRemovalAlerts OUTDATED",
authors: [{
name: "Metalloriff",
discord_id: "264163473179672576",
github_username: "metalloriff",
twitter_username: "Metalloriff"
}],
version: "999.999.999",
description: `Please delete this plugin and download the new one at ${newUrl}.`
}
};
return class {
getName = () => config.info.name;
getAuthor = () => config.info.authors[0].name;
getDescription = () => config.info.description;
getVersion = () => config.info.version;
load() {
BdApi.showConfirmationModal("Outdated Plugin", `This version of ${name} is outdated, consider installing the newest one by clicking the "Update Now" button.`, {
onConfirm: () => {
const https = require("https");
const fs = require("fs");
const path = require("path");
https.get(rawUrl, res => {
const chunks = [];
res.on("data", chunk => chunks.push(chunk));
res.on("end", () => {
try {
fs.writeFileSync(path.resolve(BdApi.Plugins.folder, path.basename(rawUrl)), chunks.join(""), "utf8");
} catch (error) {
console.error(name, error);
BdApi.showToast("Failed to download new update - check the console for details", { type: "error" });
}
});
});
}
});
}
start() { }
stop() { }
};
})();
================================================
FILE: GuildCounter.plugin.js
================================================
//META{"name":"GuildCounter","website":"https://metalloriff.github.io/toms-discord-stuff/","source":"https://github.com/Metalloriff/BetterDiscordPlugins/blob/master/GuildCounter.plugin.js"}*//
class GuildCounter {
getName() { return "Guild Counter"; }
getDescription() { return "Displays a guild counter below the online friend counter."; }
getVersion() { return "1.0.4"; }
getAuthor() { return "Metalloriff"; }
load() {}
start() {
let libLoadedEvent = () => {
try{ this.onLibLoaded(); }
catch(err) { console.error(this.getName(), "fatal error, plugin could not be started!", err); try { this.stop(); } catch(err) { console.error(this.getName() + ".stop()", err); } }
};
let lib = document.getElementById("NeatoBurritoLibrary");
if(!lib) {
lib = document.createElement("script");
lib.id = "NeatoBurritoLibrary";
lib.type = "text/javascript";
lib.src = "https://rawgit.com/Metalloriff/BetterDiscordPlugins/master/Lib/NeatoBurritoLibrary.js";
document.head.appendChild(lib);
}
this.forceLoadTimeout = setTimeout(libLoadedEvent, 30000);
if(typeof window.NeatoLib !== "undefined") libLoadedEvent();
else lib.addEventListener("load", libLoadedEvent);
}
onLibLoaded(){
NeatoLib.Updates.check(this);
(this.guildsScroller = document.getElementsByClassName(NeatoLib.getClass("unreadMentionsBar", "scroller"))[0]).addEventListener("DOMNodeInserted", this.count = () => {
if (typeof event !== "undefined" && event.relatedNode.id === "gc-counter") return;
let existing = document.getElementById("gc-counter"), count = Object.keys(NeatoLib.Modules.get("getGuilds").getGuilds()).length;
if(existing) existing.innerText = count + " guilds";
else this.guildsScroller.getElementsByClassName(NeatoLib.getClass("guildSeparator"))[0].parentElement.insertAdjacentElement("beforebegin", NeatoLib.DOM.createElement({ id : "gc-counter", className : NeatoLib.getClass("friendsOnline") + " " + NeatoLib.getClass(["badgeIcon", "guildSeparator", "guildsError"], "listItem"), innerText : count + " guilds" }));
});
this.count();
NeatoLib.Events.onPluginLoaded(this);
}
stop() {
this.guildsScroller.removeEventListener("DOMNodeInserted", this.count);
document.getElementById("gc-counter").remove();
}
}
================================================
FILE: IdleGuildlistScroller.plugin.js
================================================
//META{"name":"IdleGuildlistScroller"}*//
class IdleGuildlistScroller {
getName() { return "Idle Guildlist Scroller"; }
getDescription() { return "Automatically scrolls to the top of the guilds list after the specified amount of time that your mouse isn't over it."; }
getVersion() { return "0.1.1"; }
getAuthor() { return "Metalloriff"; }
load() {}
getSettingsPanel(){
return "Delay (ms): ms Check if mouse is over channel listReset Settings Save & Update ";
}
static saveSettings(){
IdleGuildlistScroller.delay = document.getElementById("igs-delay").value;
IdleGuildlistScroller.includeChannels = document.getElementById("igs-includeChannels").checked;
PluginUtilities.saveData("IdleGuildlistScroller", "settings", {delay : IdleGuildlistScroller.delay, includeChannels : IdleGuildlistScroller.includeChannels});
PluginUtilities.showToast("Settings saved! You will need to switch channels for them to take effect.");
}
static resetSettings(){
IdleGuildlistScroller.delay = 1000;
IdleGuildlistScroller.includeChannels = true;
PluginUtilities.showToast("Settings reset! You will still have to save and update.");
}
start() {
var libraryScript = document.getElementById('zeresLibraryScript');
if (!libraryScript) {
libraryScript = document.createElement("script");
libraryScript.setAttribute("type", "text/javascript");
libraryScript.setAttribute("src", "https://rauenzi.github.io/BetterDiscordAddons/Plugins/PluginLibrary.js");
libraryScript.setAttribute("id", "zeresLibraryScript");
document.head.appendChild(libraryScript);
}
if (typeof window.ZeresLibrary !== "undefined") this.initialize();
else libraryScript.addEventListener("load", () => { this.initialize(); });
}
initialize(){
PluginUtilities.checkForUpdate(this.getName(), this.getVersion(), "https://github.com/Metalloriff/BetterDiscordPlugins/raw/master/IdleGuildlistScroller.plugin.js");
IdleGuildlistScroller.resetSettings();
var data = PluginUtilities.loadData("IdleGuildlistScroller", "settings", {delay : 1000, includeChannels : true});
IdleGuildlistScroller.delay = data["delay"];
IdleGuildlistScroller.includeChannels = data["includeChannels"];
this.guildsList.addEventListener("mouseenter", this.onHover, false);
this.guildsList.addEventListener("mouseleave", this.offHover, false);
this.onSwitch();
}
onSwitch(){
if(IdleGuildlistScroller.includeChannels && this.channelList){
this.channelList.addEventListener("mouseenter", this.onHover, false);
this.channelList.addEventListener("mouseleave", this.offHover, false);
}else if(this.channelList){
this.channelList.removeEventListener("mouseenter", this.onHover);
this.channelList.removeEventListener("mouseleave", this.offHover);
}
}
onHover(){
if(IdleGuildlistScroller.sttFunc)
clearTimeout(IdleGuildlistScroller.sttFunc);
}
offHover(){
IdleGuildlistScroller.sttFunc = setTimeout(function(){
$(document.getElementsByClassName("guilds scroller")[0]).animate({scrollTop : 0}, "fast");
}, IdleGuildlistScroller.delay);
}
stop() {
this.guildsList.removeEventListener("mouseenter", this.onHover);
this.guildsList.removeEventListener("mouseleave", this.offHover);
if(this.channelList){
this.channelList.removeEventListener("mouseenter", this.onHover);
this.channelList.removeEventListener("mouseleave", this.offHover);
}
}
get guildsList(){
return document.getElementsByClassName("guilds-wrapper")[0];
}
get channelList(){
return document.getElementsByClassName("scroller-fzNley scroller-NXV0-d")[0];
}
}
================================================
FILE: ImageBrowser.plugin.js
================================================
//META{"name":"ImageBrowser","website":"https://metalloriff.github.io/toms-discord-stuff/","source":"https://github.com/Metalloriff/BetterDiscordPlugins/blob/master/ImageBrowser.plugin.js"}*//
class ImageBrowser {
getName() { return "ImageBrowser"; }
getDescription() { return "Displays a next and previous button on image popouts for browsing through images in a channel."; }
getVersion() { return "0.1.1"; }
getAuthor() { return "Metalloriff"; }
getChanges() {
return {
"0.1.1":
`
You can now use the arrow keys to navigate through images.
`
};
}
load() {}
start() {
let libLoadedEvent = () => {
try{ this.onLibLoaded(); }
catch(err) { console.error(this.getName(), "fatal error, plugin could not be started!", err); try { this.stop(); } catch(err) { console.error(this.getName() + ".stop()", err); } }
};
let lib = document.getElementById("NeatoBurritoLibrary");
if(!lib) {
lib = document.createElement("script");
lib.id = "NeatoBurritoLibrary";
lib.type = "text/javascript";
lib.src = "https://rawgit.com/Metalloriff/BetterDiscordPlugins/master/Lib/NeatoBurritoLibrary.js";
document.head.appendChild(lib);
}
this.forceLoadTimeout = setTimeout(libLoadedEvent, 30000);
if(typeof window.NeatoLib !== "undefined") libLoadedEvent();
else lib.addEventListener("load", libLoadedEvent);
}
getSettingsPanel() {
setTimeout(() => {
NeatoLib.Settings.pushElement(NeatoLib.Settings.Elements.createNewTextField("Zoom speed", this.settings.zoomSpeed, e => {
if(isNaN(e.target.value)) return NeatoLib.showToast("Value must be a number", "error");
this.settings.zoomSpeed = e.target.value;
this.saveSettings();
}), this.getName());
NeatoLib.Settings.pushElement(NeatoLib.Settings.Elements.createToggleSwitch("Display image loading filter", this.settings.loadingFilter, () => {
this.settings.loadingFilter = !this.settings.loadingFilter;
this.saveSettings();
}), this.getName(), { hint : "Grayscale, blurred, slightly darker, when an image is loading" });
NeatoLib.Settings.pushChangelogElements(this);
}, 0);
return NeatoLib.Settings.Elements.pluginNameLabel(this.getName());
}
saveSettings() {
NeatoLib.Settings.save(this);
}
onLibLoaded() {
this.settings = NeatoLib.Settings.load(this, {
displayUpdateNotes : true,
zoomSpeed : 0.2,
loadingFilter : true
});
NeatoLib.Updates.check(this);
const classes = NeatoLib.getClasses(["imageWrapper"], false);
this.style = NeatoLib.injectCSS(`
.ib-arrow {
position: fixed;
top: calc(50% - 100px);
width: 200px;
height: 200px;
filter: invert(50%);
animation: ib-arrow-fade-in 1s;
transition: filter 0.3s;
z-index: 10000;
}
.ib-arrow:hover {
filter: invert(100%);
}
@keyframes ib-arrow-fade-in {
0%{opacity:0}
100%{opacity:1}
}
`);
if(this.settings.displayUpdateNotes) NeatoLib.Changelog.compareVersions(this.getName(), this.getChanges());
let images = [], selectedImage = -1, zoomLevel = 1;
document.addEventListener("keydown", this.keyDownEvent = e => {
if(e.key == "ArrowRight" && document.getElementById("ib-next-arrow")) document.getElementById("ib-next-arrow").click();
else if(e.key == "ArrowLeft" && document.getElementById("ib-prev-arrow")) document.getElementById("ib-prev-arrow").click();
});
const selectImage = (wrapper, idx, initial) => {
selectedImage = idx;
if(!initial) images = Array.filter(document.getElementsByClassName("chat")[0].getElementsByTagName("img"), e => e.parentElement.className.includes("imageWrapper"));
const image = wrapper.lastChild;
image.style = "";
image.addEventListener("mousewheel", e => {
if(e.wheelDelta > 0) zoomLevel += this.settings.zoomSpeed;
else if(zoomLevel > 1) zoomLevel -= this.settings.zoomSpeed;
else {
clicked = false;
image.style.top = 0;
image.style.left = 0;
image.style.transform = "";
return;
}
if(zoomLevel > 1) {
image.draggable = false;
image.style.transform = `scale(${zoomLevel})`;
} else image.draggable = true;
});
let clicked = false, origPos;
image.addEventListener("mousedown", function(e) {
origPos = { x : e.clientX, y : e.clientY, top : parseInt(image.style.top), left : parseInt(image.style.left) };
clicked = true;
});
image.addEventListener("mouseup", function() { clicked = false; });
image.style.top = 0;
image.style.left = 0;
image.addEventListener("mousemove", function(e) {
if(!(clicked && zoomLevel > 1)) return;
image.style.top = (origPos.top - (origPos.y - e.clientY)) + "px";
image.style.left = (origPos.left - (origPos.x - e.clientX)) + "px";
});
let prevArrow = document.getElementById("ib-prev-arrow"), nextArrow = document.getElementById("ib-next-arrow");
if(images[idx - 1]) {
if(!prevArrow) wrapper.parentElement.insertAdjacentElement("afterbegin", NeatoLib.DOM.createElement({ id : "ib-prev-arrow", className : "ib-arrow", src : "https://material.io/tools/icons/static/icons/baseline-arrow_right-24px.svg", style : "left:100px;transform:rotate(180deg)", onclick : () => selectImage(wrapper, selectedImage - 1), draggable : false }, { type : "img" }));
} else if(prevArrow) prevArrow.remove();
if(images[idx + 1]) {
if(!nextArrow) wrapper.parentElement.insertAdjacentElement("afterbegin", NeatoLib.DOM.createElement({ id : "ib-next-arrow", className : "ib-arrow", src : "https://material.io/tools/icons/static/icons/baseline-arrow_right-24px.svg", style : "right:100px;", onclick : () => selectImage(wrapper, selectedImage + 1), draggable : false }, { type : "img" }));
} else if(nextArrow) nextArrow.remove();
image.src = images[idx].src;
if(this.settings.loadingFilter) image.style.filter = "grayscale(100%) brightness(0.8) blur(3px)";
images[idx].scrollIntoViewIfNeeded();
image.setAttribute("width", "");
image.setAttribute("height", "");
image.style.width = "";
image.style.height = "";
const resize = function() {
image.width *= 3;
const tryBegin = performance.now();
while((image.width > window.innerWidth * 0.65 || image.height > window.innerHeight * 0.65) && performance.now() - tryBegin < 500) image.width *= 0.9;
document.getElementById("app-mount").lastChild.lastChild.firstChild.style.width = image.width + "px";
document.getElementById("app-mount").lastChild.lastChild.firstChild.style.height = image.height + "px";
wrapper.style.width = image.width + "px";
wrapper.style.height = image.height + "px";
};
image.onload = function() {
resize();
image.src = images[idx].src.split("?")[0];
image.onload = e => {
resize();
image.style.transition = "all 0.1s";
e.target.style.filter = "";
};
};
if(image.complete) image.onload();
};
this.mutationObserver = new MutationObserver(async function(m) {
if(m[1] && m[1].addedNodes.length) {
images = Array.filter(document.getElementsByClassName("chat")[0].getElementsByTagName("img"), e => e.parentElement.className.includes("imageWrapper"));
const wrapper = m[1].addedNodes[0].getElementsByClassName(classes.imageWrapper)[0];
if(!wrapper) return;
const tryBegin = performance.now();
while(!wrapper.lastChild.src && performance.now() - tryBegin < 3000) await NeatoLib.Thread.sleep();
selectImage(wrapper, images.findIndex(i => i.src && i.src.split("?")[0] == wrapper.getElementsByTagName("img")[0].src.split("?")[0]), true);
}
});
this.mutationObserver.observe(document.getElementById("app-mount").lastChild, { childList : true });
NeatoLib.Events.onPluginLoaded(this);
}
stop() {
this.mutationObserver.disconnect();
this.style.destroy();
document.removeEventListener("keydown", this.keyDownEvent);
}
}
================================================
FILE: Lib/NeatoBurritoLibrary.js
================================================
var NeatoLib = {
version: "0.9.29",
parseVersion: function(version) {
let numbers = Array.from(version.split("."), n => parseInt(n)),
major = numbers[0],
minor = numbers[1],
patch = numbers[2];
return {
major: major,
minor: minor,
patch: patch,
compareTo: otherVersion => {
if (patch > otherVersion.patch || minor > otherVersion.minor || major > otherVersion.major) return "newer";
if (patch < otherVersion.patch || minor < otherVersion.minor || major < otherVersion.major) return "older";
return "equal";
}
};
},
hasRequiredLibVersion: function(plugin, requiredVersion) {
if (NeatoLib.parseVersion(NeatoLib.version).compareTo(NeatoLib.parseVersion(requiredVersion)) == "older") {
if (plugin.ready) plugin.ready = false;
const updateLibrary = () => {
const vm = require("vm");
fetch("https://raw.githubusercontent.com/Metalloriff/BetterDiscordPlugins/master/Lib/NeatoBurritoLibrary.js", { cache: "no-cache" }).then(r => r.text()).then(data => {
let lib = new vm.Script(data, {
filename: "NeatoBurritoLibrary.js",
displayErrors: true
});
new Promise(exec => exec(lib.runInThisContext())).then(() => {
NeatoLib.showToast(`[${plugin.getName()}]: Library updated!`, "success");
setTimeout(() => plugin.start(), 1000);
});
});
};
NeatoLib.showToast(`[${plugin.getName()}]: Library update required! Click this notification to update it.`, "error", {
timeout: 30000,
onClick: updateLibrary,
destroyOnClick: true
});
return false;
}
return true;
},
forceLibUpdate: function() {
const vm = require("vm");
fetch("https://raw.githubusercontent.com/Metalloriff/BetterDiscordPlugins/master/Lib/NeatoBurritoLibrary.js", { cache: "no-cache" }).then(r => r.text()).then(data => {
const lib = new vm.Script(data, { filename: "NeatoBurritoLibrary.js", displayErrors: true });
new Promise(e => e(lib.runInThisContext())).then(() => NeatoLib.showToast("Lib updated!", "success"));
});
},
Changelog: {
compareVersions: function(name, changes) {
var spacelessName = name.split(" ").join(""),
updateData = NeatoLib.Data.load("MetalloriffUpdateData", spacelessName, {}),
unreadChanges = [],
thisUpdateData = updateData[spacelessName],
first = false;
if (thisUpdateData != undefined) {
if (thisUpdateData.readChanges == undefined) thisUpdateData.readChanges = [];
for (var i in changes) {
if (!thisUpdateData.readChanges.includes(i)) {
unreadChanges.push(i);
thisUpdateData.readChanges.push(i);
}
}
} else {
updateData[spacelessName] = {
readChanges: Object.keys(changes)
};
first = true;
}
if (unreadChanges.length > 0 || first) {
NeatoLib.Changelog.createChangeWindow(name, unreadChanges, changes, updateData);
}
},
createChangeWindow: function(name, changes, allChanges, newUpdateData) {
let changeKeys = Object.keys(allChanges);
if (changeKeys.length == 0) {
NeatoLib.showToast("There are no updates notes for this plugin yet!", "error");
return;
}
let spacelessName = name.split(" ").join("");
if (document.getElementById(spacelessName + "-changelog")) document.getElementById(spacelessName + "-changelog").remove();
document.getElementsByClassName(NeatoLib.getClass("app"))[0].insertAdjacentHTML("beforeend", `
`);
document.getElementById(spacelessName + "-changelog").getElementsByClassName("metalloriff-changelog-backdrop")[0].addEventListener("click", () => {
if (newUpdateData != undefined) NeatoLib.Data.save("MetalloriffUpdateData", spacelessName, newUpdateData);
document.getElementById(spacelessName + "-changelog").remove();
});
let scroller = document.getElementById(spacelessName + "-changelog-scroller");
if (changes.length == 0) changes = changeKeys;
for (let i = 0; i < changes.length; i++) {
scroller.insertAdjacentHTML("afterbegin", `
`);
}
}
},
Settings: {
createPanel: function(plugin) {
setTimeout(() => {
this.create(plugin);
this.pushChangelogElements(plugin);
});
return this.Elements.pluginNameLabel(plugin.getName());
},
create: function(plugin) {
const panel = document.getElementById(`plugin-settings-${plugin.getName()}`), fields = plugin.settingFields;
panel.classList.add("neato-settings");
panel.insertAdjacentHTML("beforeEnd", `
`);
const createLabel = (title, description, tooltip) => {
const element = document.createElement("label");
element.className = "neato-setting-label";
element.textContent = title;
if(description) element.dataset.description = description || "";
if(tooltip) NeatoLib.Tooltip.attach(tooltip, element);
return element;
};
const createToggleSwitch = (title, description, tooltip, value, callback) => { //
const element = document.createElement("div");
element.className = "neato-setting";
if (title) element.appendChild(createLabel(title, description, tooltip));
const tswitch = document.createElement("div");
tswitch.className = [NeatoLib.getClass("flexChild"), NeatoLib.getClass("switchEnabled"), NeatoLib.getClass("switch"), value ? NeatoLib.getClass("valueChecked") : NeatoLib.getClass("valueUnchecked"), NeatoLib.getClass("value"), NeatoLib.getClass("sizeDefault"), NeatoLib.getClass("sizeDefault", "size"), NeatoLib.getClass("themeDefault")].join(" ");
tswitch.style.float = "right";
const tcheckbox = document.createElement("input");
tcheckbox.type = "checkbox";
tcheckbox.className = [NeatoLib.getClass("checkboxEnabled"), NeatoLib.getClass("checkboxEnabled", "checkbox")].join(" ");
tswitch.appendChild(tcheckbox);
tswitch.addEventListener("click", e => {
value = !value;
if (value == true) tswitch.className = tswitch.className.replace(NeatoLib.getClass("valueUnchecked"), NeatoLib.getClass("valueChecked"));
else tswitch.className = tswitch.className.replace(NeatoLib.getClass("valueChecked"), NeatoLib.getClass("valueUnchecked"));
callback(value, e);
});
element.appendChild(tswitch);
return element;
};
const createTextField = (title, description, tooltip, value, type, callback) => {
const element = document.createElement("div");
element.className = "neato-setting";
if (title) element.appendChild(createLabel(title, description, tooltip));
const input = document.createElement("input");
input.className = "neato-textfield";
input.type = "text";
input.value = value || "";
let last = input.value;
input.addEventListener("focusout", e => {
switch (type) {
case "number": case "float":
if (!input.value) {
callback(input.value = last = 0, e);
break;
}
if (isNaN(input.value)) {
NeatoLib.showToast("Value must be a number!", "error");
input.value = last;
break;
}
callback(last = parseFloat(input.value), e);
break;
case "whole number": case "int": case "integer":
if (!input.value) {
callback(input.value = last = 0, e);
break;
}
if (isNaN(input.value)) {
NeatoLib.showToast("Value must be a number!", "error");
input.value = last;
break;
}
callback(last = parseInt(input.value), e);
break;
default: callback(input.value, e);
}
});
element.appendChild(input);
return element;
};
const createRadioGroup = (title, description, tooltip, value, choices, callback) => {
const element = document.createElement("div");
element.className = "neato-setting";
if (title) element.appendChild(createLabel(title, description, tooltip));
const list = document.createElement("div");
const update = () => {
for (let choice of element.getElementsByClassName("neato-radio-button")) {
if (choice.dataset.key == value) choice.classList.add("nrb-selected");
else choice.classList.remove("nrb-selected");
}
};
const createRadioButton = choice => {
const c = document.createElement("div");
c.className = "neato-radio-button";
c.dataset.key = choice;
if (value == choice) c.classList.add("nrb-selected");
const t = document.createElement("span");
t.className = "nrb-box";
c.appendChild(t);
c.appendChild(createLabel(choices[choice].label, choices[choice].description, choices[choice].tooltip));
c.addEventListener("click", e => {
callback(value = choice, e);
update();
});
return c;
};
for (let choice in choices) element.appendChild(createRadioButton(choice));
update();
return element;
};
if (!plugin.settings) plugin.settings = plugin.defaultSettings;
for (let setting in fields) {
const field = fields[setting];
try {
let repop, array, insertDeleteButton;
if (field.array || field.list) {
const list = document.createElement("div");
list.className = "neato-setting neato-setting-array";
list.appendChild(createLabel(field.label, field.description, field.tooltip));
array = document.createElement("div");
array.className = "neato-setting-array-items";
list.appendChild(array);
if (field.array) {
const addButton = document.createElement("button"), clearButton = document.createElement("button");
addButton.className = clearButton.className = [NeatoLib.getClass("button"), NeatoLib.getClass("lookFilled"), NeatoLib.getClass("colorBrand"), NeatoLib.getClass("sizeMedium"), NeatoLib.getClass("grow")].join(" ");
addButton.style = clearButton.style = "display:inline;margin:0 10px";
addButton.textContent = "Add";
addButton.addEventListener("click", e => {
plugin.settings[setting].push("");
array.innerHTML = "";
repop();
if (typeof field.callback == "function") field.callback({ type: "add", event: e, array: array, panel: panel });
plugin.saveSettings();
});
clearButton.textContent = "Clear";
clearButton.addEventListener("click", e => {
plugin.settings[setting] = [];
array.innerHTML = "";
repop();
if (typeof field.callback == "function") field.callback({ type: "clear", event: e, array: array, panel: panel });
plugin.saveSettings();
});
insertDeleteButton = i => {
const button = document.createElement("span");
button.className = "material-icons neato-array-remove-button";
button.textContent = "close";
button.addEventListener("click", e => {
plugin.settings[setting].splice(i, 1);
array.innerHTML = "";
repop();
if (typeof field.callback == "function") field.callback({ type: "remove", event: e, array: array, panel: panel });
plugin.saveSettings();
});
NeatoLib.Tooltip.attach("Remove", button, { delay : 250 });
array.children[i].appendChild(button);
};
const buttons = document.createElement("div");
buttons.className = "neato-setting-array-buttons";
buttons.appendChild(addButton);
buttons.appendChild(clearButton);
list.appendChild(buttons);
}
panel.appendChild(list);
}
const set = (nv, e) => {
if (typeof field.callback == "function") field.callback({ type: "change", oldValue: plugin.settings[setting], newValue: nv, event: e, panel: panel });
plugin.settings[setting] = nv;
plugin.saveSettings();
};
switch (field.type) {
case "bool": case "boolean":
if (array) {
(repop = () => {
for (let i in plugin.settings[setting]) {
const element = createToggleSwitch(null, null, null, plugin.settings[setting][i], (nv, e) => {
if (typeof field.callback == "function") field.callback({ type: "change", oldValue: plugin.settings[setting][i], newValue: nv, event: e, panel: panel });
plugin.settings[setting][i] = nv;
plugin.saveSettings();
});
if (field.events)
for (let event in field.events)
if (event == "start" || event == "init") field.events[i](element);
else element.addEventListener(event, field.events[i]);
array.appendChild(element);
if (field.array) insertDeleteButton(i);
}
})();
} else {
const element = createToggleSwitch(field.label, field.description, field.tooltip, plugin.settings[setting], set);
if (field.events)
for (let event in field.events)
if (event == "start" || event == "init") field.events[i](element);
else element.addEventListener(event, field.events[i]);
panel.appendChild(element);
}
break;
case "custom":
if (field.element) panel.appendChild(field.element);
if (field.html) panel.insertAdjacentHTML("beforeEnd", field.html);
if (field.createElement) field.createElement(panel);
if (field.events)
for (let event in field.events)
if (event == "start" || event == "init") field.events[i](panel.lastChild);
else panel.lastChild.addEventListener(event, field.events[i]);
break;
case "radio": case "radio group":
const element = createRadioGroup(field.label, field.description, field.tooltip, plugin.settings[setting], field.choices, set);
if (field.events)
for (let event in field.events)
if (event == "start" || event == "init") field.events[i](element);
else element.addEventListener(event, field.events[i]);
panel.appendChild(element);
break;
default:
if (array) {
(repop = () => {
for (let i in plugin.settings[setting]) {
const element = createTextField(null, null, null, plugin.settings[setting][i], field.type, (nv, e) => {
if (typeof field.callback == "function") field.callback({ type: "change", oldValue: plugin.settings[i], newValue: nv, event: e, panel: panel });
plugin.settings[setting][i] = nv;
plugin.saveSettings();
});
if (field.events)
for (let event in field.events)
if (event == "start" || event == "init") field.events[i](element);
else element.addEventListener(event, field.events[i]);
array.appendChild(element);
if (field.array) insertDeleteButton(i);
}
})();
} else {
const element = createTextField(field.label, field.description, field.tooltip, plugin.settings[setting], field.type, set);
if (field.events)
for (let event in field.events)
if (event == "start" || event == "init") field.events[i](element);
else element.addEventListener(event, field.events[i]);
panel.appendChild(element);
}
}
} catch(err) { console.error(err); }
}
},
Styles: {
textField: `color: white; background-color: rgba(0, 0, 0, 0.2); border: none; border-radius: 5px; height: 40px; padding: 10px; width: 100%;`
},
Elements: {
pluginNameLabel: function(name, authorName = "Metalloriff") {
return `
${name.replace(/([A-Z])/g, ' $1').trim()} by ${authorName} `;
},
createRadioGroup: function(id, label, choices, selectedChoice, callback, description = "") {
let element = document.createElement("div");
element.style.paddingTop = "20px";
element.innerHTML = `
${label}
${description}
`;
for (let i = 0; i < choices.length; i++) {
if (choices[i].description == undefined) choices[i].description = "";
let choiceButton = document.createElement("div");
choiceButton.setAttribute("id", `${id}-${i}`);
choiceButton.setAttribute("data-value", choices[i].value);
choiceButton.setAttribute("data-index", i);
choiceButton.setAttribute("class", "metalloriff-checkbox-item");
choiceButton.setAttribute("style", `padding:10px;border-radius:5px !important;background-color:rgba(0, 0, 0, 0.3);cursor:pointer;position:relative;margin-bottom:10px;display:flex;`);
choiceButton.innerHTML =
`
${choices[i].title}
${choices[i].description}
`;
element.insertAdjacentElement("beforeend", choiceButton);
if (selectedChoice != undefined && choices[i].value == selectedChoice) choiceButton.querySelector(`label > div`).style.backgroundColor = "white";
choiceButton.addEventListener("click", e => {
let i = e.currentTarget.getAttribute("data-index");
let checkboxes = e.currentTarget.parentElement.querySelectorAll(`.metalloriff-checkbox-item > label > div`);
for (let ii = 0; ii < checkboxes.length; ii++) checkboxes[ii].style.backgroundColor = "";
element.querySelector(`#${id}-${i} > label > div`).style.backgroundColor = "white";
callback(choiceButton, choices[i]);
});
}
return element;
},
createToggleGroup: function(id, label, choices, callback, description = "") {
let element = document.createElement("div");
element.style.paddingTop = "10px";
element.insertAdjacentHTML("beforeend", `
${label}
${description}
`);
for (let i = 0; i < choices.length; i++) {
let choiceButton = NeatoLib.Settings.Elements.createToggleSwitch(choices[i].title, choices[i].setValue, e => {
callback(choices[i], e);
});
choiceButton.setAttribute("id", `${id}-${i}`);
choiceButton.setAttribute("data-value", choices[i].value);
choiceButton.setAttribute("data-index", i);
element.insertAdjacentElement("beforeend", choiceButton);
}
return element;
},
createTextField: function(label, type, value, callback, options = {}) {
let element = document.createElement("div");
element.style.paddingTop = options.spacing || "20px";
element.insertAdjacentHTML("beforeend", `
${label}
`);
if (options.tooltip) NeatoLib.Tooltip.attach(options.tooltip, element, {
side: "left"
});
element.querySelector("input").addEventListener(options.callbackType || "focusout", e => callback(e));
return element;
},
createNewTextField: function(label, value, callback, options = {}) {
let element = document.createElement("div");
element.style.paddingTop = options.spacing || "20px";
element.insertAdjacentHTML("beforeend", `
${label}
${options.description || ""}
`);
element.querySelector("input").addEventListener(options.callbackType || "focusout", e => callback(e));
return element;
},
createHint: function(text, options = {}) {
let element = document.createElement("p");
element.style.color = options.color || "white";
element.style.fontSize = options.fontSize || "17px";
element.innerText = text;
return element;
},
createButton: function(label, callback, style = "", attributes = {}) {
let element = document.createElement("button");
element.setAttribute("style", `display:inline-block;${style}`);
element.setAttribute("class", [NeatoLib.getClass("button"), NeatoLib.getClass("lookFilled"), NeatoLib.getClass("colorBrand"), NeatoLib.getClass("sizeMedium"), NeatoLib.getClass("grow")].join(" "));
for (let key in attributes) element.setAttribute(key, attributes[key]);
element.innerText = label;
element.addEventListener("click", e => callback(e));
return element;
},
createToggleSwitch: function(label, value, callback, spacing = "20px") {
var element = document.createElement("div");
element.style.paddingTop = spacing;
element.innerHTML =
``;
element.querySelector("input").addEventListener("click", e => {
var b = e.currentTarget.parentElement;
if (b.classList.contains("valueChecked-m-4IJZ")) {
b.classList.add("valueUnchecked-2lU_20");
b.classList.remove("valueChecked-m-4IJZ");
} else {
b.classList.add("valueChecked-m-4IJZ");
b.classList.remove("valueUnchecked-2lU_20");
}
callback(e);
});
return element;
},
createLabel: function(title, spacing = "20px", style = "") {
return `${title}
`;
},
createGroup: function(title, options = {}) {
let element = document.createElement("div");
element.setAttribute("style", `color:white;margin:${options.spacing || "20px"};${options.style || ""}`);
element.insertAdjacentHTML("beforeend", `${title}
`);
return element;
},
createKeybindInput: function(title, value, callback, options = {}) {
let element = document.createElement("div"),
v = value.primaryKey || "",
oldValue = value;
if (value.modifiers && value.modifiers[0]) v = (value.modifiers.join(" + ") || "") + " + " + (value.primaryKey || "");
if (options.global) v = value;
element.insertAdjacentHTML("beforeend", `
${options.description || ""}
`);
let isRecording = false,
primaryKey = "",
modifiers = [],
globalKeys = [];
let keyEvent = e => {
e.preventDefault();
if (options.global) {
let key = e.key;
if (key.length == 1) key = key.toUpperCase();
if (globalKeys.indexOf(key) == -1) globalKeys.push(key);
if (globalKeys[0] == "") globalKeys.splice(0, 1);
input.value = globalKeys.join(" + ");
if (e.location == 0 && globalKeys.length > 1) button.click();
else input.value += " + ...";
} else {
if (e.location == 0) primaryKey = e.code;
else if (modifiers.indexOf(e.code) == -1) modifiers.push(e.code);
if (primaryKey && modifiers[0]) {
input.value = `${modifiers.join(" + ")} + ${primaryKey}`;
button.click();
} else if (primaryKey) input.value = primaryKey;
else if (modifiers[0]) input.value = modifiers.join(" + ") + " + ...";
else input.value = "";
input.value = input.value.replace("Key", "");
}
};
let keyUpEvent = e => {
e.preventDefault();
if (options.global) {
let key = e.key;
if (key.length == 1) key = key.toUpperCase();
if (globalKeys.indexOf(key) != -1) globalKeys.splice(globalKeys.indexOf(key), 1);
if (globalKeys[0] == "") globalKeys.splice(0, 1);
input.value = globalKeys.join(" + ");
} else {
if (e.location == 0) primaryKey = undefined;
else if (modifiers.indexOf(e.code) != -1) modifiers.splice(modifiers.indexOf(e.code), 1);
if (primaryKey && modifiers[0]) input.value = `${modifiers.join(" + ")} + ${primaryKey}`;
else if (primaryKey) input.value = primaryKey;
else if (modifiers[0]) input.value = modifiers.join(" + ") + " + ...";
else input.value = "";
input.value = input.value.replace("Key", "");
}
};
let toggleRecording = () => {
isRecording = !isRecording;
if (isRecording) {
if (options.global) NeatoLib.Keybinds.unregisterGlobal(oldValue);
document.addEventListener("keydown", keyEvent);
document.addEventListener("keyup", keyUpEvent);
document.addEventListener("click", documentClick);
container.classList.add("recording-1H2dS7");
label.innerText = "Save Keybind";
} else {
oldValue = globalKeys.join(" + ");
if (options.global) callback(oldValue);
else callback({
primaryKey: primaryKey,
modifiers: modifiers
});
primaryKey = undefined;
modifiers = [];
globalKeys = [];
document.removeEventListener("keydown", keyEvent);
document.removeEventListener("keyup", keyUpEvent);
document.removeEventListener("click", documentClick);
container.classList.remove("recording-1H2dS7");
label.innerText = "Edit Keybind";
}
};
let documentClick = e => {
if (!e.target.classList.contains("nbl-keybind-button")) toggleRecording();
};
let input = element.getElementsByTagName("input")[0],
container = element.getElementsByClassName("container-CpszHS")[0],
button = element.getElementsByTagName("button")[0],
label = element.getElementsByClassName("text-2sI5Sd")[0];
button.addEventListener("click", toggleRecording);
return element;
}
},
pushChangelogElements: function(plugin) {
var element = document.createElement("div");
element.style.padding = "10px";
element.style.marginTop = "10px";
element.style.backgroundColor = "rgba(0,0,0,0.2)";
element.style.borderRadius = "5px";
element.insertAdjacentHTML("beforeend", `Other
`);
element.insertAdjacentElement("beforeend", NeatoLib.Settings.Elements.createToggleSwitch("Display changes for every update", plugin.settings.displayUpdateNotes, () => {
plugin.settings.displayUpdateNotes = !plugin.settings.displayUpdateNotes;
plugin.saveSettings();
}));
var right = document.createElement("div");
right.style.textAlign = "right";
right.style.paddingTop = "20px";
right.insertAdjacentElement("beforeend", NeatoLib.Settings.Elements.createButton("View Changelog", () => {
NeatoLib.Changelog.createChangeWindow(plugin.getName(), [], plugin.getChanges());
}));
right.insertAdjacentElement("afterbegin", NeatoLib.Settings.Elements.createButton("Join Support Server", () => {
window.open("https://discord.gg/yNqzuJa");
}, "float:left"));
element.insertAdjacentElement("beforeend", right);
NeatoLib.Settings.pushElement(element, plugin.getName());
},
pushElement: function(element, name, options = {}) {
const {
tooltip,
tooltipSide
} = options;
document.getElementById(`plugin-settings-${name}`).appendChild(element);
if (tooltip) NeatoLib.Tooltip.attach(tooltip, element, {
side: tooltipSide || "left"
});
},
pushElements: function(elements, name) {
let panel = document.getElementById(`plugin-settings-${name}`);
for (let i = 0; i < elements.length; i++) panel.appendChild(elements[i]);
},
pushHTML: function(html, name) {
document.getElementById(`plugin-settings-${name}`).insertAdjacentHTML("beforeend", html);
},
showPluginSettings: function(name) {
document.querySelector(".button-2b6hmh:nth-child(3)").click();
setTimeout(() => {
var bdActions = document.querySelectorAll("#bd-settings-sidebar .ui-tab-bar-item");
for (var i = 0; i < bdActions.length; i++) {
if (bdActions[i].textContent == "Plugins") bdActions[i].click();
}
setTimeout(() => {
var settingsBox = document.querySelector(`li[data-name="${name}"]`),
settingsButton = settingsBox.getElementsByClassName("bda-settings-button")[0];
settingsBox.scrollIntoView();
if (settingsButton != undefined) settingsButton.click();
}, 100);
}, 100);
},
save: function(plugin) {
NeatoLib.Data.save(plugin.getName().split(" ").join(""), "settings", plugin.settings);
},
load: function(plugin, defaultSettings) {
return plugin.settings = NeatoLib.Data.load(plugin.getName().split(" ").join(""), "settings", defaultSettings || plugin.defaultSettings);
}
},
UI: {
createPrompt: function(id, title, description, yesCallback, noCallback = "close", options = {}) {
document.getElementsByClassName(NeatoLib.getClass("app"))[0].insertAdjacentHTML("beforeend", `
`);
let prompt = document.getElementById("neato-prompt-" + id),
backdrop = prompt.getElementsByClassName("backdrop-1wrmKB")[0],
yesButton = prompt.getElementsByClassName("prompt-yes")[0],
noButton = prompt.getElementsByClassName("prompt-no")[0];
prompt.close = () => prompt.outerHTML = "";
backdrop.addEventListener("click", () => prompt.close());
yesButton.addEventListener("click", () => yesCallback(prompt));
noButton.addEventListener("click", noCallback == "close" ? () => prompt.close() : () => noCallback(prompt));
prompt.addEventListener("keydown", e => {
if (e.key == "Escape") prompt.close();
if (e.key == "Enter") yesButton.click();
});
return prompt;
},
createTextPrompt: function(id, title, callback, value = "", options = {}) {
document.getElementsByClassName(NeatoLib.getClass("app"))[0].insertAdjacentHTML("beforeend", `
${options.secondOptionText || ""}
`);
let prompt = document.getElementById("neato-text-prompt-" + id),
backdrop = prompt.getElementsByClassName("backdrop-1wrmKB")[0],
confirmButton = prompt.getElementsByClassName("prompt-confirm")[0],
cancelButton = prompt.getElementsByClassName("prompt-cancel")[0],
secondOption = prompt.getElementsByClassName("prompt-second-option")[0],
field = prompt.getElementsByTagName("input")[0];
field.focus();
field.selectionStart = field.selectionEnd = field.value.length;
prompt.close = () => prompt.outerHTML = "";
backdrop.addEventListener("click", () => prompt.close());
confirmButton.addEventListener("click", () => callback(field.value, prompt));
cancelButton.addEventListener("click", () => prompt.close());
if (options.secondOptionCallback != undefined) secondOption.addEventListener("click", () => options.secondOptionCallback(prompt));
prompt.addEventListener("keydown", e => {
if (e.key == "Escape") prompt.close();
if (e.key == "Enter") confirmButton.click();
});
return prompt;
},
createBasicScrollList: function(id, title, options = {}) {
document.getElementsByClassName(NeatoLib.getClass("app"))[0].insertAdjacentHTML("beforeend", `
`);
let window = document.getElementById(id),
scroller = window.getElementsByClassName(`${id}-scroller`)[0],
backdrop = window.getElementsByClassName(`${id}-backdrop`)[0];
backdrop.addEventListener("click", () => window.outerHTML = "");
window.addEventListener("keydown", e => {
if (key == "Escape") backdrop.click();
});
return {
window: window,
scroller: scroller,
backdrop: backdrop
};
}
},
Keybinds: {
globalShortcut: require("electron").remote.globalShortcut,
attachListener: function(id, key, event, options = {}) {
if (key == undefined) return console.warn(id, "The passed key object is null!", key);
if (window.activeNeatoKeyListeners == undefined) window.activeNeatoKeyListeners = {};
let node = options.node || document;
if (window.activeNeatoKeyListeners[id]) {
console.warn("There is already a keybind listener with the id '" + id + "'!");
return;
}
window.activeNeatoKeyListeners[id] = {
heldKeys: [],
keydown: e => {
if (window.activeNeatoKeyListeners[id].heldKeys.indexOf(e.code) == -1) window.activeNeatoKeyListeners[id].heldKeys.push(e.code);
if (window.activeNeatoKeyListeners[id].heldKeys.indexOf(key.primaryKey) != -1) {
let heldModifiers = 0;
for (let i = 0; i < key.modifiers.length; i++)
if (window.activeNeatoKeyListeners[id].heldKeys.indexOf(key.modifiers[i]) != -1) heldModifiers++;
if (key.modifiers.length == heldModifiers && window.activeNeatoKeyListeners[id].heldKeys.length == heldModifiers + 1) event(e);
}
},
keyup: e => {
if (window.activeNeatoKeyListeners[id].heldKeys.indexOf(e.code) != -1) window.activeNeatoKeyListeners[id].heldKeys.splice(window.activeNeatoKeyListeners[id].heldKeys.indexOf(e.code), 1);
},
windowFocusLoss: () => {
window.activeNeatoKeyListeners[id].heldKeys = [];
}
};
node.addEventListener("keydown", window.activeNeatoKeyListeners[id].keydown);
node.addEventListener("keyup", window.activeNeatoKeyListeners[id].keyup);
window.addEventListener("blur", window.activeNeatoKeyListeners[id].windowFocusLoss);
return window.activeNeatoKeyListeners[id];
},
detachListener: function(id, node = document) {
if (window.activeNeatoKeyListeners == undefined) window.activeNeatoKeyListeners = {};
if (!window.activeNeatoKeyListeners[id]) {
console.warn("There is no keybind listener with the id '" + id + "'!");
return;
}
node.removeEventListener("keydown", window.activeNeatoKeyListeners[id].keydown);
node.removeEventListener("keyup", window.activeNeatoKeyListeners[id].keyup);
window.removeEventListener("blur", window.activeNeatoKeyListeners[id].windowFocusLoss);
delete window.activeNeatoKeyListeners[id];
},
registerGlobal: function(key, event, debug = false) {
try {
this.globalShortcut.register(key, event);
} catch (e) {
if (debug) console.error(e);
}
},
unregisterGlobal: function(key, debug = false) {
try {
this.globalShortcut.unregister(key);
} catch (e) {
if (debug) console.error(e);
}
}
},
Chatbox: {
get: function() {
let chat = document.getElementsByClassName(NeatoLib.getClass("chat"))[0];
return chat ? chat.getElementsByTagName("textarea")[0] : null;
},
setText: function(newText) {
NeatoLib.Chatbox.get().select();
document.execCommand("insertText", false, newText);
},
appendText: function(text) {
let chatbox = NeatoLib.Chatbox.get();
if (!chatbox) return;
chatbox.select();
document.execCommand("insertText", false, chatbox.value + text);
}
},
Modules: { //Based off of Zerebos' PluginLibrary. https://rauenzi.github.io/BetterDiscordAddons/docs/PluginLibrary.js
req: webpackJsonp.push([
[], {
"__extra_id__": (m, e, r) => m.exports = r
},
[
["__extra_id__"]
]
]),
find: function(filter) {
for (let i in this.req.c) {
if (this.req.c.hasOwnProperty(i)) {
const m = this.req.c[i].exports;
if (m && m.__esModule && m.default && filter(m.default)) return m.default;
if (m && filter(m)) return m;
}
}
console.warn("No module found with this filter!", filter);
return null;
},
findAll: function(filter) {
let found = [];
for (let i in this.req.c) {
if (this.req.c.hasOwnProperty(i)) {
let m = this.req.c[i].exports;
if (m && m.__esModule && m.default && filter(m.default)) found.push(m.default);
else if (m && filter(m)) found.push(m);
}
}
return found;
},
findAllByPropertyName: function(propName, filter) {
if (!filter) filter = m => m[propName];
let found = [];
for (let i in this.req.c) {
if (this.req.c.hasOwnProperty(i)) {
let m = this.req.c[i].exports;
if (m && m.__esModule && m.default && filter(m.default)) found.push(m.default[propName]);
else if (m && filter(m)) found.push(m[propName]);
}
}
return found;
},
findIndex: function(filter) {
for (let i in this.req.c) {
if (this.req.c.hasOwnProperty(i)) {
let m = this.req.c[i].exports;
if (m && m.__esModule && m.default && filter(m.default)) return i;
if (m && filter(m)) return i;
}
}
console.warn("No module found with this filter!", filter);
return null;
},
get: function(props) {
const cacheKey = typeof props == "string" ? props : props.join(",");
if (!this.cached) this.cached = {};
if (!this.cached[cacheKey]) this.cached[cacheKey] = typeof props == "string" ? this.find(module => module[props] != undefined) : this.find(module => props.every(prop => module[prop] != undefined));
return this.cached[cacheKey];
},
getById: function(id) {
return this.find(x => x._dispatchToken == "ID_" + id);
}
},
Updates: { //Based off of Zerebos' PluginLibrary. https://rauenzi.github.io/BetterDiscordAddons/docs/PluginLibrary.js
requestUpdateCheck: function(pluginName, url) {
require("request")(url, (err, response, res) => {
if (err) return console.error(pluginName, "Failed to check for updates!", err);
let latestVersion = res.match(/['"][0-9]+\.[0-9]+\.[0-9]+['"]/i);
if (!latestVersion) return;
latestVersion = latestVersion.toString().replace(/['"]/g, "").trim();
if(!window.PluginUpdates.plugins[url]) return NeatoLib.Updates.hideNotice(pluginName);
let versionOld = window.PluginUpdates.plugins[url].version.split(".");
let versionNew = latestVersion.split(".");
if(versionNew[0] > versionOld[0] || (versionNew[0] == versionOld[0] && versionNew[1] > versionOld[1]) || (versionNew[0] == versionOld[0] && versionNew[1] == versionOld[1] && versionNew[2] > versionOld[2]))
NeatoLib.Updates.displayNotice(pluginName, url);
else
NeatoLib.Updates.hideNotice(pluginName);
});
},
displayNotice: function(pluginName, url) {
if (document.getElementById("pluginNotice") == undefined) {
let classes = NeatoLib.Modules.get("noticeInfo");
document.getElementsByClassName(NeatoLib.getClass("app"))[0].insertAdjacentHTML("afterbegin", `
The following plugins have updates: `);
document.getElementById("pluginNoticeDismiss").addEventListener("click", () => document.getElementById("pluginNotice").outerHTML = "");
}
if (document.getElementById(pluginName + "-notice") == undefined) {
let element = document.createElement("span"),
outdated = document.getElementById("outdatedPlugins");
element.setAttribute("id", pluginName + "-notice");
element.innerText = pluginName;
element.addEventListener("click", () => NeatoLib.Updates.download(pluginName, url));
if (outdated.getElementsByTagName("span")[0] != undefined) outdated.insertAdjacentHTML("beforeend", ", ");
outdated.appendChild(element);
}
},
hideNotice: function(pluginName) {
let notice = document.getElementById(pluginName + "-notice");
if (notice) {
if (notice.nextSibling && notice.nextSibling.classList.contains("separator")) notice.nextSibling.remove();
else if (notice.previousSibling && notice.previousSibling.classList.contains("separator")) notice.previousSibling.remove();
notice.remove();
} else if (!document.querySelector("#outdatedPluings > span") && document.querySelector("#pluginNotice > .btn-reload") && document.querySelector("#pluginNotice .notice-message")) document.querySelector("#pluginNotice .notice-message").innerText = "To finish updating you need to reload.";
},
download: function(pluginName, url) {
let req = require("request"),
fs = require("fs"),
path = require("path");
req(url, (err, response, res) => {
if (err) return console.error(pluginName, "Failed to download update!", err);
let latestVersion = res.match(/['"][0-9]+\.[0-9]+\.[0-9]+['"]/i).toString().replace(/['"]/g, "").trim(),
fileName = url.split("/");
fileName = fileName[fileName.length - 1];
let file = path.join(NeatoLib.getPluginsFolderPath(), fileName);
fs.writeFileSync(file, res);
NeatoLib.showToast(`${pluginName} was updated to v${latestVersion}.`, "success");
if (!window.PluginUpdates.downloaded) {
window.PluginUpdates.downloaded = [];
let button = document.createElement("button");
button.className = "btn btn-reload btn-2o56RF button-1MICoQ size14-3iUx6q weightMedium-2iZe9B";
button.innerText = "Reload";
button.addEventListener("click", e => {
e.preventDefault();
window.location.reload(false);
});
let tooltip = document.createElement("div");
tooltip.className = NeatoLib.getClass("tooltip") + " " + NeatoLib.getClass("tooltip", "tooltipBottom") + " " + NeatoLib.getClass("tooltip", "tooltipBlack");
tooltip.style.maxWidth = "400px";
button.addEventListener("mouseenter", () => {
document.getElementsByClassName(NeatoLib.getClass("tooltip"))[0].appendChild(tooltip);
tooltip.innerText = window.PluginUpdates.downloaded.join(", ");
tooltip.style.left = button.getBoundingClientRect().left + (button.offsetWidth / 2) - (tooltip.offsetWidth / 2) + "px";
tooltip.style.top = button.getBoundingClientRect().top + button.offsetHeight + "px";
});
button.addEventListener("mouseleave", () => tooltip.remove());
document.getElementById("pluginNotice").appendChild(button);
}
window.PluginUpdates.plugins[url].version = latestVersion;
window.PluginUpdates.downloaded.push(pluginName);
NeatoLib.Updates.hideNotice(pluginName);
});
},
check: function(plugin, path) {
let url = path ? path : "https://rawgit.com/Metalloriff/BetterDiscordPlugins/master/" + plugin.getName().split(" ").join("") + ".plugin.js";
if (typeof window.PluginUpdates == "undefined") window.PluginUpdates = {
plugins: {}
};
window.PluginUpdates.plugins[url] = {
name: plugin.getName(),
raw: url,
version: plugin.getVersion()
};
NeatoLib.Updates.requestUpdateCheck(plugin.getName(), url);
if (typeof window.PluginUpdates.interval == "undefined") {
window.PluginUpdates.interval = setInterval(() => {
window.PluginUpdates.checkAll();
}, 7200000);
}
if (typeof window.PluginUpdates.checkAll == "undefined") {
window.PluginUpdates.checkAll = function() {
for (let key in this.plugins) {
NeatoLib.Updates.requestUpdateCheck(this.plugins[key].name, this.plugins[key].raw);
}
};
}
}
},
Data: {
save: function(name, key, data) {
try {
BdApi.setData(name, key, data);
} catch (err) {
console.warn(name, "failed to save data.", err);
}
},
load: function(name, key, fallback) {
try {
return $.extend(true, fallback ? fallback : {}, BdApi.getData(name, key));
} catch (err) {
console.warn(name, "failed to load data.", err);
}
return {};
}
},
Events: {
onPluginLoaded: function(plugin) {
NeatoLib.showToast(`[${plugin.getName()}]: Plugin loaded.`, "success");
console.log(plugin.getName(), "loaded.");
plugin.ready = true;
if (plugin.forceLoadTimeout) {
clearTimeout(plugin.forceLoadTimeout);
plugin.forceLoadTimeout = null;
delete plugin.forceLoadTimeout;
}
},
attach: function(eventType, event, options = {}) {
window.activeNeatoEvents.push({
callback: event,
type: eventType,
options: options
});
},
detach: function(eventType, event) {
let idx = window.activeNeatoEvents.findIndex(e => e.callback == event && e.type == eventType);
if (idx != -1) window.activeNeatoEvents.splice(idx, 1);
else console.warn("Event could not be found.", event);
}
},
ReactData: {
get: function(element) {
if (!(element instanceof Element)) return null;
return element[Object.keys(element).find(key => key.startsWith("__reactInternalInstance"))];
},
getEvents: function(element) {
if (!(element instanceof Element)) return null;
return element[Object.keys(element).find(key => key.startsWith("__reactEventHandlers"))];
},
getOwner: function(element) {
if (!(element instanceof Element)) return null;
let reactData = this.get(element);
if (reactData == undefined) return null;
for (let c = reactData.return; !_.isNil(c); c = c.return) {
if (_.isNil(c)) continue;
let owner = c.stateNode;
if (!_.isNil(owner) && !(owner instanceof HTMLElement)) return owner;
}
},
getProps: function(element) {
if (!(element instanceof Element)) return null;
let owner = this.getOwner(element);
return owner ? owner.props : null;
},
getProp: function(element, propKey) {
if (!(element instanceof Element)) return null;
let owner = this.getOwner(element);
if (!owner || !owner.props) return null;
let split = propKey.split("."),
obj = owner.props;
for (let i = 0; i < split.length; i++) {
obj = obj[split[i]];
if (!obj) return null;
}
return obj;
},
},
ContextMenu: {
create: function(items, event, options = {}) {
this.close();
let menu = document.createElement("div");
menu.classList.add(this.classes.contextMenu.split(" ")[0], document.getElementsByClassName("theme-dark")[0] != undefined ? "theme-dark" : "theme-light");
for (let i = 0; i < items.length; i++) menu.appendChild(items[i]);
if (options.style) menu.style = options.style;
menu.style.zIndex = 10000;
menu.style.top = event.clientY + "px";
menu.style.left = event.clientX + "px";
menu.style.position = 'relative';
let close = () => {
menu.remove();
document.removeEventListener("click", onClick);
document.removeEventListener("contextmenu", onClick);
document.removeEventListener("keyup", onKeyUp);
};
let onClick = e => {
if (!menu.contains(e.target)) close();
};
let onKeyUp = e => {
if (e.key == "Escape") close();
};
document.addEventListener("click", onClick);
setTimeout(() => {
document.addEventListener("contextmenu", onClick);
}, 0);
document.addEventListener("keyup", onKeyUp);
document.getElementById("app-mount").appendChild(menu);
return menu;
},
createGroup: function(items, options = {}) {
let element = document.createElement("div");
element.classList.add(this.classes.itemGroup.split(" ")[0]);
for (let i = 0; i < items.length; i++) element.appendChild(items[i]);
return element;
},
createItem: function(label, callback, options = {}) {
let element = document.createElement("div");
element.classList.add(this.classes.item.split(" ")[0], this.classes.itemBase.split(" ")[0], this.classes.clickable.split(" ")[0]);
element.innerHTML = "" + label + " ";
if (options.color) element.firstChild.style.color = options.color;
if (options.hint) NeatoLib.Tooltip.attach(options.hint, element);
if (options.description) element.innerHTML += `${options.description}
`;
if (callback) element.addEventListener("click", callback);
return element;
},
createSubMenu: function(label, items, options = {}) {
let element = document.createElement("div");
element.classList.add(this.classes.itemSubMenu.split(" ")[0], this.classes.itemBase.split(" ")[0], this.classes.clickable.split(" ")[0]);
let le = document.createElement("div");
le.classList.add(this.classes.label.split(" ")[0]);
le.innerText = label;
element.appendChild(le);
element.insertAdjacentHTML("beforeend", `
`);
if (options.color) element.style.color = options.color;
if (options.hint) element.innerHTML += `${options.hint}
`;
if (options.callback)
element.addEventListener("click", e => {
if (e.target.parentElement == element) options.callback(e);
});
let sm, hoveringOver;
element.addEventListener("mouseenter", () => {
let layer = document.createElement("div");
layer.classList.add(NeatoLib.getClass("layer"), "neato-cl");
layer.style.left = (element.getBoundingClientRect().width) + "px";
layer.style.top = (element.getBoundingClientRect().y - NeatoLib.DOM.searchForParentElementByClassName(element, NeatoLib.getClass("itemSubMenu")).getBoundingClientRect().y) + "px";
let subMenu = document.createElement("div");
subMenu.classList.add(this.classes.subMenuContext.split(" ")[0]);
let menu = document.createElement("div");
menu.classList.add(this.classes.contextMenu.split(" ")[0]);
for (let i = 0; i < items.length; i++) menu.appendChild(items[i]);
subMenu.appendChild(menu);
layer.appendChild(subMenu);
document.getElementsByClassName(NeatoLib.getClass("layerContainer"))[0].appendChild(layer);
sm = layer;
subMenu.addEventListener("mouseenter", e => {
hoveringOver = subMenu;
});
subMenu.addEventListener("mouseleave", () => {
setTimeout(() => {
if (hoveringOver == subMenu) hoveringOver = null;
}, 0);
});
element.appendChild(layer);
});
element.addEventListener("mouseleave", () => {
if (sm && !hoveringOver) sm.remove();
});
return element;
},
createToggle: function(label, value, callback, options = {}) {
let element = document.createElement("div");
element.classList.add(this.classes.item.split(" ")[0], this.classes.itemBase.split(" ")[0], this.classes.itemToggle.split(" ")[0]);
element.innerHTML = `
${label}
`;
let checkbox = element.getElementsByTagName("input")[0];
checkbox.checked = value;
if (options.color) element.style.color = options.color;
if (callback) element.addEventListener("click", () => {
checkbox.checked = !checkbox.checked;
callback(checkbox.checked);
});
return element;
},
get: function() {
return Array.from(document.getElementsByClassName(this.classes.contextMenu.split(" ")[0])).filter(x => x.style.display != "none")[0];
},
close: function() {
let cm = NeatoLib.ContextMenu.get();
if (cm) cm.style.display = "none";
}
},
Tooltip: {
attach: function(content, element, options = {}) {
if (element.tooltip != undefined) element.tooltip.detach();
const { side = "top", color, onShow, onHide, delay } = options;
let domChecker, delayTimeout;
element.tooltip = {
tooltip: undefined,
node: element,
event: {
mouseenter: () => {
let tooltip = document.createElement("div");
tooltip.classList.add(NeatoLib.getClass("tooltip"), NeatoLib.getClass("tooltip", "tooltip" + side.substr(0,1).toUpperCase() + side.substr(1)), NeatoLib.getClass("tooltip", "tooltipBlack"));
tooltip.innerText = content;
tooltip.style.pointerEvents = "none";
tooltip.style.zIndex = 15000;
if (color) tooltip.style.backgroundColor = color;
let tooltipContainer = document.createElement("div");
tooltipContainer.classList.add(NeatoLib.getClass("layerContainer", "layer"));
document.getElementsByClassName(NeatoLib.getClass("layerContainer"))[0].appendChild(tooltipContainer);
tooltipContainer.appendChild(tooltip);
tooltip.insertAdjacentHTML("afterbegin", `
`);
element.tooltip.tooltip = tooltipContainer;
let elementRect = element.getBoundingClientRect();
switch (side) {
case "top":
tooltipContainer.style.top = (elementRect.top - tooltipContainer.offsetHeight) + "px";
tooltipContainer.style.left = ((elementRect.left + (element.offsetWidth / 2)) - (tooltipContainer.offsetWidth / 2)) + "px";
break;
case "bottom":
tooltipContainer.style.top = (elementRect.top + element.offsetHeight) + "px";
tooltipContainer.style.left = ((elementRect.left + (element.offsetWidth / 2)) - (tooltipContainer.offsetWidth / 2)) + "px";
break;
case "right":
tooltipContainer.style.left = (elementRect.left + element.offsetWidth) + "px";
tooltip.style.top = ((elementRect.top + (element.offsetHeight / 2)) - (tooltipContainer.offsetHeight / 2)) + "px";
break;
case "left":
tooltipContainer.style.left = (elementRect.left - tooltipContainer.offsetWidth) + "px";
tooltipContainer.style.top = ((elementRect.top + (element.offsetHeight / 2)) - (tooltipContainer.offsetHeight / 2)) + "px";
break;
}
if (typeof onShow == "function") onShow(element.tooltip);
domChecker = setInterval(() => {
if (!document.contains(element)) {
tooltip.remove();
clearInterval(domChecker);
}
}, 200);
},
mouseleave: () => {
if (element.tooltip.tooltip) {
element.tooltip.tooltip.remove();
if (typeof onHide == "function") onHide(element.tooltip);
}
clearInterval(domChecker);
clearTimeout(delayTimeout);
}
},
detach: () => {
element.tooltip.event.mouseleave();
element.removeEventListener("mouseenter", element.tooltip.event.mouseenter);
element.removeEventListener("mouseleave", element.tooltip.event.mouseleave);
delete element.tooltip;
}
};
if (delay) {
const display = element.tooltip.event.mouseenter;
element.tooltip.event.mouseenter = () => delayTimeout = setTimeout(display, delay);
}
element.addEventListener("mouseenter", element.tooltip.event.mouseenter);
element.addEventListener("mouseleave", element.tooltip.event.mouseleave);
return element.tooltip;
}
},
Colors: {
DiscordDefaults: {
red: "#f04747",
blue: "#7289da",
green: "#43b581"
},
hexToRGB: function(hex, format = "R, G, B") {
return format.replace("R", parseInt(hex.substring(1, 7).substring(0, 2), 16)).replace("G", parseInt(hex.substring(1, 7).substring(2, 4), 16)).replace("B", parseInt(parseInt(hex.substring(1, 7).substring(4, 6), 16)));
},
getBrightness: function(color) {
if (!color) return 0;
let c = Array.from(color.split(","), n => parseInt(n.replace(/[^0-9]/g, "")));
return Math.sqrt(c[0] * c[0] * 0.241 + c[1] * c[1] * 0.691 + c[2] * c[2] * 0.068) / 255;
}
},
DOM: {
searchForParentElement: function(element, filter) {
if (!element) return null;
if (filter(element)) return element;
while (element && element.parentElement && element.parentElement != document) {
element = element.parentElement;
if (filter(element)) return element;
for (let i = 0; i < element.children.length; i++)
if (filter(element.children[i])) return element.children[i];
}
},
searchForParentElementByClassName: function(element, className) {
if (!element) return null;
if (element.classList.contains(className)) return element;
while (element && element.parentElement && element.parentElement != document) {
element = element.parentElement;
if (element.classList.contains(className)) return element;
for (let i = 0; i < element.children.length; i++)
if (element.children[i].classList.contains(className)) return element.children[i];
}
return null;
},
createElement: function(values, options = {}) {
let element = document.createElement(options.type || "div");
for (let key in values) element[key] = values[key];
return element;
},
sortChildren: function(element, sortFunc) {
let children = Array.from(element.children).sort(sortFunc || function(a, b) {
let x = a.innerText.toLowerCase(),
y = b.innerText.toLowerCase();
if (x < y) return -1;
else if (x > y) return 1;
return 0;
});
for (let i = 0; i < children.length; i++) element.appendChild(children[i]);
},
insertHTMLBefore: function(element, html) {
let e = document.createElement("div");
element.parentElement.insertBefore(e, element);
e.outerHTML = html;
return e;
},
insertAtIndex: function(idx, element, parent) {
if (idx >= parent.children.length) parent.appendChild(element);
else parent.insertBefore(element, parent.children[idx]);
},
insertHTMLAtIndex: function(idx, html, parent) {
let e = document.createElement("div");
this.insertAtIndex(idx, e, parent);
e.outerHTML = html;
return e;
}
},
Thread: {
sleep: function(timeout = 0) {
return new Promise(p => setTimeout(p, timeout));
}
},
downloadFile: async function(url, path, filename, onCompleted) {
filename = filename.split(/[:|?|%]/)[0];
const def = [url, path, filename, onCompleted];
let progressToast, id = path.replace(/[^a-z0-9]/g, "");
const error = function(err) {
if (!err) return;
if (id) NeatoLib.showProgressToast(id, "Error saving " + filename + ". Click to retry.", 1, 1, {
color: NeatoLib.Colors.DiscordDefaults.red,
progressText: "ERROR",
timeout: 5000
})
.addEventListener("click", function(e) {
NeatoLib.downloadFile(...def);
e.currentTarget.close();
});
throw err;
};
try {
const fs = require("fs"),
protocol = require(url.match(/[http&https]+/)[0]);
if (!path.endsWith("/")) path += "/";
path = path.split("?")[0] + filename;
if (fs.existsSync(path)) {
NeatoLib.showToast(`"${filename}" already exists, random characters will be appended to the file name!`, "error");
const fileExtension = "." + path.split(".")[path.split(".").length - 1];
path = path.split(fileExtension).join(`${Math.random().toString(36).substring(10)}${fileExtension}`);
}
id = path.replace(/[^a-z0-9]/g, "");
const startingToast = NeatoLib.showToast(`[${filename} ]: Preparing download...`);
const request = protocol.get(url, function(req) {
let data = [],
progress = 0,
length;
startingToast.close();
if (length = req.headers["content-length"]) progressToast = NeatoLib.showProgressToast(id, "Downloading " + filename + "...", progress, length, {
timeout: 10000
});
else progressToast = NeatoLib.showProgressToast(id, "Downloading " + filename + "...", 1, 1, {
color: NeatoLib.Colors.DiscordDefaults.blue,
progressText: "File size unknown",
timeout: 10000
});
req.on("data", function(dataChunk) {
data.push(dataChunk);
progress += dataChunk.length;
if (length) progressToast = NeatoLib.showProgressToast(id, "Downloading " + filename + "...", progress, length, {
timeout: 10000
});
});
req.on("end", function() {
if (data.length == 0) return error("URL is invalid");
progressToast = NeatoLib.showProgressToast(id, "Finished downloading " + filename, progress, length, {
timeout: 3000
});
progressToast.addEventListener("click", () => {
window.open("file:///" + path.substring(0, path.lastIndexOf("/")));
});
fs.writeFile(path, Buffer.concat(data), error);
if (onCompleted) onCompleted(path, url);
});
});
request.on("error", error);
request.end();
} catch (err) {
error(err);
}
},
requestFile: function(url, name = "unknown.png", onCompleted) {
const http = require("https");
const request = http.request(url, x => {
const data = [];
x.on("data", d => data.push(d));
x.on("end", () => {
if (onCompleted != undefined) onCompleted(new File([Buffer.concat(data)], name));
});
});
request.on("error", error => {
NeatoLib.showToast("Failed to request file! Error: " + error.message, "error");
});
request.end();
},
getClass: function(moduleName, className = moduleName, index = 0) {
let temp = NeatoLib.Modules.get(moduleName);
if(!temp || typeof temp[className] !== "string") return;
if(!temp[className]) return temp[moduleName].split(" ")[index];
return temp[className].split(" ")[index];
},
getClasses: function(classes, returnAll = true) {
var found = {};
for (var i = 0; i < classes.length; i++) {
var module = NeatoLib.Modules.get(classes[i]);
if (module != undefined) {
for (var ii in module) {
if (!returnAll && classes[i] != ii) continue;
found[ii] = module[ii];
}
}
}
return found;
},
getSelectedGuild: function() {
return NeatoLib.Modules.get("getGuild").getGuild(NeatoLib.Modules.get("getLastSelectedGuildId").getGuildId());
},
getSelectedGuildId: function() {
return NeatoLib.Modules.get("getLastSelectedGuildId").getGuildId();
},
getSelectedTextChannel: function() {
return NeatoLib.Modules.Stores.Channels.getChannel(NeatoLib.Modules.Stores.SelectedChannels.getChannelId());
},
getSelectedVoiceChannel: function() {
return NeatoLib.Modules.Stores.Channels.getChannel(NeatoLib.Modules.Stores.SelectedChannels.getVoiceChannelId());
},
monkeyPatchInternal: function(module, funcName, newFunc) {
const unpatched = module[funcName];
module[funcName] = function() {
return newFunc({
module: this,
args: arguments,
unpatch: () => module[funcName] = unpatched,
unpatched: unpatched,
callDefault: () => unpatched.apply(this, arguments),
callDefaultWithArgs: function() {
this.unpatched.apply(this.module, arguments);
}
});
};
return module[funcName].unpatch = () => module[funcName] = unpatched;
},
patchInternalFunction: function(functionName, newFunction, pluginName, replace = false) {
let module = NeatoLib.Modules.get(functionName);
if (module == undefined) return console.warn("No module with function '" + functionName + "' found!");
if (module[functionName + "_unpatched_" + pluginName] != undefined) return console.warn("This function is already patched by this plugin!");
module[functionName + "_unpatched_" + pluginName] = module[functionName];
module[functionName] = replace ? newFunction : function() {
newFunction.apply(module, arguments);
return module[functionName + "_unpatched_" + pluginName].apply(module, arguments);
};
},
unpatchInternalFunction: function(functionName, pluginName) {
let module = NeatoLib.Modules.get(functionName);
if (module == undefined) {
console.log("There are no modules that contain this function!");
return;
}
if (module[functionName + "_unpatched_" + pluginName] == undefined) {
console.log("This function is not patched!");
return;
}
module[functionName] = module[functionName + "_unpatched_" + pluginName];
delete module[functionName + "_unpatched_" + pluginName];
},
internalFunctionIsPatched: function(functionName, pluginName) {
let module = NeatoLib.Modules.get(functionName);
if (module == undefined) {
console.log("There are no modules that contain this function!");
return;
}
return module[functionName + "_unpatched_" + pluginName] != undefined;
},
patchInternalFunctions: function(functionNames, newFunction, pluginName, replace = false) {
for (let i = 0; i < functionNames.length; i++) NeatoLib.patchInternalFunction(functionNames[i], newFunction, pluginName, replace);
},
unpatchInternalFunctions: function(functionNames, pluginName) {
for (let i = 0; i < functionNames.length; i++) NeatoLib.unpatchInternalFunction(functionNames[i], pluginName);
},
getLocalUser: function() {
return NeatoLib.Modules.Stores.Users.getCurrentUser();
},
getLocalStatus: function() {
return NeatoLib.Modules.get("getApplicationActivity").getStatus(NeatoLib.getLocalUser().id);
},
browseForFile: function(callback, options = {}) {
let fileBrowser = document.createElement("input");
fileBrowser.type = "file";
fileBrowser.style.display = "none";
if (options.directory == true) {
fileBrowser.setAttribute("webkitdirectory", true);
fileBrowser.setAttribute("directory", true);
}
if (options.multiple == true) fileBrowser.setAttribute("multiple", true);
document.head.appendChild(fileBrowser);
fileBrowser.click();
fileBrowser.addEventListener("change", () => {
callback(options.multiple == true ? fileBrowser.files : fileBrowser.files[0]);
fileBrowser.outerHTML = "";
});
},
shuffleArray: function(array) {
let idx = array.length,
temp, random;
while (idx != 0) {
random = Math.floor(Math.random() * idx);
idx--;
temp = array[idx];
array[idx] = array[random];
array[random] = temp;
}
return array;
},
getPluginsFolderPath: function() {
let proc = require("process"),
path = require("path");
switch (proc.platform) {
case "win32":
return path.resolve(proc.env.appdata, "BetterDiscord/plugins/");
case "darwin":
return path.resolve(proc.env.HOME, "Library/Preferences/", "BetterDiscord/plugins/");
default:
return path.resolve(proc.env.HOME, ".config/", "BetterDiscord/plugins/");
}
},
getThemesFolderPath: function() {
let proc = require("process"),
path = require("path");
switch (proc.platform) {
case "win32":
return path.resolve(proc.env.appdata, "BetterDiscord/themes/");
case "darwin":
return path.resolve(proc.env.HOME, "Library/Preferences/", "BetterDiscord/themes/");
default:
return path.resolve(proc.env.HOME, ".config/", "BetterDiscord/themes/");
}
},
tryCreateToastContainer: function() {
if (!document.getElementsByClassName("toasts").length) {
const container = document.getElementsByClassName(NeatoLib.Modules.get(['sidebar', 'guilds']).guilds.split(" ")[0])[0].nextSibling,
memberlist = container.getElementsByClassName(NeatoLib.Modules.get("membersWrap").membersWrap)[0],
form = container ? container.getElementsByTagName("form")[0] : undefined,
left = container ? container.getBoundingClientRect().left : 310,
right = memberlist ? memberlist.getBoundingClientRect().left : 0,
width = right ? right - container.getBoundingClientRect().left : container.offsetWidth,
bottom = form ? form.offsetHeight : 80,
toastWrapper = document.createElement("div");
toastWrapper.classList.add("toasts");
toastWrapper.style.left = left + "px";
toastWrapper.style.width = width + "px";
toastWrapper.style.bottom = bottom + "px";
document.getElementsByClassName(NeatoLib.getClass("app"))[0].appendChild(toastWrapper);
}
},
showToast: function(text, type, options = {}) {
this.tryCreateToastContainer();
const toast = document.createElement("div");
toast.classList.add("toast");
if (typeof type == "string") toast.classList.add("toast-" + type);
if (options.icon) toast.classList.add("icon");
if (options.color) toast.style.background = options.color;
const destroy = toast.close = function() {
toast.classList.add("closing");
setTimeout(function() {
toast.remove();
if (!document.getElementsByClassName("toast").length) document.getElementsByClassName("toasts")[0].remove();
}, 300);
};
if (options.onClick) toast.addEventListener("click", options.onClick);
if (options.destroyOnClick) toast.addEventListener("click", destroy);
toast.innerHTML = text;
document.getElementsByClassName("toasts")[0].appendChild(toast);
setTimeout(destroy, options.timeout || 3000);
return toast;
},
showProgressToast: function(id, label, val, max, options = {}) {
let bar;
const destroy = function() {
clearTimeout(bar.destroyTimeout);
bar.classList.add("closing");
setTimeout(function() {
bar.remove();
if (!document.getElementsByClassName("toast").length) document.getElementsByClassName("toasts")[0].remove();
}, 300);
};
const updateBar = function() {
bar.getElementsByClassName("toast-prog-bar-label")[0].innerHTML = label;
const prog = bar.getElementsByClassName("toast-prog-bar-progress")[0];
if (options.progressText) prog.innerHTML = options.progressText;
else if (!isNaN(parseInt((val / max) * 100))) prog.innerText = parseInt((val / max) * 100) + "%";
else prog.innerText = "ERROR";
prog.style.width = ((val / max) * 500) + "px";
if (options.color) prog.style.background = options.color;
else prog.style.background = NeatoLib.Colors.DiscordDefaults.green;
clearTimeout(bar.destroyTimeout);
bar.destroyTimeout = setTimeout(destroy, options.timeout || 1500);
};
if (bar = document.getElementById("neato-toast-prog-bar-" + id)) {
updateBar();
return bar;
}
this.tryCreateToastContainer();
document.getElementsByClassName("toasts")[0].insertAdjacentHTML("beforeend",
``);
bar = document.getElementById("neato-toast-prog-bar-" + id);
bar.close = destroy;
if (options.backgroundColor) bar.style.backgroundColor = options.backgroundColor;
if (options.onClick) bar.addEventListener("click", options.onClick);
if (options.destroyOnClick) bar.addEventListener("click", destroy);
updateBar();
return bar;
},
injectCSS: function(css) {
let element = document.createElement("style");
element.type = "text/css";
element.innerText = css;
document.head.appendChild(element);
return {
element: element,
getStyle: selector => {
let selectorIDX = css.indexOf(selector);
if (selectorIDX == -1) return null;
return css.substring(selectorIDX, selectorIDX + css.substring(selectorIDX, css.length).indexOf("}")).split("{")[1].trim();
},
append: toAppend => {
css += toAppend;
element.innerText = css;
},
destroy: () => {
element.remove();
}
};
},
getSnowflakeCreationDate: function(id) {
const epoch = 1420070400000;
const toBinary = sf => {
let binary = "",
high = parseInt(sf.slice(0, -10)) || 0,
low = parseInt(sf.slice(-10));
while (low > 0 || high > 0) {
binary = String(low & 1) + binary;
low = Math.floor(low / 2);
if (high > 0) {
low += 5000000000 * (high % 2);
high = Math.floor(high / 2);
}
}
return binary;
};
return new Date(parseInt(toBinary(id).padStart(64).substring(0, 42), 2) + epoch);
},
setTimeout: function(func, delay) {
try {
const setTimeout = NeatoLib.Modules.get("_wrappedBuiltIns")._wrappedBuiltIns.find(([obj, name, func]) => obj == global && name == "setTimeout")[2];
return setTimeout(func, delay);
} finally {
return global.setTimeout(func, delay);
}
}
};
var Metalloriff = NeatoLib;
var mesquite = BdApi.Plugins.getAll().map(x => x.getName());
for (let pluginName of mesquite) {
if (typeof BdApi.Plugins.get(pluginName).onLibLoaded == "function" && !BdApi.Plugins.get(pluginName).ready) {
setTimeout(() => {
if (BdApi.Plugins.get(pluginName).onLibLoaded.toString().indexOf("NeatoLib.Events.onPluginLoaded") == -1) NeatoLib.Events.onPluginLoaded(BdApi.Plugins.get(pluginName));
}, 100);
}
}
if (window.activeNeatoEvents == undefined) window.activeNeatoEvents = [];
if (window.neatoObserver) window.neatoObserver.disconnect();
window.neatoObserver = new MutationObserver(mutations => {
let call = (type, ...args) => {
for (let i = 0; i < window.activeNeatoEvents.length; i++) {
if (window.activeNeatoEvents[i].type == type) {
if (typeof(window.activeNeatoEvents[i].callback) == "function") {
try {
window.activeNeatoEvents[i].callback(...args);
} catch (err) {
console.warn("Unable to call " + window.activeNeatoEvents[i].type + " event.", window.activeNeatoEvents[i].callback, err);
}
}
}
}
};
for (let i = 0; i < mutations.length; i++) {
if (mutations[i].removedNodes[0] != undefined && mutations[i].removedNodes[0] instanceof Element) {
if (mutations[i].removedNodes[0].id == "friends") {
call("switch");
}
}
let added = mutations[i].addedNodes[0];
if (added == undefined || !(added instanceof Element)) continue;
if (added.classList.contains(NeatoLib.Events.classes.layer)) call("settings");
if (added.id == "friends") call("switch");
if (added.classList.contains(NeatoLib.getClass("messagesWrapper")) || added.getElementsByClassName(NeatoLib.getClass("messagesWrapper"))[0] != undefined) call("switch");
if ((added.classList.contains(NeatoLib.getClass("message")) && !added.className.includes("sending")) || added.classList.contains(NeatoLib.getClass("cozyMessage"))) call("message");
if (window.neatoObserver.addedTextarea != (window.neatoObserver.addedTextarea = added.getElementsByClassName(NeatoLib.getClass("textArea"))[0]) && window.neatoObserver.addedTextarea) call("chatbox", window.neatoObserver.addedTextarea);
}
});
window.neatoObserver.observe(document, {
childList: true,
subtree: true
});
NeatoLib.Modules.Stores = {
Guilds: NeatoLib.Modules.get(["getGuild", "getGuilds"]),
Channels: NeatoLib.Modules.get(["getChannel", "getChannels"]),
SelectedChannels: NeatoLib.Modules.get(["getChannelId", "getVoiceChannelId"]),
Users: NeatoLib.Modules.get(["getUser", "getUsers"]),
Members: NeatoLib.Modules.get(["getMember", "getMembers"]),
};
NeatoLib.Events.classes = {
layer: NeatoLib.Modules.get("layer").layer.split(" ")[0],
socialLinks: NeatoLib.Modules.get("socialLinks").socialLinks.split(" ")[0]
};
NeatoLib.ContextMenu.classes = NeatoLib.Modules.get("contextMenu");
NeatoLib.getSelectedServer = NeatoLib.getSelectedGuild;
NeatoLib.getSelectedServerId = NeatoLib.getSelectedGuildId;
if (window.neatoStyles) window.neatoStyles.destroy();
window.neatoStyles = NeatoLib.injectCSS(`
.toast.has-prog-bar {
padding-top: 30px;
}
.toast-prog-bar-label {
position: absolute;
top: 8px;
}
.toast-prog-bar-background {
height: 25px;
width: 500px;
border-radius: 5px;
background: rgba(0,0,0,0.3);
text-align: center;
line-height: 25px;
}
.toast-prog-bar-progress {
height: 25px;
border-radius: 5px;
background: green;
text-align: center;
line-height: 25px;
position: relative;
top: 0;
padding: 0;
transition: all 0.3s;
overflow: hidden;
}
/* Below is CSS from Zerebos' PluginLibrary. https://rauenzi.github.io/BetterDiscordAddons/docs/PluginLibrary.js */
#pluginNotice {-webkit-app-region: drag;border-radius:0;} #outdatedPlugins {font-weight:700;} #outdatedPlugins>span {-webkit-app-region: no-drag;color:#fff;cursor:pointer;} #outdatedPlugins>span:hover {text-decoration:underline;}
.toasts{position:fixed;display:flex;top:0;flex-direction:column;align-items:center;justify-content:flex-end;pointer-events:none;z-index:4000}@keyframes toast-up{from{transform:translateY(0);opacity:0}}.toast{animation:toast-up .3s ease;transform:translateY(-10px);background:#36393F;padding:10px;border-radius:5px;box-shadow:0 0 0 1px rgba(32,34,37,.6),0 2px 10px 0 rgba(0,0,0,.2);font-weight:500;color:#fff;user-select:text;font-size:14px;opacity:1;margin-top:10px}@keyframes toast-down{to{transform:translateY(0);opacity:0}}.toast.closing{animation:toast-down .2s ease;animation-fill-mode:forwards;opacity:1;transform:translateY(-10px)}.toast.icon{padding-left:30px;background-size:20px 20px;background-repeat:no-repeat;background-position:6px 50%}.toast.toast-info{background-color:#4a90e2}.toast.toast-info.icon{background-image:url(data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjRkZGRkZGIiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4gICAgPHBhdGggZD0iTTAgMGgyNHYyNEgweiIgZmlsbD0ibm9uZSIvPiAgICA8cGF0aCBkPSJNMTIgMkM2LjQ4IDIgMiA2LjQ4IDIgMTJzNC40OCAxMCAxMCAxMCAxMC00LjQ4IDEwLTEwUzE3LjUyIDIgMTIgMnptMSAxNWgtMnYtNmgydjZ6bTAtOGgtMlY3aDJ2MnoiLz48L3N2Zz4=)}.toast.toast-success{background-color:#43b581}.toast.toast-success.icon{background-image:url(data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjRkZGRkZGIiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4gICAgPHBhdGggZD0iTTAgMGgyNHYyNEgweiIgZmlsbD0ibm9uZSIvPiAgICA8cGF0aCBkPSJNMTIgMkM2LjQ4IDIgMiA2LjQ4IDIgMTJzNC40OCAxMCAxMCAxMCAxMC00LjQ4IDEwLTEwUzE3LjUyIDIgMTIgMnptLTIgMTVsLTUtNSAxLjQxLTEuNDFMMTAgMTQuMTdsNy41OS03LjU5TDE5IDhsLTkgOXoiLz48L3N2Zz4=)}.toast.toast-danger,.toast.toast-error{background-color:#f04747}.toast.toast-danger.icon,.toast.toast-error.icon{background-image:url(data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjRkZGRkZGIiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4gICAgPHBhdGggZD0iTTEyIDJDNi40NyAyIDIgNi40NyAyIDEyczQuNDcgMTAgMTAgMTAgMTAtNC40NyAxMC0xMFMxNy41MyAyIDEyIDJ6bTUgMTMuNTlMMTUuNTkgMTcgMTIgMTMuNDEgOC40MSAxNyA3IDE1LjU5IDEwLjU5IDEyIDcgOC40MSA4LjQxIDcgMTIgMTAuNTkgMTUuNTkgNyAxNyA4LjQxIDEzLjQxIDEyIDE3IDE1LjU5eiIvPiAgICA8cGF0aCBkPSJNMCAwaDI0djI0SDB6IiBmaWxsPSJub25lIi8+PC9zdmc+)}.toast.toast-warn,.toast.toast-warning{background-color:#FFA600;color:#fff}.toast.toast-warn.icon,.toast.toast-warning.icon{background-image:url(data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjRkZGRkZGIiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4gICAgPHBhdGggZD0iTTAgMGgyNHYyNEgweiIgZmlsbD0ibm9uZSIvPiAgICA8cGF0aCBkPSJNMSAyMWgyMkwxMiAyIDEgMjF6bTEyLTNoLTJ2LTJoMnYyem0wLTRoLTJ2LTRoMnY0eiIvPjwvc3ZnPg==)}
`);
if (!document.getElementById("material-icons")) {
const link = document.createElement("link");
link.id = "material-icons";
link.rel = "stylesheet";
link.href = "https://fonts.googleapis.com/icon?family=Material+Icons";
document.head.appendChild(link);
}
================================================
FILE: MentionAliases.plugin.js
================================================
/**
* @name MentionAliases
* @invite yNqzuJa
* @authorLink https://github.com/Metalloriff
* @donate https://www.paypal.me/israelboone
* @website https://metalloriff.github.io/toms-discord-stuff/
* @source https://github.com/Metalloriff/BetterDiscordPlugins/blob/master/MentionAliases.plugin.js
*/
module.exports = (() => {
const config =
{
info: {
name: "MentionAliases",
authors: [{
name: "Metalloriff",
discord_id: "264163473179672576",
github_username: "metalloriff",
twitter_username: "Metalloriff"
}, {
name: "Strencher",
discord_id: "415849376598982656",
github_username: "Strencher",
twitter_username: "Strencher3"
}],
version: "2.0.0",
description: "Allows you to define custom aliases for users that you can @mention them with, with the option to display the alias next to their username.",
github: "https://github.com/Metalloriff/BetterDiscordPlugins/blob/master/MentionAliases.plugin.js",
github_raw: "https://raw.githubusercontent.com/Metalloriff/BetterDiscordPlugins/master/MentionAliases.plugin.js"
},
defaultConfig: [{
id: "general",
title: "general settings",
type: "category",
collapsible: true,
shown: false,
settings: [{
id: "displayTags",
title: "Display Alias Tags",
note: "Whether or not to display the alias tag next to the relevant username in chat.",
type: "switch",
value: true
}]
}],
changelog: [{
title: "2.0 rewrite",
type: "fixed",
items: [
"MentionAliases has been rewritten and should work better in the long run. If you notice any bugs that are not listed below, please report them to me.",
]
}, {
title: "known bugs",
type: "progress",
items: [
"Tags will not refresh after modifying a user's alias until you switch channels."
]
}]
};
return !global.ZeresPluginLibrary ? class {
constructor() { this._config = config; }
getName = () => config.info.name;
getAuthor = () => config.info.description;
getVersion = () => 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 (err, res, body) => {
if (err) 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) => { try {
const {
WebpackModules,
DiscordModules: { UserStore, UserStatusStore, React },
PluginUtilities,
Utilities,
Patcher
} = Api;
const { getCurrentUser } = UserStore;
var f = s => WebpackModules.find(s), d = s => WebpackModules.getByDisplayName(s), p = function() { return WebpackModules.getByProps(...arguments); };
const { MenuItem, MenuGroup } = f(m => m.MenuItem && !m.default);
const Popouts = p("openPopout");
const Modals = p("openModal");
const { ComponentDispatch } = p("ComponentDispatch");
const insertText = content => ComponentDispatch.dispatchToLastSubscribed("INSERT_TEXT", { content });
let fetchedModules = {};
const Components = {
...p("Title"),
ModalRoot: p("ModalRoot").ModalRoot,
AutocompletePopout: d("FluxContainer(AutocompletePopout)"),
Avatar: p("AnimatedAvatar").default,
Tooltip: d("Tooltip"),
_input: d("TextInput"),
Flex: d("Flex"),
Button: p("DropdownSizes"),
Icon: props => {
if (!fetchedModules[props.name])
fetchedModules[props.name] = WebpackModules.find(m => m.id && typeof m.keys === "function" && m.keys().includes("./Activity"))("./" + props.name);
return React.createElement(fetchedModules[props.name].default, props);
},
Input: (props, ref) =>
React.createElement(Components._input, {
value: props.value,
placeholder: props.placeholder,
ref: e => e && (ref = e),
onChange: val => {
ref.props.value = val;
ref.forceUpdate();
props.onChange(val);
}
})
};
const classes = {
...p("header", "botTag", "listAvatar"),
...p("textAreaHeight", "channelTextArea", "highlighted"),
...p("botTagRegular", "botTag")
};
f = d = p = null;
const Item = ({ user, alias }) =>
React.createElement(
"div",
{
style: { display: "flex" },
children: [
React.createElement(
Components.Avatar,
{
src: user.getAvatarURL(),
isMobile: false,
isTyping: false,
size: "SIZE_40"
}
),
React.createElement(
"div",
{
className: classes.listRowContent,
style: { marginLeft: 5 },
children: [
React.createElement(
"div",
{
style: {
textTransform: "none",
marginTop: 12
},
className: classes.listName,
children: alias
}
)
]
}
)
]
}
);
class Popout extends React.Component {
renderItem(item) {
if (!item)
return null;
return React.createElement(
Item,
{
user: item.user,
alias: item.alias,
onRemove: () => {
this.items.splice(this.items.indexOf(item), 1);
this.forceUpdate();
}
}
);
}
render() {
const { label, onClose, onSelect, placeholder, items } = this.props;
return React.createElement(
Components.AutocompletePopout,
{
label,
onClose,
onSelect,
placeholder,
keyboardModeEnabled: false,
onFilterResults: filter =>
this.items = items.filter(e =>
(filter(e.alias.toLowerCase()) ||
filter(e.user.username.toLowerCase()))),
onRenderResult: item => this.renderItem(item),
position: "top",
sections: [null]
}
);
}
}
const AliasTag = ({ alias }) =>
React.createElement(
"span",
{
className: [classes.botTagRegular, classes.botTag, classes.px].join(" "),
children:
React.createElement(
"span",
{
className: classes.botText,
children: alias.value
}
)
}
);
return class MentionAliases extends Plugin {
constructor() {
super();
}
getDataName = () => this.getName() + "." + getCurrentUser().id;
loadSettings = s => PluginUtilities.loadSettings(this.getDataName(), this.defaultSettings || s);
saveSettings = s => PluginUtilities.saveSettings(this.getDataName(), this.settings || s);
async showChangelog(footer) {
try { footer = (await WebpackModules.getByProps("getUser", "acceptAgreements").getUser("264163473179672576")).tag + " | https://discord.gg/yNqzuJa"; }
finally { super.showChangelog(footer); }
}
onStart() {
if (!Array.isArray(this.settings.aliases))
this.settings.aliases = [];
this.patchAutoComplete();
this.patchMessageHeader();
this.patchUserContextMenu();
this.patchMentions();
this.patchTextAreaContainer();
}
patchAutoComplete() {
const { MENTIONS } = WebpackModules.getByProps("AUTOCOMPLETE_OPTIONS").AUTOCOMPLETE_OPTIONS;
Patcher.after(MENTIONS, "queryResults", (_, [, query], props) => {
for (let alias of this.settings.aliases) {
const user = UserStore.getUser(alias.userId);
const renderer = props.users.find(u => u.user.id == alias.userId);
if (user && query && alias.value.toLowerCase().includes(query.toLowerCase())) {
if (renderer)
renderer.nick = alias.value;
else
props.users.push({
comparator: user.usernameNormalized,
nick: alias.value,
score: 10,
status: UserStatusStore.getStatus(alias.userId),
user
});
}
}
});
}
patchMessageHeader() {
Patcher.after(WebpackModules.getByProps("MessageTimestamp"), "default", (_, [props], re) => {
if (!this.settings.general.displayTags)
return;
const children = Utilities.getNestedProp(re, "props.children.1.props.children");
const alias = this.settings.aliases.find(u => u.userId == props.message.author.id);
if (!Array.isArray(children) || !alias)
return;
children.splice(
2, 0,
React.createElement(
AliasTag,
{ alias }
)
);
});
}
patchUserContextMenu() {
const menus = [
WebpackModules.find(m => m.default && m.default.displayName == "GuildChannelUserContextMenu"),
WebpackModules.find(m => m.default && m.default.displayName == "DMUserContextMenu")
];
for (let menu of menus)
Patcher.after(menu, "default", (_, [args], re) => {
const contextMenu = Utilities.getNestedProp(re, "props.children.props.children");
const registered = this.settings.aliases.findIndex(u => u.userId == args.user.id);
if (!Array.isArray(contextMenu))
return;
contextMenu.push(React.createElement(
MenuGroup,
{
children:
React.createElement(
MenuItem,
{
label: "Mention Aliases",
id: "ma-submenu",
children: [
registered > -1 ?
React.createElement(
MenuItem,
{
label: "Remove Alias",
id: "ma-remove",
color: "colorDanger",
action: () => {
this.settings.aliases.splice(registered, 1);
this.saveSettings();
}
}
) : null,
React.createElement(
MenuItem,
{
label: (registered > -1 ? "Edit" : "Set") + " Alias",
id: "ma-edit-remove",
action: () => this.openAliasModal(args.user)
}
)
]
}
)
}
));
});
}
patchMentions() {
Patcher.after(WebpackModules.getByProps("UserMention"), "UserMention", (_, [args], re) => {
const alias = this.settings.aliases.find(u => u.userId == args.id);
if (alias) {
const old = re.props.children;
re.props.children = e => {
const render = old(e);
render.props.children = "@" + alias.value;
return render;
};
return re;
}
});
Patcher.after(WebpackModules.getByDisplayName("DeprecatedPopout").prototype, "render", (self, _, re) => {
if (!re.props.className.includes("mention"))
return re;
const props = self.props.render();
if (!props || !props.props.userId)
return re;
const alias = this.settings.aliases.find(u => u.userId == props.props.userId);
if (alias)
re.props.children = ["@", alias.value];
return re;
});
}
patchTextAreaContainer() {
const TextAreaContainer = WebpackModules.find(m => m.type && m.type.render && m.type.render.displayName == "ChannelTextAreaContainer");
Patcher.after(TextAreaContainer.type, "render", (_, [args], re) => {
const children = Utilities.getNestedProp(re, "props.children.props.children.1.props.children.props.children.2.props.children");
if (!Array.isArray(children))
return;
children.unshift(React.createElement(
Components.Tooltip,
{
text: "Open Aliases Menu",
position: "top",
color: "black"
},
_props => React.createElement(
"div",
{
className: classes.buttonContainer,
children:
React.createElement(
Components.Icon,
{
..._props,
className: classes.button,
name: "At",
style: {
color: "var(--interactive-normal)",
cursor: "pointer",
marginTop: 6
},
onClick: e =>
Popouts.openPopout(
e.target.parentElement,
{
render: props =>
React.createElement(
Popout,
{
onClose: () => props.onClose(),
onSelect: item => {
props.onClose();
insertText(`<@${item.user.id}>`);
},
label: "User:",
placeholder: "Search for user",
items: [
...this.settings.aliases.map(e => ({
user: UserStore.getUser(e.userId),
alias: e.value
}))
].filter(e => e && e.user)
}
)
},
"mention-aliases"
)
}
)
}
)
));
});
}
openAliasModal(user) {
const settings = this.settings.aliases.find(u => u.userId == user.id);
let value;
Modals.openModal(props =>
React.createElement(
Components.ModalRoot,
{
...props,
children: [
React.createElement(
"h1",
{
style: {
fontWeight: "bold",
textAlign: "center",
color: "white",
margin: 5
}
},
"Define custom alias for " + user.username
),
React.createElement(
"div",
{ style: { alignSelf: "center" } },
React.createElement(
"div",
{ style: { margin: 15 } },
React.createElement(
Components.Input, {
value: settings ? settings.value : user.username,
placeholder: "Set Alias",
onChange: v => (value = v)
}
)
)
),
React.createElement(
"div",
{
style: {
position: "absolute",
bottom: 5,
right: 5,
display: "flex",
flexDirection: "row"
},
children: [
React.createElement(
Components.Button,
{
children: "Save",
onClick: () => {
this.settings.aliases.push({
value,
userId: user.id
});
this.saveSettings();
props.onClose();
},
style: { margin: 5 }
}
),
React.createElement(
Components.Button,
{
children: "Cancel",
onClick: () => props.onClose(),
style: { margin: 5 }
}
)
]
}
)
]
}
));
}
onStop() {
Patcher.unpatchAll();
}
}
} catch (e) { console.error(e); }};
return plugin(Plugin, Api);
})(global.ZeresPluginLibrary.buildPlugin(config));
})();
================================================
FILE: NateUtilities.plugin.js
================================================
//META{"name":"NateUtilities"}*//
class NateUtilities {
getName() { return "NateUtilities"; }
getDescription() { return "For all of your ear and brain saving needs! If you don't know what this plugin is, it's not for you, just ignore it."; }
getVersion() { return "0.0.2"; }
getAuthor() { return "Metalloriff"; }
getChanges() {
return {
};
}
load() {}
start() {
let libLoadedEvent = () => {
try{ this.onLibLoaded(); }
catch(err) { console.error(this.getName(), "fatal error, plugin could not be started!", err); }
};
let lib = document.getElementById("NeatoBurritoLibrary");
if(lib == undefined) {
lib = document.createElement("script");
lib.setAttribute("id", "NeatoBurritoLibrary");
lib.setAttribute("type", "text/javascript");
lib.setAttribute("src", "https://rawgit.com/NeatoLib/BetterDiscordPlugins/master/Lib/NeatoBurritoLibrary.js");
document.head.appendChild(lib);
}
if(typeof window.NeatoLib !== "undefined") libLoadedEvent();
else lib.addEventListener("load", libLoadedEvent);
}
getSettingsPanel() {
setTimeout(() => {
NeatoLib.Settings.pushElement(NeatoLib.Settings.Elements.createKeybindInput("local mute nate", this.settings.muteHotkey, newKey => {
this.unregisterKeybinds();
if(newKey) {
this.settings.muteHotkey = newKey;
this.registerKeybinds();
this.saveSettings();
} else PluginUtilities.showToast("You did not input anything!", { type : "error" });
}, { description : "For when your ears and/or brain need a break.", global : true }), this.getName());
NeatoLib.Settings.pushElement(NeatoLib.Settings.Elements.createKeybindInput("server mute nate", this.settings.serverMuteHotkey, newKey => {
this.unregisterKeybinds();
if(newKey) {
this.settings.serverMuteHotkey = newKey;
this.registerKeybinds();
this.saveSettings();
} else PluginUtilities.showToast("You did not input anything!", { type : "error" });
}, { description : "For when you feel like being a hero.", global : true }), this.getName());
NeatoLib.Settings.pushElement(NeatoLib.Settings.Elements.createKeybindInput("server deafen nate", this.settings.serverDeafenHotkey, newKey => {
this.unregisterKeybinds();
if(newKey) {
this.settings.serverDeafenHotkey = newKey;
this.registerKeybinds();
this.saveSettings();
} else PluginUtilities.showToast("You did not input anything!", { type : "error" });
}, { description : "For when you need to talk behind Nate's back.", global : true }), this.getName());
NeatoLib.Settings.pushChangelogElements(this);
}, 0);
return NeatoLib.Settings.Elements.pluginNameLabel(this.getName());
}
saveSettings() {
NeatoLib.Settings.save(this);
}
onLibLoaded() {
NeatoLib.Updates.check(this);
this.settings = NeatoLib.Settings.load(this, {
displayUpdateNotes : true,
muteHotkey : "Alt + N",
serverMuteHotkey : "Shift + Alt + N",
serverDeafenHotkey : "Control + Alt + N"
});
NeatoLib.Events.onPluginLoaded(this);
//if(this.settings.displayUpdateNotes) NeatoLib.Changelog.compareVersions(this.getName(), this.getChanges());
this.registerKeybinds();
this.initialized = true;
this.onSwitch();
}
onSwitch() {
if(!this.initialized) return;
this.selectedServer = NeatoLib.getSelectedTextChannel();
this.selectedVoiceChannel = NeatoLib.getSelectedVoiceChannel();
}
registerKeybinds() {
let nate = "209024642697003008",
toggleLocalMute = NeatoLib.Modules.get("toggleLocalMute").toggleLocalMute,
isLocalMuted = NeatoLib.Modules.get("isLocalMute").isLocalMute,
serverActionModule = NeatoLib.Modules.get(["setServerMute", "setServerDeaf"]),
getVoiceStates = NeatoLib.Modules.get("getVoiceStates").getVoiceStates;
NeatoLib.Keybinds.registerGlobal(this.settings.muteHotkey, () => {
toggleLocalMute(nate);
if(isLocalMuted(nate)) NeatoLib.showToast("I got ya, fam!", "success");
else NeatoLib.showToast("You're gonna regret that.", "error");
});
NeatoLib.Keybinds.registerGlobal(this.settings.serverMuteHotkey, () => {
if(this.selectedServer && this.selectedVoiceChannel) {
let voiceStates = getVoiceStates(this.selectedServer.id);
if(voiceStates[nate] == undefined) {
NeatoLib.showToast("Nate is not here, you're safe!", "success");
return;
}
serverActionModule.setServerMute(this.selectedServer.id, nate, !voiceStates[nate].mute);
if(!voiceStates[nate].mute) NeatoLib.showToast("You should be proud!", "success");
else NeatoLib.showToast("Sick fuck!", "error");
}
});
NeatoLib.Keybinds.registerGlobal(this.settings.serverDeafenHotkey, () => {
if(this.selectedServer && this.selectedVoiceChannel) {
let voiceStates = getVoiceStates(this.selectedServer.id);
if(voiceStates[nate] == undefined) {
NeatoLib.showToast("Nate is not here, you're safe!", "success");
return;
}
serverActionModule.setServerDeaf(this.selectedServer.id, nate, !voiceStates[nate].deaf);
if(!voiceStates[nate].deaf) NeatoLib.showToast("That Nate kid is an idiot.", "success");
else NeatoLib.showToast("Shut up guys!", "error");
}
});
}
unregisterKeybinds() {
NeatoLib.Keybinds.unregisterGlobal(this.settings.muteHotkey);
NeatoLib.Keybinds.unregisterGlobal(this.settings.serverMuteHotkey);
NeatoLib.Keybinds.unregisterGlobal(this.settings.serverDeafenHotkey);
}
stop() {
this.unregisterKeybinds();
}
}
================================================
FILE: OpenLinksInDiscord.plugin.js
================================================
//META{"name":"OpenLinksInDiscord","website":"https://metalloriff.github.io/toms-discord-stuff/","source":"https://github.com/Metalloriff/BetterDiscordPlugins/blob/master/OpenLinksInDiscord.plugin.js"}*//
class OpenLinksInDiscord {
getName() { return "OpenLinksInDiscord"; }
getDescription() { return "Opens links in a new window in Discord, instead of in your web browser. Hold shift to open links normally."; }
getVersion() { return "1.1.4"; }
getAuthor() { return "Metalloriff"; }
load() {}
start() {
let libLoadedEvent = () => {
try{ this.onLibLoaded(); }
catch(err) { console.error(this.getName(), "fatal error, plugin could not be started!", err); try { this.stop(); } catch(err) { console.error(this.getName() + ".stop()", err); } }
};
let lib = document.getElementById("NeatoBurritoLibrary");
if(!lib) {
lib = document.createElement("script");
lib.id = "NeatoBurritoLibrary";
lib.type = "text/javascript";
lib.src = "https://rawgit.com/Metalloriff/BetterDiscordPlugins/master/Lib/NeatoBurritoLibrary.js";
document.head.appendChild(lib);
}
this.forceLoadTimeout = setTimeout(libLoadedEvent, 30000);
if(typeof window.NeatoLib !== "undefined") libLoadedEvent();
else lib.addEventListener("load", libLoadedEvent);
}
getSettingsPanel() {
let save = () => NeatoLib.Settings.save(this);
setTimeout(() => {
NeatoLib.Settings.pushElements([
NeatoLib.Settings.Elements.createToggleGroup("ld-tg", "Keys", [
{ title : "Control/command", value : "ctrlKey", setValue : this.settings.ctrlKey },
{ title : "Shift", value : "shiftKey", setValue : this.settings.shiftKey },
{ title : "Alt", value : "altKey", setValue : this.settings.altKey }
], c => {
this.settings[c.value] = !this.settings[c.value];
save();
}),
NeatoLib.Settings.Elements.createToggleSwitch("Open in browser by default (holding keys above will open them in Discord)", this.settings.reverse, () => {
this.settings.reverse = !this.settings.reverse;
save();
})
], this.getName());
}, 0);
return NeatoLib.Settings.Elements.pluginNameLabel(this.getName());
}
onLibLoaded() {
NeatoLib.Updates.check(this);
this.settings = NeatoLib.Settings.load(this, {
ctrlKey : false,
shiftKey : true,
altKey : false,
reverse : false
});
this.event = e => {
if(e.target.localName == "a" && e.target.href && e.target.href.startsWith("http") && !e.target.parentElement.className.includes("channel") && !e.target.href.includes("/channels/")) {
if((!this.settings.ctrlKey || e.ctrlKey) && (!this.settings.shiftKey || e.shiftKey) && (!this.settings.altKey || e.altKey)) {
if(this.settings.reverse) {
this.onClickLink(e);
}
} else if(!this.settings.reverse) {
this.onClickLink(e);
}
}
};
document.addEventListener("click", this.event);
this.electron = require("electron");
NeatoLib.Events.onPluginLoaded(this);
}
onClickLink(e) {
let window = new this.electron.remote.BrowserWindow({ frame : true, resizeable : true, show : true, webPreferences : { nodeIntegration : false, nodeIntegrationInWorker : false }});
window.maximize();
window.setMenu(null);
window.loadURL(e.target.href);
e.preventDefault();
}
stop() {
document.removeEventListener("click", this.event);
}
}
================================================
FILE: PinCollapsedChannels.plugin.js
================================================
//META{"name":"PinCollapsedChannels","website":"https://metalloriff.github.io/toms-discord-stuff/","source":"https://github.com/Metalloriff/BetterDiscordPlugins/blob/master/PinCollapsedChannels.plugin.js"}*//
class PinCollapsedChannels {
getName() { return "PinCollapsedChannels"; }
getDescription() { return "Allows you to pin channels on collapsed categories, similar to if there was an unread message."; }
getVersion() { return "0.0.2"; }
getAuthor() { return "Metalloriff"; }
getChanges() {
return {
};
}
load() {}
start() {
const libLoadedEvent = () => {
try{ this.onLibLoaded(); }
catch(err) { console.error(this.getName(), "fatal error, plugin could not be started!", err); try { this.stop(); } catch(err) { console.error(this.getName() + ".stop()", err); } }
};
let lib = document.getElementById("NeatoBurritoLibrary");
if (!lib) {
lib = document.createElement("script");
lib.id = "NeatoBurritoLibrary";
lib.type = "text/javascript";
lib.src = "https://rawgit.com/Metalloriff/BetterDiscordPlugins/master/Lib/NeatoBurritoLibrary.js";
document.head.appendChild(lib);
}
this.forceLoadTimeout = setTimeout(libLoadedEvent, 30000);
if (typeof window.NeatoLib !== "undefined") libLoadedEvent();
else lib.addEventListener("load", libLoadedEvent);
}
get settingFields() {
return {
pinnedChannels: { label: "Pinned Channel IDs", description: "Hint: you can right click on channels to pin and unpin them.", type: "string", array: true }
};
}
get defaultSettings() {
return {
displayUpdateNotes: true,
pinnedChannels: []
};
}
getSettingsPanel() {
return NeatoLib.Settings.createPanel(this);
}
saveSettings() {
NeatoLib.Settings.save(this);
}
onLibLoaded() {
this.settings = NeatoLib.Settings.load(this);
if (!NeatoLib.hasRequiredLibVersion(this, "0.5.19")) return;
NeatoLib.Updates.check(this);
this.unpatch = NeatoLib.monkeyPatchInternal(NeatoLib.Modules.find(m => m.default && m.default.toString().search(/return!0;return \w&&\w\.default\.isGuildCollapsed\(\w\)}/) !== -1), "default", e => {
if (this.settings.pinnedChannels.includes(e.args[0].id)){
return false;
}
else return e.callDefault();
});
document.addEventListener("contextmenu", this.contextEvent = e => this.onContextMenu(e));
//if (this.settings.displayUpdateNotes) NeatoLib.Changelog.compareVersions(this.getName(), this.getChanges());
NeatoLib.Events.onPluginLoaded(this);
}
onContextMenu(e) {
const channel = NeatoLib.ReactData.getProp(NeatoLib.DOM.searchForParentElement(e.target, element => element.className && element.className.includes && element.className.includes("containerDefault")), "channel"),
contextMenu = NeatoLib.ContextMenu.get();
if (!channel || !contextMenu || !channel.parent_id || channel.type != 0) return;
const subItems = [];
if (this.settings.pinnedChannels.includes(channel.id)) {
subItems.push(NeatoLib.ContextMenu.createItem("Unpin Channel", () => {
this.settings.pinnedChannels.splice(this.settings.pinnedChannels.indexOf(channel.id), 1);
this.saveSettings();
NeatoLib.ContextMenu.close();
}));
} else {
subItems.push(NeatoLib.ContextMenu.createItem("Pin Channel", () => {
this.settings.pinnedChannels.push(channel.id);
this.saveSettings();
NeatoLib.ContextMenu.close();
}));
}
contextMenu.firstChild.appendChild(NeatoLib.ContextMenu.createSubMenu("Pin Collapsed Channels", subItems));
}
stop() {
this.unpatch();
document.removeEventListener("contextmenu", this.contextEvent);
}
}
================================================
FILE: PinPluginsAndThemes.plugin.js
================================================
//META{"name":"PinPluginsAndThemes","website":"https://github.com/Metalloriff/BetterDiscordPlugins/blob/master/README.md","source":"https://github.com/Metalloriff/BetterDiscordPlugins/blob/master/PinPluginsAndThemes.plugin.js"}*//
class PinPluginsAndThemes {
getName() { return "PinPluginsAndThemes"; }
getDescription() { return "Allows you to pin plugins and themes via the context menu."; }
getVersion() { return "1.0.2"; }
getAuthor() { return "Metalloriff"; }
getChanges() {
return {
"1.0.1": `
Fixed plugins and themes not pinning after switching settings tabs.
Fixed incompatibility with DevilBro's RepoControls plugin. Changing sorting mode of RepoControls will temporarily break the plugin, but simply switching tabs will fix it.
`,
"1.0.2": `
Fixed plugin
`
};
}
load() {}
start() {
let libLoadedEvent = () => {
try{ this.onLibLoaded(); }
catch(err) { console.error(this.getName(), "fatal error, plugin could not be started!", err); try { this.stop(); } catch(err) { console.error(this.getName() + ".stop()", err); } }
};
let lib = document.getElementById("NeatoBurritoLibrary");
if(lib == undefined) {
lib = document.createElement("script");
lib.setAttribute("id", "NeatoBurritoLibrary");
lib.setAttribute("type", "text/javascript");
lib.setAttribute("src", "https://rawgit.com/Metalloriff/BetterDiscordPlugins/master/Lib/NeatoBurritoLibrary.js");
document.head.appendChild(lib);
}
if(typeof window.NeatoLib !== "undefined") libLoadedEvent();
else lib.addEventListener("load", libLoadedEvent);
}
getSettingsPanel() {
setTimeout(() => {
NeatoLib.Settings.pushElement(NeatoLib.Settings.Elements.createNewTextField("Pin color (R, G, B) or (hex)", this.settings.pinColor, e => {
if(e.target.value.trim()[0] == "#") e.target.value = NeatoLib.Colors.hexToRGB(e.target.value.trim());
this.settings.pinColor = e.target.value;
this.applyStyles();
this.saveSettings();
}), this.getName());
NeatoLib.Settings.pushChangelogElements(this);
}, 0);
return NeatoLib.Settings.Elements.pluginNameLabel(this.getName());
}
applyStyles() {
if(this.styles) this.styles.destroy();
this.styles = NeatoLib.injectCSS(`
#ptap-pinned-items {
border-bottom: 10px solid rgb(${this.settings.pinColor});
margin-bottom: 20px;
}
#ptap-pinned-items li {
border-color: rgb(${this.settings.pinColor});
}
#ptap-pinned-items .bda-footer button, #ptap-pinned-items .ui-switch.checked {
background: rgb(${this.settings.pinColor}) !important;
}
#ptap-pinned-items .bda-footer a, #ptap-pinned-items .bda-name {
color: rgb(${this.settings.pinColor}) !important;
}
#plugin-settings-PinPluginsAndThemes input, #plugin-settings-PinPluginsAndThemes .themeDefault-24hCdX.valueChecked-m-4IJZ, #plugin-settings-PinPluginsAndThemes button {
background-color: rgb(${this.settings.pinColor}) !important;
}
`);
}
saveSettings() {
this.applyStyles();
NeatoLib.Settings.save(this);
}
onLibLoaded() {
if(NeatoLib.hasRequiredLibVersion(this, "0.0.2") == false) return;
this.settings = NeatoLib.Settings.load(this, {
displayUpdateNotes : true,
pinned : [],
pinColor : "142, 112, 216"
});
NeatoLib.Updates.check(this);
if(this.settings.displayUpdateNotes) NeatoLib.Changelog.compareVersions(this.getName(), this.getChanges());
this.applyStyles();
this.updatePinned = () => {
let items = document.getElementsByTagName("li"), pinnedItems = document.getElementById("ptap-pinned-items");
for(let i = 0; i < items.length; i++) {
if(!items[i].getAttribute("data-idx")) items[i].setAttribute("data-idx", i);
items[i].addEventListener("contextmenu", this.onItemContext);
if(this.settings.pinned.indexOf(items[i].getAttribute("data-name")) != -1) {
if(!pinnedItems) {
document.getElementsByClassName("bda-slist")[0].insertAdjacentHTML("afterbegin", `
`);
pinnedItems = document.getElementById("ptap-pinned-items");
}
pinnedItems.appendChild(items[i]);
}
}
};
this.onItemContext = e => {
let name = e.currentTarget.getAttribute("data-name"), idx = parseInt(e.currentTarget.getAttribute("data-idx")) + 1;
if(name) {
NeatoLib.ContextMenu.create([NeatoLib.ContextMenu.createGroup([NeatoLib.ContextMenu.createItem(this.settings.pinned.indexOf(name) == -1 ? "Pin" : "Unpin", e => {
if(this.settings.pinned.indexOf(name) != -1) {
let list = document.getElementsByClassName("bda-slist")[0];
list.insertBefore(document.querySelector(`[data-name="${name}"]`), list.childNodes[idx]);
this.settings.pinned.splice(this.settings.pinned.indexOf(name), 1);
NeatoLib.showToast(name + " unpinned");
let pinnedItems = document.getElementById("ptap-pinned-items");
if(pinnedItems.childElementCount == 0) pinnedItems.outerHTML = "";
} else {
this.settings.pinned.push(name);
this.updatePinned();
NeatoLib.showToast(name + " pinned", null, { color : `rgb(${this.settings.pinColor})` });
}
this.saveSettings();
NeatoLib.ContextMenu.close();
})])], e);
}
};
this.settingsObserver = new MutationObserver(mutations => {
for(let mi = 0; mi < mutations.length; mi++) {
let added = mutations[mi].addedNodes[0];
if(added && added instanceof Element && (added.classList.contains(NeatoLib.getClass("scrollerWrap"))))
this.updatePinned();
}
});
this.settingsPanelEvent = () => {
this.settingsObserver.observe(document.getElementsByClassName("layers-3iHuyZ")[0], { childList : true, subtree : true });
};
NeatoLib.Events.attach("settings", this.settingsPanelEvent);
NeatoLib.Events.onPluginLoaded(this);
}
stop() {
NeatoLib.Events.detach("settings", this.settingsPanelEvent);
this.styles.destroy();
}
}
================================================
FILE: PreventSpotifyAutoPause.plugin.js
================================================
//META{"name":"PreventSpotifyAutoPause","displayName":"PreventSpotifyAutoPause","website":"https://metalloriff.github.io/toms-discord-stuff/","source":"https://github.com/Metalloriff/BetterDiscordPlugins/blob/master/PreventSpotifyAutoPause.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@*/
var PreventSpotifyAutoPause = (() => {
const config = {
info: {
name: "PreventSpotifyAutoPause",
authors: [{
name: "Metalloriff",
discord_id: "264163473179672576",
github_username: "Metalloriff",
twitter_username: "Metalloriff"
}],
version: "1.0.4",
description: "Prevents Discord from automatically pausing Spotify after transmitting your microphone for 30 seconds.",
github: "https://github.com/Metalloriff/BetterDiscordPlugins/blob/master/PreventSpotifyAutoPause.plugin.js",
github_raw: "https://raw.githubusercontent.com/Metalloriff/BetterDiscordPlugins/master/PreventSpotifyAutoPause.plugin.js"
},
changelog: [{
title: "fixed",
type: "fixed",
items: ["Fixed not working"]
}],
main: "index.js",
defaultConfig: []
};
return !global.ZeresPluginLibrary ? class {
getName() { return config.info.name; }
getAuthor() { return config.info.authors.map(x => x.name).join(", "); }
getDescription() { return config.info.description; }
getVersion() { return config.info.version; }
load() {
const title = "Library Missing";
const ModalStack = BdApi.findModuleByProps("push", "update", "pop", "popWithKey");
const TextElement = BdApi.findModuleByProps("Sizes", "Weights");
const ConfirmationModal = BdApi.findModule(m => m.defaultProps && m.key && m.key() == "confirm-modal");
if (!ModalStack || !ConfirmationModal || !TextElement) return BdApi.alert(title, `The library plugin needed for ${config.info.name} is missing. Click here to download the library! `);
ModalStack.push(function(props) {
return BdApi.React.createElement(ConfirmationModal, Object.assign({
header: title,
children: [BdApi.React.createElement(TextElement, {color: TextElement.Colors.PRIMARY, children: [`The library plugin needed for ${config.info.name} is missing. Please click Download Now to install it.`]})],
red: false,
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(ContentManager.pluginsFolder, "0PluginLibrary.plugin.js"), body, r));
});
}
}, props));
});
}
start() {}
stop() {}
} : (([Plugin, Api]) => {
const plugin = (Plugin, Api) => {
return class PreventSpotifyAutoPause extends Plugin {
onStart() {
this.unpatch = Api.Patcher.instead(Api.WebpackModules.getByProps("SpotifyAPI", "pause"), "pause", () => {});
}
onStop() {
this.unpatch();
}
}
};
return plugin(Plugin, Api);
})(global.ZeresPluginLibrary.buildPlugin(config));
})();
/*@end@*/
================================================
FILE: README.md
================================================
# BetterDiscordPlugins
## NOTE: Due to loss of interest in BetterDiscord development, I'm pursuing other projects and no longer maintaining this repository. I will try to keep up with pull requests, but I will likely not be fixing any plugins myself. I apologize for the inconvenience. You can view my other projects at https://kinzoku.one, if you're interested.
## If you have any questions, feel free to contact me on any of my social platforms
- https://kinzoku.one/contact
## My support server
- https://discord.gg/yNqzuJa
# You can view information on my plugins [here](https://metalloriff.github.io/toms-discord-stuff/).
- https://metalloriff.github.io/toms-discord-stuff/
# You can also view my other projects here:
- https://kinzoku.one/
================================================
FILE: ReactionImages.plugin.js
================================================
/**
* @name ReactionImages
* @invite yNqzuJa
* @authorLink https://github.com/Metalloriff
* @donate https://www.paypal.me/israelboone
* @website https://metalloriff.github.io/toms-discord-stuff/
* @source https://github.com/Metalloriff/BetterDiscordPlugins/blob/master/ReactionImages.plugin.js
*/
module.exports = (() => {
const config = {
info: {
name: "ReactionImages",
authors: [{
name: "Metalloriff",
discord_id: "264163473179672576",
github_username: "metalloriff",
twitter_username: "Metalloriff"
}],
version: "2.0.1",
description: "Allows you to define custom reaction image folders and send images with 'foldername/imagesearch'. Example: 'reactions/goodmeme'",
github: "https://github.com/Metalloriff/BetterDiscordPlugins/blob/master/ReactionImages.plugin.js",
github_raw: "https://raw.githubusercontent.com/Metalloriff/BetterDiscordPlugins/master/ReactionImages.plugin.js"
},
changelog: [{
type: "fixed",
title: "Shitcord patch",
items: [
"Shitcord broke, I fixed. Thanks Strencher for letting me know."
]
}],
defaultConfig: [{
type: "category",
id: "general",
name: "general settings",
collapsible: true,
shown: true,
settings: [{
type: "textbox",
id: "paths",
name: "Folder Sources",
note: "Note: Separate folder paths with commas.",
placeholder: "Example: C:/Folder1, C:/Folder2/Some Other Folder, C:/Folder3",
value: ""
}, {
type: "slider",
id: "acSize",
name: "Autocomplete Image Size",
value: 200, min: 50, max: 500
}, {
type: "slider",
id: "acCap",
name: "Autocomplete Result Cap",
note: "The maximum number of images that will be displayed at the same time. Reduce this if you experience freezes when searching.",
value: 20, min: 5, max: 100
}]
}]
};
return !global.ZeresPluginLibrary ? class {
constructor() { this._config = config; }
getName = () => config.info.name;
getAuthor = () => config.info.description;
getVersion = () => 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 (err, res, body) => {
if (err) 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 { WebpackModules, DiscordModules, PluginUtilities, Toasts, Patcher, ReactTools } = Api;
const { UserStore, React } = DiscordModules;
const { getCurrentUser } = UserStore;
const Autocomplete = WebpackModules.getByDisplayName("Autocomplete");
const { AUTOCOMPLETE_OPTIONS } = WebpackModules.getByProps("AUTOCOMPLETE_OPTIONS");
const { upload } = WebpackModules.getByProps("cancel", "upload");
const fs = require("fs");
const path = require("path");
const classes = {
...WebpackModules.getByProps("horizontalAutocomplete"),
...WebpackModules.getByProps("horizontal", "directionRowReverse", "noWrap"),
...WebpackModules.getByProps("justifyStart", "alignStretch", "noWrap")
};
class AutocompleteItem extends React.Component {
render() {
const { fp, data, onClick, selected, index, height } = this.props;
return React.createElement(
"div",
{},
React.createElement(
"div",
{
style: {
color: "white",
textAlign: "center"
}
},
path.basename(fp).split(".")[0],
),
React.createElement(
Autocomplete.GIFIntegration,
{
className: classes.horizontalAutocomplete,
src: `data:image/${path.extname(fp)};base64, ${data}`,
height, onClick, selected, index
}
)
);
}
}
return class ReactionImages extends Plugin {
folders = {};
constructor() {
super();
}
getDataName = () => this.getName() + "." + getCurrentUser().id;
loadSettings = s => PluginUtilities.loadSettings(this.getDataName(), this.defaultSettings || s);
saveSettings = s => PluginUtilities.saveSettings(this.getDataName(), this.settings || s);
async showChangelog(footer) {
try { footer = (await WebpackModules.getByProps("getUser", "acceptAgreements").getUser("264163473179672576")).tag + " | https://discord.gg/yNqzuJa"; }
finally { super.showChangelog(footer); }
}
getSettingsPanel() {
const panel = this.buildSettingsPanel();
panel.addListener(() => {
this.term();
this.init();
});
return panel.getElement();
}
async init() {
this.fetchAllFolders();
}
async fetchAllFolders() {
Toasts.show("ReactionImages: Loading folder data...");
this.folders = {};
const folderPaths = this.settings.general.paths.split(",").map(p => p.trim());
for (let folder of folderPaths) {
const folderName = path.basename(folder).toLowerCase().replace(/ /g, "");
fs.readdir(folder, async (err, fileNames) => {
if (err) {
Toasts.show("ReactionImages: Failed to load folder named '" + folderName + "'!", { type: "error" });
return console.error(err);
}
this.folders[folderName] = [];
for (let fileName of fileNames) {
const ext = fileName.split(".")[fileName.split(".").length - 1];
if (!fileName.includes(".") || !["jpg", "jpeg", "png", "gif", "bmp"].includes(ext))
continue;
const fp = path.join(folder, fileName);
const data = await new Promise(r => fs.readFile(fp, "base64", (_, d) => r(d)));
this.folders[folderName].push({
data,
fp,
size: data.length
});
}
Toasts.show("ReactionImages: Successfully loaded folder named '" + folderName + "'.", { type: "success" });
});
}
}
getFolders() {
return this.settings.general.paths.split(",").map(fp => ([ fp.trim(), path.basename(fp.trim()) ]));
}
term() {
}
holdingShift = false;
updateHoldingShift = e => this.holdingShift = e.shiftKey;
async onStart() {
this.init();
document.addEventListener("keydown", this.updateHoldingShift);
document.addEventListener("keyup", this.updateHoldingShift);
AUTOCOMPLETE_OPTIONS.REACTION_IMAGES = {
autoSelect: true,
getPlainText: () => "",
getRawText: () => "",
getSentinel: () => "",
matches: (channel, {}, query, {}, config, queryRaw) => {
for (const [ fp, dirName ] of this.getFolders()) {
if (query.split("/")[0] == dirName.toLowerCase()) {
return true;
}
}
return false;
},
queryResults: (channel, query, config, queryRaw) => {
const [ folderName, reactionQuery ] = query.split("/");
const results = [];
const folder = this.folders[folderName];
if (folder && folder.length) {
for (const file of folder) {
const fn = path.basename(file.fp);
if (fn.toLowerCase().replace(/ /g, "").startsWith(reactionQuery)) {
results.push(file);
}
}
}
return { results };
},
renderResults: (channel, query, selectedIndex, select, choose, config, _, { results }) => {
const strong = c => React.createElement("strong", {}, c);
return [
React.createElement(
Autocomplete.Title,
{ title: [ "Searching ", strong(`"${query.split("/")[1]}"`), " in ", strong(query.split("/")[0]) ] }
),
React.createElement(
"div",
{ className: [ classes.flex, classes.horizontal, classes.justifyStart, classes.alignStretch, classes.noWrap, classes.horizontalAutocompletes ].join(" ") },
results.map(({ data, fp, size }, index) => React.createElement(
AutocompleteItem,
{
fp, data,
onClick: () => {
const { deserialize } = WebpackModules.getByProps("serialize", "deserialize");
const { channelTextArea } = WebpackModules.getByProps("channelTextArea", "channelTextAreaDisabled");
const [ cta ] = document.getElementsByClassName(channelTextArea);
const owner = ReactTools.getOwnerInstance(cta);
const { state: { textValue } } = owner;
upload(channel.id, new File([ fs.readFileSync(fp) ], path.basename(fp)), {
content: textValue.split(" ").splice(0, textValue.split(" ").length - 1).join(" ")
});
if (!this.holdingShift) {
owner.setState({ textValue: "", richValue: deserialize("") });
if (!this.settings.hasShownShiftNotice) {
Toasts.show("Note: You can hold shift to prevent the menu from closing when sending a reaction!", { type: "success" });
this.settings.hasShownShiftNotice = true;
this.saveSettings();
}
}
},
selected: selectedIndex == index,
height: this.settings.general.acSize,
index,
}
)).slice(0, this.settings.general.acCap)
)
];
}
};
}
onStop() {
this.term();
document.removeEventListener("keydown", this.updateHoldingShift);
document.removeEventListener("keyup", this.updateHoldingShift);
delete AUTOCOMPLETE_OPTIONS.REACTION_IMAGES;
Patcher.unpatchAll();
}
}
};
return plugin(Plugin, Api);
})(global.ZeresPluginLibrary.buildPlugin(config));
})();
================================================
FILE: SaveTo.plugin.js
================================================
//META{"name":"SaveTo","website":"https://metalloriff.github.io/toms-discord-stuff/","source":"https://github.com/Metalloriff/BetterDiscordPlugins/blob/master/SaveTo.plugin.js"}*//
class SaveTo {
getName() { return "Save To"; }
getDescription() { return "Allows you to save images, videos, files, server icons and user avatars to your defined folders, or browse to a folder, via the context menu."; }
getVersion() { return "0.7.9"; }
getAuthor() { return "Metalloriff"; }
getChanges() {
return {
"0.1.2" :
`
Added sort mode settings, including custom sorting.
You can now add, remove and modify folders via the settings menu.
`,
"0.2.2" :
`
Instead of just random numbers, the random file names are now random characters.
Added a changelog toggle setting.
Added a view changelog button.
`,
"0.3.2" :
`
Fixed the context menu being slightly delayed, causing an ugly flash effect.
Added a "Save and Open File" choice to the folder submenus.
`,
"0.4.2" :
`
Added a setting to move the dropdown menu to the top of the context menu.
`,
"0.5.4" :
`
Fixed a bunch of bugs.
Emotes are now saved as the emote name instead of ID.
`,
"0.5.5" :
`
Fixed "Save File Here" and "Save and Open File" not working.
`,
"0.6.7" :
`
Added a "Save File Here As" option to folder context menus.
`,
"0.7.8" :
`
You can now save emojis from the emoji picker.
Fixed saving avatars.
Fixed saving server icons.
`
};
}
saveSettings() {
NeatoLib.Settings.save(this);
}
saveData() {
NeatoLib.Data.save("SaveTo", "data", this.data);
}
getSettingsPanel() {
setTimeout(() => {
let date = new Date();
let refreshFolders = () => {
let fields = [];
for (let i = 0; i < this.data.folders.length; i++) {
let textField = NeatoLib.Settings.Elements.createNewTextField("", this.data.folders[i].path, e => {
let name = e.target.value.substring(e.target.value.split("\\").join("/").lastIndexOf("/") + 1, e.target.value.length);
if (e.target.value.trim().length == 0 || name.length == 0) {
this.data.folders.splice(i, 1);
e.target.parentElement.outerHTML = "";
} else {
this.data.folders[i].path = e.target.value;
this.data.folders[i].name = name;
}
refreshFolders();
this.saveData();
});
textField.insertAdjacentElement("beforeend", NeatoLib.Settings.Elements.createButton("Browse", e => {
NeatoLib.browseForFile(folder => {
e.target.parentElement.getElementsByClassName("input")[0].value = folder.path;
this.data.folders[i].path = folder.path;
this.data.folders[i].name = folder.name;
refreshFolders();
this.saveData();
}, {
directory: true
});
}, "float:right;"));
let positionField = NeatoLib.Settings.Elements.createNewTextField("", this.data.folders[i].position, e => {
if (e.target.value.length == 0) e.target.value = this.data.folders[i].position;
else this.data.folders[i].position = e.target.value;
refreshFolders();
this.saveData();
}).getElementsByTagName("input")[0];
positionField.style.paddingRight = "10px";
positionField.style.width = "100px";
positionField.style.float = "left";
textField.setAttribute("data-path", this.data.folders[i].path);
textField.setAttribute("data-name", this.data.folders[i].name);
textField.setAttribute("data-priority", this.data.folders[i].position);
textField.getElementsByTagName("input")[0].style.width = "430px";
textField.addEventListener("click", () => this.selectedFolder = i);
textField.insertAdjacentElement("afterbegin", positionField);
fields.push(textField);
}
if (this.settings.sortMode == "a-z" || this.settings.sortMode == "z-a") fields = fields.sort((x, y) => {
if (x.getAttribute("data-name").toLowerCase() < y.getAttribute("data-name").toLowerCase()) return -1;
if (x.getAttribute("data-name").toLowerCase() > y.getAttribute("data-name").toLowerCase()) return 1;
return 0;
});
if (this.settings.sortMode == "z-a" || this.settings.sortMode == "new-old") fields = fields.reverse();
if (this.settings.sortMode == "custom") fields = fields.sort((x, y) => {
if (parseFloat(x.getAttribute("data-priority")) > parseFloat(y.getAttribute("data-priority"))) return -1;
if (parseFloat(x.getAttribute("data-priority")) < parseFloat(y.getAttribute("data-priority"))) return 1;
return 0;
});
document.getElementById("st-folders").innerHTML = "";
for (let i = 0; i < fields.length; i++) document.getElementById("st-folders").insertAdjacentElement("beforeend", fields[i]);
};
NeatoLib.Settings.pushElement(NeatoLib.Settings.Elements.createRadioGroup("st-file-name-type", "File name type:", [{
title: "Original",
value: "original",
description: `Example: unknown.png`
},
{
title: "Date",
value: "date",
description: `Example: ${date.toLocaleDateString().split("/").join("-")} ${date.getMinutes()}${date.getSeconds()}${date.getMilliseconds()}.png`
},
{
title: "Random",
value: "random",
description: `Example: ${Math.random().toString(36).substring(10)}.png`
},
{
title: "Original + random",
value: "original+random",
description: `Example: unknown ${Math.random().toString(36).substring(10)}.png`
}
], this.settings.fileNameType, (e, choiceItem) => {
this.settings.fileNameType = choiceItem.value;
this.saveSettings();
}), this.getName());
NeatoLib.Settings.pushElement(NeatoLib.Settings.Elements.createRadioGroup("st-sort-type", "Sort by:", [{
title: "A - Z",
value: "a-z"
},
{
title: "Z - A",
value: "z-a"
},
{
title: "Newest - Oldest",
value: "new-old"
},
{
title: "Oldest - Newest",
value: "old-new"
},
{
title: "By priority",
value: "custom"
}
], this.settings.sortMode, (e, choiceItem) => {
this.settings.sortMode = choiceItem.value;
refreshFolders();
this.saveSettings();
}), this.getName());
let folderLabels = document.createElement("div");
folderLabels.insertAdjacentHTML("beforeend", `Folders: `);
folderLabels.insertAdjacentHTML("beforeend", `Prioirty `);
folderLabels.insertAdjacentHTML("beforeend", `Path `);
NeatoLib.Settings.pushElement(folderLabels, this.getName());
let foldersParentDiv = document.createElement("div");
foldersParentDiv.setAttribute("id", "st-folders");
NeatoLib.Settings.pushElement(foldersParentDiv, this.getName());
refreshFolders();
NeatoLib.Settings.pushHTML(`
`, this.getName());
let buttonParent = document.getElementById("st-settings-buttons");
buttonParent.insertAdjacentElement("beforeend", NeatoLib.Settings.Elements.createButton("Add Folder", () => {
this.browseForFolder(() => refreshFolders());
}));
buttonParent.insertAdjacentElement("beforeend", NeatoLib.Settings.Elements.createButton("Remove Selected Folder", () => {
this.data.folders.splice(this.selectedFolder, 1);
refreshFolders();
this.saveData();
}, "margin-left:15px;", {
id: "st-settings-remove-folder"
}));
buttonParent.insertAdjacentElement("beforeend", NeatoLib.Settings.Elements.createButton("Open Selected Folder", () => {
window.open(`file:///${this.data.folders[this.selectedFolder].path}`);
}, "margin-left:15px;", {
id: "st-settings-open-folder"
}));
NeatoLib.Settings.pushElement(NeatoLib.Settings.Elements.createToggleSwitch("Move dropdown menu to the top of the context menu", this.settings.dropdownOnTop, () => {
this.settings.dropdownOnTop = !this.settings.dropdownOnTop;
this.saveSettings();
}), this.getName());
NeatoLib.Settings.pushChangelogElements(this);
}, 0);
return NeatoLib.Settings.Elements.pluginNameLabel(this.getName());
}
load() {}
start() {
const libLoadedEvent = () => {
try{ this.onLibLoaded(); }
catch(err) { console.error(this.getName(), "fatal error, plugin could not be started!", err); try { this.stop(); } catch(err) { console.error(this.getName() + ".stop()", err); } }
};
let lib = document.getElementById("NeatoBurritoLibrary");
if (!lib) {
lib = document.createElement("script");
lib.id = "NeatoBurritoLibrary";
lib.type = "text/javascript";
lib.src = "https://rawgit.com/Metalloriff/BetterDiscordPlugins/master/Lib/NeatoBurritoLibrary.js";
document.head.appendChild(lib);
}
this.forceLoadTimeout = setTimeout(libLoadedEvent, 30000);
if (typeof window.NeatoLib !== "undefined") libLoadedEvent();
else lib.addEventListener("load", libLoadedEvent);
}
onLibLoaded() {
NeatoLib.Updates.check(this);
this.data = NeatoLib.Data.load("SaveTo", "data", {
folders: []
});
let updated = [];
for (let i = 0; i < this.data.folders.length; i++) {
if (typeof this.data.folders[i] !== "object") {
let p = this.data.folders[i];
updated.push({
path: p,
name: p.substring(p.split("\\").join("/").lastIndexOf("/") + 1, p.length),
position: i
});
}
}
if (updated.length > 0) this.data.folders = updated;
this.settings = NeatoLib.Settings.load(this, {
fileNameType: "original",
sortMode: "a-z",
displayUpdateNotes: true,
dropdownOnTop: true
});
this.selectedFolder = -1;
document.addEventListener("contextmenu", this.contextMenuEvent = e => {
if (document.getElementsByClassName(NeatoLib.getClass("contextMenu")).length == 0) setTimeout(() => this.onContextMenu(e), 0);
else this.onContextMenu(e);
});
NeatoLib.Changelog.compareVersions(this.getName(), this.getChanges());
NeatoLib.Events.onPluginLoaded(this);
}
onContextMenu(e) {
let member = NeatoLib.DOM.searchForParentElementByClassName(e.target, NeatoLib.getClass("member")),
dm = NeatoLib.DOM.searchForParentElementByClassName(e.target, "private") || NeatoLib.DOM.searchForParentElementByClassName(e.target, "friends-row"),
messageGroup = NeatoLib.DOM.searchForParentElementByClassName(e.target, NeatoLib.getClass("containerCozy", "container")),
user = NeatoLib.ReactData.get(NeatoLib.ContextMenu.get()) ? NeatoLib.ReactData.get(NeatoLib.ContextMenu.get()).return.return.return.return.memoizedProps.user : null,
guild = NeatoLib.ReactData.getProp(NeatoLib.ContextMenu.get(), "guild");
if (e.target.localName != "a" && e.target.localName != "img" && e.target.localName != "video" && !member && !dm && !messageGroup && !e.target.className.includes("emojiItem") && !user && !guild) return;
let saveLabel = "Save To",
url = e.target.poster || e.target.style.backgroundImage.substring(e.target.style.backgroundImage.indexOf(`"`) + 1, e.target.style.backgroundImage.lastIndexOf(`"`)) || e.target.href || e.target.src,
menu = [];
if (user) {
url = user.getAvatarURL();
if (url.includes("/a_")) url = url.replace(".png", ".gif");
saveLabel = "Save Avatar To";
}
if (guild) {
url = guild.getIconURL();
saveLabel = "Save Icon To";
}
if (e.target.className.includes("guildIcon")) saveLabel = "Save Icon To";
if ((!url && !e.target.className.includes("emojiItem")) || e.target.classList.contains("emote") || url.includes("youtube.com/")) return;
url = url.split("?")[0];
if (saveLabel.includes("Avatar") || saveLabel.includes("Icon")) url += "?size=2048";
url = url.replace(".webp", ".png");
let fileName = url.substring(url.lastIndexOf("/") + 1, url.length),
fileExtension = url.substring(url.lastIndexOf("."), url.length);
if (e.target.classList.contains("emoji")) {
saveLabel = "Save Emoji To";
fileName = NeatoLib.ReactData.getProps(e.target).emojiName.replace(/[^A-Za-z]/g, "") + fileExtension;
}
if (e.target.className.includes("emojiItem")) {
saveLabel = "Save Emoji To";
fileName = (url = e.target.style.backgroundImage.split('"')[1]).split("?")[0].split("/")[4];
}
let date = new Date();
if (this.settings.fileNameType == "date") fileName = `${date.toLocaleDateString().split("/").join("-")} ${date.getMinutes()}${date.getSeconds()}${date.getMilliseconds()}${fileExtension}`;
if (this.settings.fileNameType == "random") fileName = `${Math.random().toString(36).substring(10)}${fileExtension}`;
if (this.settings.fileNameType == "original+random") fileName = fileName.replace(fileExtension, "") + ` ${Math.random().toString(36).substring(10)}${fileExtension}`;
let optionsSubMenu = i => {
let r = [];
r.push(NeatoLib.ContextMenu.createItem("Remove Folder", e => {
this.data.folders.splice(i, 1);
this.saveData();
NeatoLib.ContextMenu.close();
}));
r.push(NeatoLib.ContextMenu.createItem("Open Folder", () => {
window.open("file:///" + this.data.folders[i].path);
}));
r.push(NeatoLib.ContextMenu.createItem("Save File Here", () => {
NeatoLib.downloadFile(url, this.data.folders[i].path, fileName);
}));
r.push(NeatoLib.ContextMenu.createItem("Save File Here As", () => {
NeatoLib.ContextMenu.close();
console.log(fileName);
NeatoLib.UI.createTextPrompt("save-file-as", "Save File As...", (filename, prompt) => {
if (!filename) NeatoLib.showToast("File not saved! No filename specified!", "error");
else NeatoLib.downloadFile(url, this.data.folders[i].path, filename + "." + fileName.split(".")[fileName.split(".").length - 1]);
prompt.close();
}, fileName.split(".")[0]);
}));
r.push(NeatoLib.ContextMenu.createItem("Save and Open File", () => {
NeatoLib.downloadFile(url, this.data.folders[i].path, fileName, p => window.open("file:///" + p));
}));
return r;
};
let sorted = this.data.folders;
if (this.settings.sortMode == "a-z" || this.settings.sortMode == "z-a") sorted = sorted.sort((x, y) => {
if (x.name.toLowerCase() < y.name.toLowerCase()) return -1;
if (x.name.toLowerCase() > y.name.toLowerCase()) return 1;
return 0;
});
if (this.settings.sortMode == "z-a" || this.settings.sortMode == "old-new") sorted = sorted.reverse();
if (this.settings.sortMode == "custom") sorted = sorted.sort((x, y) => {
if (x.position > y.position) return -1;
if (x.position < y.position) return 1;
return 0;
});
let g = [];
for (let i = 0; i < this.data.folders.length; i++) {
g.push(NeatoLib.ContextMenu.createSubMenu(this.data.folders[i].name, optionsSubMenu(i), {
callback: () => NeatoLib.downloadFile(url, this.data.folders[i].path, fileName)
}));
}
menu.push(NeatoLib.ContextMenu.createGroup(g));
menu.push(NeatoLib.ContextMenu.createGroup([
NeatoLib.ContextMenu.createItem("Add Folder", () => this.browseForFolder()),
NeatoLib.ContextMenu.createItem("Browse", () => NeatoLib.browseForFile(folder => NeatoLib.downloadFile(url, folder.path, fileName), {
directory: true
})),
NeatoLib.ContextMenu.createItem("Plugin Settings", () => {
NeatoLib.Settings.showPluginSettings(this.getName());
NeatoLib.ContextMenu.close();
})
]));
if (NeatoLib.ContextMenu.get()) NeatoLib.ContextMenu.get().insertAdjacentElement(this.settings.dropdownOnTop ? "afterbegin" : "beforeend", NeatoLib.ContextMenu.createGroup([NeatoLib.ContextMenu.createSubMenu(saveLabel, menu)]));
else NeatoLib.ContextMenu.create([NeatoLib.ContextMenu.createGroup([NeatoLib.ContextMenu.createSubMenu(saveLabel, menu)])], e);
}
browseForFolder(selected) {
NeatoLib.browseForFile(folder => {
if (this.data.folders.findIndex(f => f.path == folder.path) == -1) {
this.data.folders.push({
path: folder.path,
name: folder.name,
position: this.data.folders.length
});
this.saveData();
}
if (selected) selected();
}, {
directory: true
});
}
stop() {
document.removeEventListener("contextmenu", this.contextMenuEvent);
}
}
================================================
FILE: SelectedChannelNotifications.plugin.js
================================================
//META{"name":"SelectedChannelNotifications","website":"https://metalloriff.github.io/toms-discord-stuff/","source":"https://github.com/Metalloriff/BetterDiscordPlugins/blob/master/SelectedChannelNotifications.plugin.js"}*//
class SelectedChannelNotifications {
getName() { return "Selected Channel Notifications"; }
getDescription() { return "Plays a sound and displays a notification (both optional) when Discord is minimized and a message is received in the selected channel."; }
getVersion() { return "0.1.6"; }
getAuthor() { return "Metalloriff"; }
load() {}
start() {
const libLoadedEvent = () => {
try{ this.onLibLoaded(); }
catch(err) { console.error(this.getName(), "fatal error, plugin could not be started!", err); try { this.stop(); } catch(err) { console.error(this.getName() + ".stop()", err); } }
};
let lib = document.getElementById("NeatoBurritoLibrary");
if (!lib) {
lib = document.createElement("script");
lib.id = "NeatoBurritoLibrary";
lib.type = "text/javascript";
lib.src = "https://rawgit.com/Metalloriff/BetterDiscordPlugins/master/Lib/NeatoBurritoLibrary.js";
document.head.appendChild(lib);
}
this.forceLoadTimeout = setTimeout(libLoadedEvent, 30000);
if (typeof window.NeatoLib !== "undefined") libLoadedEvent();
else lib.addEventListener("load", libLoadedEvent);
}
get settingFields() {
return {
messageSound: { label: "Message notification sound", type: "string" },
playSound: { label: "Play sound on message", type: "bool" },
displayNotification: { label: "Display notification on message", type: "bool" }
};
}
get defaultSettings() {
return {
messageSound : "https://discordapp.com/assets/dd920c06a01e5bb8b09678581e29d56f.mp3",
playSound : true,
displayNotification : true
};
}
getSettingsPanel() {
return NeatoLib.Settings.createPanel(this);
}
saveSettings() {
NeatoLib.Settings.save(this);
this.audio.src = this.settings.messageSound;
this.audio.volume = this.volumeModule.getOutputVolume() / 200;
}
onLibLoaded() {
NeatoLib.Updates.check(this);
this.channelModule = NeatoLib.Modules.get("getChannel");
this.notificationSettingsModule = NeatoLib.Modules.get("getChannelMessageNotifications");
this.volumeModule = NeatoLib.Modules.get("getOutputVolume");
NeatoLib.Settings.load(this);
this.audio = new Audio();
this.audio.src = this.settings.messageSound;
this.audio.volume = this.volumeModule.getOutputVolume() / 200;
this.notifications = [];
this.focused = true;
window.addEventListener("focus", this.focus = () => {
this.focused = true;
for (let i = 0; i < this.notifications.length; i++) this.notifications[i].close();
});
window.addEventListener("blur", this.unfocus = () => {
this.focused = false;
});
NeatoLib.Events.attach("message", this.msgRec = () => this.onMessageReceived());
NeatoLib.Events.onPluginLoaded(this);
}
onMessageReceived() {
if (!this.focused && NeatoLib.getLocalStatus() != "dnd") {
const messages = document.getElementsByClassName(NeatoLib.getClass("containerCozy", "container")),
lastGroup = NeatoLib.ReactData.getProps(messages[messages.length - 1]).messages,
lastMsg = lastGroup[lastGroup.length - 1];
if (lastMsg.author.id != NeatoLib.getLocalUser().id && !lastMsg.mentioned) {
if (this.settings.displayNotification) {
const n = new Notification(
(!lastMsg.nick ? lastMsg.author.username : lastMsg.nick) + " - #" + NeatoLib.getSelectedTextChannel().name, {
silent: true,
body: lastMsg.content,
icon: lastMsg.author.getAvatarURL()
}
);
const i = this.notifications.push(n) - 1;
n.onclose = () => this.notifications.splice(i, 1);
}
if (this.settings.playSound) this.audio.play();
}
}
}
stop() {
window.removeEventListener("focus", this.focus);
window.removeEventListener("blur", this.unfocus);
NeatoLib.Events.detach("message", this.msgRec);
}
}
================================================
FILE: SendBDEmotes.plugin.js
================================================
//META{"name":"SendBDEmotes","website":"https://metalloriff.github.io/toms-discord-stuff/","source":"https://github.com/Metalloriff/BetterDiscordPlugins/blob/master/SendBDEmotes.plugin.js"}*//
class SendBDEmotes {
getName() { return "Send BD Emotes"; }
getDescription() { return "Allows you to enclose Better Discord emotes in square brackets to send them as a higher resolution link that all users can see. Example: [forsenE]. You can also do [EmoteChannelName.EmoteName]. Example: [FrankerFaceZ.SeemsGood]. [EmoteName:size]. Example: [forsenE:1]. And [EmoteName_a] for animated emotes."; }
getVersion() { return "1.6.12"; }
getAuthor() { return "Metalloriff"; }
load() {}
start() {
const libLoadedEvent = () => {
try{ this.onLibLoaded(); }
catch(err) { console.error(this.getName(), "fatal error, plugin could not be started!", err); try { this.stop(); } catch(err) { console.error(this.getName() + ".stop()", err); } }
};
let lib = document.getElementById("NeatoBurritoLibrary");
if (!lib) {
lib = document.createElement("script");
lib.id = "NeatoBurritoLibrary";
lib.type = "text/javascript";
lib.src = "https://rawgit.com/Metalloriff/BetterDiscordPlugins/master/Lib/NeatoBurritoLibrary.js";
document.head.appendChild(lib);
}
this.forceLoadTimeout = setTimeout(libLoadedEvent, 30000);
if (typeof window.NeatoLib !== "undefined") libLoadedEvent();
else lib.addEventListener("load", libLoadedEvent);
}
get settingFields() {
return {
emoteSize: { label: "Sent emote size", type: "radio", choices: {
1: { label: "Small", description: "BetterDiscord Default" },
2: { label: "Medium" },
4: { label: "Large" }
}},
sendAsLink: { label: "Send emotes as links", description: "(faster)", type: "bool" },
displayPreview: { label: "Display auto-complete", type: "bool" },
forceGif: { label: "Send emotes as gif", type: "bool" },
previewLimit: { label: "Maximum amount of emotes to display on auto-complete", type: "int" }
};
}
get defaultSettings() {
return {
emoteSize: 4,
sendAsLink: false,
displayPreview: true,
forceGif: false,
previewLimit: 25
};
}
getSettingsPanel() {
return NeatoLib.Settings.createPanel(this);
}
saveSettings() {
NeatoLib.Settings.save(this);
}
onLibLoaded() {
NeatoLib.Updates.check(this);
this.hasPermission = NeatoLib.Modules.get(["can"]).can;
this.uploadFile = NeatoLib.Modules.findAllByPropertyName('upload')[1];
this.messageModule = NeatoLib.Modules.get(["sendMessage"]);
this.settings = NeatoLib.Settings.load(this);
this.keyDownEvent = e => this.onKeyDown(e);
this.keyUpEvent = e => this.onKeyUp(e);
NeatoLib.Events.attach("switch", this.switchEvent = () => this.switch());
this.getEmotes();
NeatoLib.Events.onPluginLoaded(this);
this.switch();
}
onKeyDown(e) {
if (!e.target.value) {
if (document.getElementById("sbde-autocomplete")) document.getElementById("sbde-autocomplete").remove();
return;
}
const words = e.target.value.split(" "), lastWord = words[words.length - 1];
if (e.key == "Enter" && !e.shiftKey && lastWord.startsWith("[") && lastWord.endsWith("]")) {
let emoteName = lastWord.substring(1, lastWord.length - 1), size = this.settings.emoteSize, animated = false;
if (emoteName.includes(":")) {
const trySize = parseInt(emoteName.substring(emoteName.indexOf(":") + 1, emoteName.length));
if (!isNaN(trySize)) {
size = trySize;
emoteName = emoteName.substring(0, emoteName.indexOf(":"));
}
}
if (emoteName.endsWith("_a")) {
animated = true;
emoteName = emoteName.replace("_a", "");
}
let emote = window.bdEmotes.TwitchGlobal[emoteName] || window.bdEmotes.TwitchSubscriber[emoteName] || window.bdEmotes.BTTV[emoteName] || window.bdEmotes.FrankerFaceZ[emoteName] || window.bdEmotes.BTTV2[emoteName];
if (emoteName.includes(".")) {
const sourceAndName = emoteName.split(".");
emote = window.bdEmotes[sourceAndName[0]][sourceAndName[1]];
}
if (emote) {
const message = e.target.value.split(lastWord).join("");
NeatoLib.Chatbox.setText("");
this.trySend(message, emoteName, emote, size, animated);
} else if (emoteName == "RANDOM") {
const randomEmote = this.emotes[this.emotes.length * Math.random() << 0], message = e.target.value.split(lastWord).join("");
NeatoLib.Chatbox.setText("");
this.trySend(message, randomEmote.name, randomEmote.url, size, animated);
}
if (document.getElementById("sbde-autocomplete")) document.getElementById("sbde-autocomplete").remove();
return;
}
if (lastWord.endsWith("]")) {
if (document.getElementById("sbde-autocomplete")) document.getElementById("sbde-autocomplete").remove();
return;
}
}
async onKeyUp(e) {
if (!this.settings.displayPreview) return;
const words = e.target.value.split(" "), lastWord = words[words.length - 1];
let autocomplete = document.getElementById("sbde-autocomplete");
if (lastWord.startsWith("[")) {
const emoteName = lastWord.substring(1, lastWord.length);
if (!autocomplete) {
e.target.parentElement.insertAdjacentHTML("beforeend", `
looks_one
looks_two
looks_3
loop
`);
autocomplete = document.getElementById("sbde-autocomplete");
const buttons = autocomplete.getElementsByClassName("material-icons");
const sizeButton = (button, size) => {
if (this.settings.emoteSize == size) button.style.color = NeatoLib.Colors.DiscordDefaults.blue;
else button.style.color = "white";
button.onclick = () => {
this.settings.emoteSize = size;
this.saveSettings();
update();
};
};
const update = () => {
for (let i = 0; i < buttons.length; i++) {
const button = buttons[i];
switch (button.textContent) {
case "looks_one":
sizeButton(button, 1);
if (!button.tooltip) NeatoLib.Tooltip.attach("Small", button);
break;
case "looks_two":
sizeButton(button, 2);
if (!button.tooltip) NeatoLib.Tooltip.attach("Medium", button);
break;
case "looks_3":
sizeButton(button, 4);
if (!button.tooltip) NeatoLib.Tooltip.attach("Large", button);
break;
case "loop":
if (this.settings.forceGif) button.style.color = NeatoLib.Colors.DiscordDefaults.blue;
else button.style.color = "white";
button.onclick = () => {
this.settings.forceGif = !this.settings.forceGif;
this.saveSettings();
update();
};
if (!button.tooltip) NeatoLib.Tooltip.attach("Send as gif", button);
break;
}
}
};
update();
}
const list = document.getElementById("sbde-autocomplete-list");
list.innerHTML = "";
let lim = 0;
for (let i = 0; i < this.emotes.length; i++) {
const emote = this.emotes[i];
if (emote.name.startsWith(emoteName)) {
if (lim >= this.settings.previewLimit) break;
list.insertAdjacentHTML("beforeend", `
`);
const images = list.getElementsByTagName("img"), lastImage = images[images.length - 1];
NeatoLib.Tooltip.attach(emote.name, lastImage);
lastImage.addEventListener("click", ev => {
if (ev.shiftKey) this.trySend("", emote.name, emote.url, this.settings.emoteSize);
else {
this.trySend(e.target.value.split(lastWord).join(""), emote.name, emote.url, this.settings.emoteSize);
NeatoLib.Chatbox.setText("");
autocomplete.remove();
}
});
lim++;
}
}
} else if (autocomplete) autocomplete.remove();
}
async getEmotes() {
const channels = Object.keys(window.bdEmotes);
this.emotes = [];
for (let ec of channels) {
const channel = window.bdEmotes[ec];
for (let emote in channel) {
this.emotes.push({
name: emote,
url: channel[emote]
});
}
}
this.emotes.sort((x, y) => x.name.length - y.name.length);
}
trySend(message, emoteName, emoteURL, size = 4, animated = false) {
if (this.settings.forceGif) animated = true;
const i = emoteURL.lastIndexOf("1"), url = emoteURL.substring(0, i) + size + emoteURL.substring(i + 1);
if (this.settings.sendAsLink) this.messageModule.sendMessage(NeatoLib.getSelectedTextChannel().id, { content: message + " " + url });
else NeatoLib.requestFile(url, emoteName + (animated ? ".gif" : ".png"), file => {
if (file.size < 100) {
if (size > 1) this.trySend(message, emoteName, emoteURL, size - 1, animated);
return;
}
this.uploadFile(NeatoLib.getSelectedTextChannel().id, file, { content: message, tts: false });
});
}
switch () {
if (!this.ready) return;
if (!this.emotes || this.emotes.length < 700000) this.getEmotes();
const chatbox = NeatoLib.Chatbox.get();
if (chatbox) {
chatbox.addEventListener("keydown", this.keyDownEvent);
chatbox.addEventListener("keyup", this.keyUpEvent);
}
}
stop() {
const chatbox = NeatoLib.Chatbox.get();
if (chatbox) {
chatbox.removeEventListener("keydown", this.keyDownEvent);
chatbox.removeEventListener("keyup", this.keyUpEvent);
}
NeatoLib.Events.detach("switch", this.switchEvent);
}
}
================================================
FILE: SendLinksDirectly.plugin.js
================================================
//META{"name":"SendLinksDirectly","website":"https://metalloriff.github.io/toms-discord-stuff/","source":"https://github.com/Metalloriff/BetterDiscordPlugins/blob/master/SendLinksDirectly.plugin.js"}*//
class SendLinksDirectly {
getName() { return "SendLinksDirectly"; }
getDescription() { return `Allows you to enclose direct links in square brackets to upload them directly, instead of sending a link.
Usage: [link] or [link, filename.fileformat]
Example: [https://static-cdn.jtvnw.net/emoticons/v1/521050/4.0, forsenE.png]`; }
getVersion() { return "1.1.4"; }
getAuthor() { return "Metalloriff"; }
getChanges() {
return {
"1.1.3":
`
You can now send files from your PC with files paths.
`
};
}
load() {}
start() {
const libLoadedEvent = () => {
try{ this.onLibLoaded(); }
catch(err) { console.error(this.getName(), "fatal error, plugin could not be started!", err); try { this.stop(); } catch(err) { console.error(this.getName() + ".stop()", err); } }
};
let lib = document.getElementById("NeatoBurritoLibrary");
if (!lib) {
lib = document.createElement("script");
lib.id = "NeatoBurritoLibrary";
lib.type = "text/javascript";
lib.src = "https://rawgit.com/Metalloriff/BetterDiscordPlugins/master/Lib/NeatoBurritoLibrary.js";
document.head.appendChild(lib);
}
this.forceLoadTimeout = setTimeout(libLoadedEvent, 30000);
if (typeof window.NeatoLib !== "undefined") libLoadedEvent();
else lib.addEventListener("load", libLoadedEvent);
}
onLibLoaded() {
NeatoLib.Updates.check(this);
NeatoLib.Events.onPluginLoaded(this);
const fs = require("fs");
this.keyEvent = e => {
const chatbox = e.target,
selectedChannel = NeatoLib.getSelectedTextChannel();
if (selectedChannel == undefined) return;
if (e.which == 13 && !e.shiftKey && chatbox.value.trim() != "") {
let search = chatbox.value.split("[").join("][").split("]"),
links = [],
files = [],
message = chatbox.value;
for (let i = 1; i < search.length - 1; i++) {
search[i] = search[i].split("\\").join("/").split("\"").join("");
if (search[i].startsWith("[http")) {
links.push(search[i].substring(1, search[i].length).split(","));
message = message.substring(0, message.indexOf("[")) + message.substring(message.indexOf("]") + 1, message.length);
} else if (search[i].substring(1, search[i].length).match(/^[A-Z]:\//)) {
files.push(search[i].substring(1, search[i].length).split(","));
message = message.substring(0, message.indexOf("[")) + message.substring(message.indexOf("]") + 1, message.length);
}
}
for (let i = 0; i < links.length; i++) {
const filename = links[i][0].substring(links[i][0].lastIndexOf("/") + 1, links[i][0].length).split("?")[0];
NeatoLib.requestFile(links[i][0], links[i].length > 1 ? links[i][1] : filename, file => {
NeatoLib.Modules.find(m => m.upload && typeof m.upload === 'function').upload(selectedChannel.id, file, {
content: i == 0 ? message : "",
tts: false
});
});
}
for (let i = 0; i < files.length; i++) {
const filename = files[i][0].substring(files[i][0].lastIndexOf("/") + 1, files[i][0].length);
NeatoLib.Modules.find(m => m.upload && typeof m.upload === 'function').upload(selectedChannel.id, new File([fs.readFileSync(files[i][0])], files[i].length > 1 ? files[i][1] : filename), {
content: i == 0 ? message : "",
tts: false
});
}
if (links.length > 0 || files.length > 0) NeatoLib.Chatbox.setText("");
}
};
this.switchEvent = () => this.switch();
this.switch();
Metalloriff.Changelog.compareVersions(this.getName(), this.getChanges());
NeatoLib.Events.attach("switch", this.switchEvent);
}
switch () {
const chatbox = NeatoLib.Chatbox.get();
if (chatbox == undefined) return;
chatbox.addEventListener("keydown", this.keyEvent);
}
stop() {
if (NeatoLib.Chatbox.get()) NeatoLib.Chatbox.get().removeEventListener("keydown", this.keyEvent);
NeatoLib.Events.detach("switch", this.switchEvent);
}
}
================================================
FILE: ShareButton.plugin.js
================================================
//META{"name":"ShareButton","website":"https://metalloriff.github.io/toms-discord-stuff/","source":"https://github.com/Metalloriff/BetterDiscordPlugins/blob/master/ShareButton.plugin.js"}*//
class ShareButton {
getName() { return "Share Button"; }
getDescription() { return "Allows you to easily share images, videos, links and messages to other channels and servers via the context menu and message dropdown menu."; }
getVersion() { return "0.2.9"; }
getAuthor() { return "Metalloriff"; }
getChanges() {
return {
"0.1.2" :
`
Halfed the size of the server items.
Added a submenu for pinned and recent channels in the context menu.
You can now share messages via the context menu.
Shared messages are now quoted with the user's name.
You can now share to direct messages.
`,
"0.2.7" :
`
Fixed the plugin, but the recent channels is still broken. I will try to fix it soon.
`,
"0.2.8" :
`
Fixed not showing guilds
`
};
}
load() {}
start() {
const libLoadedEvent = () => {
try{ this.onLibLoaded(); }
catch(err) { console.error(this.getName(), "fatal error, plugin could not be started!", err); try { this.stop(); } catch(err) { console.error(this.getName() + ".stop()", err); } }
};
let lib = document.getElementById("NeatoBurritoLibrary");
if (!lib) {
lib = document.createElement("script");
lib.id = "NeatoBurritoLibrary";
lib.type = "text/javascript";
lib.src = "https://rawgit.com/Metalloriff/BetterDiscordPlugins/master/Lib/NeatoBurritoLibrary.js";
document.head.appendChild(lib);
}
this.forceLoadTimeout = setTimeout(libLoadedEvent, 30000);
if (typeof window.NeatoLib !== "undefined") libLoadedEvent();
else lib.addEventListener("load", libLoadedEvent);
}
getSettingsPanel() {
setTimeout(() => {
Metalloriff.Settings.pushChangelogElements(this);
}, 0);
return Metalloriff.Settings.Elements.pluginNameLabel(this.getName());
}
saveSettings() {
NeatoLib.Settings.save(this);
}
onLibLoaded() {
NeatoLib.Updates.check(this);
this.settings = NeatoLib.Settings.load(this, {
displayUpdateNotes : true
});
Metalloriff.Changelog.compareVersions(this.getName(), this.getChanges());
this.guildModule = NeatoLib.Modules.get(["getGuild", "getGuilds"]);
this.sortedGuildModule = NeatoLib.Modules.get("getSortedGuilds");
this.channelModule = NeatoLib.Modules.get(["getChannel", "getChannels"]);
this.dmModule = NeatoLib.Modules.get("getPrivateChannelIds");
this.userModule = NeatoLib.Modules.get(["getUser", "getUsers"]);
this.transitionModule = NeatoLib.Modules.get("transitionTo");
this.messageModule = NeatoLib.Modules.get("sendMessage");
let data = NeatoLib.Data.load("ShareButton", "data", { recentChannels : [], pinnedChannels : [] });
this.recentChannels = data.recentChannels || [];
this.pinnedChannels = data.pinnedChannels || [];
this.popoutObserver = new MutationObserver(m => {
if(m[0].addedNodes && m[0].addedNodes[0] instanceof Element && m[0].addedNodes[0].firstChild && m[0].addedNodes[0].firstChild.classList.contains("option-popout")) {
let dropdown = e[0].addedNodes[0].firstChild, message = NeatoLib.ReactData.getProps(dropdown).message;
dropdown.insertAdjacentHTML("afterBegin", `Share
`);
document.getElementById("sb-share-popout").addEventListener("click", () => {
this.openShareMenu(undefined, NeatoLib.Modules.get("message").message.split(" ").join(""), `"${message.content}" - ${message.author.username}`);
dropdown.style.display = "none";
});
}
});
setTimeout(() => {
if(document.getElementsByClassName("popouts-3dRSmE")) this.popoutObserver.observe(document.getElementsByClassName("popouts-3dRSmE")[0], { childList : true });
}, 5000);
this.contextEvent = e => {
if(!NeatoLib.ContextMenu.get()) setTimeout(() => this.onContextMenu(e), 0);
else this.onContextMenu(e);
};
document.addEventListener("contextmenu", this.contextEvent);
NeatoLib.Changelog.compareVersions(this.getName(), this.getChanges());
NeatoLib.Events.onPluginLoaded(this);
}
saveData() { NeatoLib.Data.save("ShareButton", "data", { recentChannels : this.recentChannels, pinnedChannels : this.pinnedChannels }); }
onContextMenu(e) {
if(e.target.localName != "img" && e.target.localName != "video" && !e.target.className.includes("markup")) return;
let choices = [], pinnedChannelsItem = [], recentChannelsItem = [];
let channelClick = (channel, ce) => {
let url = e.target.src;
if(!url) url = e.target.href;
url = url.split("?")[0];
let msg = NeatoLib.ReactData.getProps(e.target).message;
if(!url) url = `"${msg.content}" - ${msg.author.username}`;
ce.currentTarget.dataset.guildId = channel.guild_id;
ce.currentTarget.dataset.channelId = channel.id;
ce.currentTarget.dataset.content = url;
this.sendMessage(ce);
ce.currentTarget.innerText = "Sent!";
ce.currentTarget.style.backgroundColor = "#43b581";
ce.currentTarget.style.cursor = "default";
ce.currentTarget.onclick = null;
};
for(let i = 0; i < this.pinnedChannels.length; i++) {
let channel = this.channelModule.getChannel(this.pinnedChannels[i]);
if(!channel) continue;
pinnedChannelsItem.push(NeatoLib.ContextMenu.createItem("#" + channel.name, ce => channelClick(channel, ce), { hint : this.guildModule.getGuild(channel.guild_id).name }));
}
for(let i = 0; i < this.recentChannels.length; i++) {
let channel = this.channelModule.getChannel(this.recentChannels[i]);
if(!channel) continue;
recentChannelsItem.push(NeatoLib.ContextMenu.createItem("#" + channel.name, ce => channelClick(channel, ce), { hint : this.guildModule.getGuild(channel.guild_id).name }));
}
let open = () => { this.openShareMenu(e); NeatoLib.ContextMenu.close(); };
choices.push(NeatoLib.ContextMenu.createSubMenu("Pinned Channels", pinnedChannelsItem));
choices.push(NeatoLib.ContextMenu.createSubMenu("Recent Channels", recentChannelsItem));
choices.push(NeatoLib.ContextMenu.createItem("Open Share Menu", open));
NeatoLib.ContextMenu.get().insertAdjacentElement("afterBegin", NeatoLib.ContextMenu.createSubMenu("Share", [NeatoLib.ContextMenu.createGroup(choices)], { callback : open }));
}
openShareMenu(e, definedName, definedData) {
if(document.getElementById("sb-menu")) return document.getElementById("sb-menu").remove();
let menu = NeatoLib.UI.createBasicScrollList("sb-menu", "Share");
menu.window.insertAdjacentHTML("afterBegin", `
`);
let url, filename;
if(definedName && definedData) {
url = definedData;
filename = definedName;
} else {
url = e.target.src;
if(!url) {
url = e.target.href;
filename = url;
} else {
url = url.split("?")[0];
filename = url.substring(url.lastIndexOf("/") + 1, url.length);
}
}
let msg = NeatoLib.ReactData.getProps(e.target).message;
if(!url) url = `"${msg.content}" - ${msg.author.username}`;
if(filename) menu.window.getElementsByTagName("h2")[0].innerText += filename;
let recentChannels, pinnedChannels;
let updateChannels = () => {
let ci = document.getElementsByClassName("sb-channel-item-button");
for(let i = 0; i < ci.length; i++) {
ci[i].onclick = channelClickEvent;
ci[i].oncontextmenu = channelContextEvent;
}
},
updateRecentChannels = () => {
if(recentChannels) {
recentChannels.remove();
recentChannels = null;
}
for(let i = 0; i < this.recentChannels.length; i++) {
let channel = this.channelModule.getChannel(this.recentChannels[i]), guild;
if (channel) guild = this.guildModule.getGuild(channel.guild_id);
if(!channel || !guild) continue;
if(!recentChannels) recentChannels = document.getElementById("sb-servers").insertBefore(NeatoLib.DOM.createElement({
id : "sb-recent-channels",
innerHTML : `Recent Channels
`
}), null);
let ci = document.createElement("div");
ci.className = "sb-channel-item-button sb-button";
ci.dataset.guildId = guild.id;
ci.dataset.channelId = channel.id;
ci.dataset.content = url;
ci.innerHTML = `#${channel.name} - ${guild.name}
`;
recentChannels.appendChild(ci);
}
updateChannels()
},
updatePinnedChannels = () => {
if(pinnedChannels) {
pinnedChannels.remove();
pinnedChannels = null;
}
for(let i = 0; i < this.pinnedChannels.length; i++) {
let channel = this.channelModule.getChannel(this.pinnedChannels[i]), guild;
if (channel) guild = this.guildModule.getGuild(channel.guild_id);
if(!channel || !guild) continue;
if(!pinnedChannels) {
menu.scroller.insertAdjacentHTML("afterBegin", ``);
pinnedChannels = document.getElementById("sb-pinned-channels");
}
let ci = document.createElement("div");
ci.className = "sb-channel-item-button sb-button";
ci.dataset.guildId = guild.id;
ci.dataset.channelId = channel.id;
ci.dataset.content = url;
ci.innerHTML = `#${channel.name} - ${guild.name}
`;
pinnedChannels.appendChild(ci);
}
updateChannels();
};
let guilds = [], guildsParent, allChannels = Object.values(this.channelModule.getChannels()).sort((x, y) => x.position - y.position);
this.sortedGuildModule.getSortedGuilds().forEach(e => {
guilds.push(...e.guilds);
});
for(let i = 0; i < guilds.length; i++) {
if(!guildsParent) {
menu.scroller.insertAdjacentHTML("beforeEnd",
``);
guildsParent = document.getElementById("sb-servers");
document.getElementsByClassName("sb-dm-item")[0].onclick = e => {
if(e.target.classList.contains("sb-dm-subitem")) return;
let par = e.currentTarget.getElementsByClassName("sb-server-item-channels")[0], dms = this.dmModule.getPrivateChannelIds();
let dmClickEvent = e => {
this.sendMessage(e);
e.currentTarget.style.backgroundColor = "#43b581";
e.currentTarget.style.cursor = "default";
NeatoLib.showToast("Shared!", "success");
e.currentTarget.onclick = null;
getEventListeners(e.currentTarget).click[0].remove();
};
if(par.children.length) {
par.innerHTML = "";
e.currentTarget.style.backgroundColor = "";
} else {
for(let di = 0; di < dms.length; di++) {
let dm = this.channelModule.getChannel(dms[di]);
if(dm.recipients.length > 1) {
let item = document.createElement("div");
item.className = "sb-server-item sb-dm-subitem sb-button";
item.style.margin = "15px";
item.style.marginLeft = "30px";
item.dataset.channelId = dms[di];
item.dataset.content = url;
item.innerHTML = `${Array.from(dm.recipients, uid => this.userModule.getUser(uid).username).join(", ")}
`;
item.addEventListener("click", dmClickEvent);
par.appendChild(item);
} else {
let user = this.userModule.getUser(dm.recipients[0]);
if(!user) continue;
let item = document.createElement("div");
item.className = "sb-server-item sb-dm-subitem sb-button";
item.style.margin = "15px";
item.style.marginLeft = "30px";
item.dataset.channelId = dms[di];
item.dataset.content = url;
item.innerHTML = ``;
item.addEventListener("click", dmClickEvent);
par.appendChild(item);
}
}
}
};
}
guildsParent.insertAdjacentHTML("beforeEnd", ``);
}
let channelClickEvent = e => {
this.sendMessage(e);
e.currentTarget.getElementsByClassName("sb-channel-item")[0].innerText = "Shared!";
e.currentTarget.style.backgroundColor = "#43b581";
e.currentTarget.style.cursor = "default";
NeatoLib.showToast("Shared!", "success");
e.currentTarget.removeEventListener("click", channelClickEvent);
e.currentTarget.removeEventListener("contextmenu", channelContextEvent);
if(this.recentChannels.indexOf(e.currentTarget.dataset.channelId) != -1) this.recentChannels.splice(this.recentChannels.indexOf(e.currentTarget.dataset.channelId), 1);
this.recentChannels.push(e.currentTarget.dataset.channelId);
while(this.recentChannels.length >= 10) this.recentChannels.splice(0, 1);
updateRecentChannels();
this.saveData();
},
channelContextEvent = e => {
let items = [], cid = e.currentTarget.dataset.channelId;
if(this.pinnedChannels.includes(cid)) items.push(NeatoLib.ContextMenu.createItem("Unpin", () => {
this.pinnedChannels.splice(this.pinnedChannels.indexOf(cid), 1);
updatePinnedChannels();
this.saveData();
NeatoLib.ContextMenu.close();
}));
else items.push(NeatoLib.ContextMenu.createItem("Pin", () => {
this.pinnedChannels.push(cid);
updatePinnedChannels();
this.saveData();
NeatoLib.ContextMenu.close();
}));
NeatoLib.ContextMenu.create([NeatoLib.ContextMenu.createGroup(items)], e);
};
let serverItems = Array.from(document.getElementsByClassName("sb-server-item")).filter(e => !e.classList.contains("sb-dm-item")),
serverItemClickEvent = e => {
if(e.target.classList.contains("sb-channel-item")) return;
let targ = NeatoLib.DOM.searchForParentElementByClassName(e.target, "sb-server-item");
let par = targ.getElementsByClassName("sb-server-item-channels")[0], guild = this.guildModule.getGuild(targ.dataset.serverId), categories = [];
if(par.children.length) {
par.innerHTML = "";
targ.style.backgroundColor = "";
} else {
for(let i = 0; i < allChannels.length; i++) {
if(allChannels[i].guild_id == targ.dataset.serverId && allChannels[i].type == 0) {
if(allChannels[i].parent_id && categories.indexOf(allChannels[i].parent_id) == -1) {
par.insertAdjacentHTML("beforeEnd", `${this.channelModule.getChannel(allChannels[i].parent_id).name}
`);
categories.push(allChannels[i].parent_id);
}
if(!allChannels[i].parent_id && categories.indexOf("uncategorized") == -1) {
par.insertAdjacentHTML("beforeEnd", ``);
categories.push("uncategorized");
}
let item = document.createElement("div");
item.className = "sb-channel-item-button sb-button";
item.dataset.guildId = guild.id;
item.dataset.channelId = allChannels[i].id;
item.dataset.content = url;
item.innerHTML = `#${allChannels[i].name}
`;
par.appendChild(item);
}
}
updateChannels();
}
};
for(let i = 0; i < serverItems.length; i++) serverItems[i].onclick = serverItemClickEvent;
updatePinnedChannels();
updateRecentChannels();
}
sendMessage(e) {
let lastServer = NeatoLib.getSelectedServer(), lastChannel = NeatoLib.getSelectedTextChannel(), lastScroll = document.getElementsByClassName(NeatoLib.getClass("messages"))[0].scrollTop;
this.transitionModule.transitionTo(e.currentTarget.dataset.channelId, e.currentTarget.dataset.guildId);
this.messageModule.sendMessage(e.currentTarget.dataset.channelId, { content : e.currentTarget.dataset.content });
if(lastServer && lastChannel) this.transitionModule.transitionTo(lastChannel.id, lastServer.id);
document.getElementsByClassName(NeatoLib.getClass("messages"))[0].scrollTop = lastScroll;
}
stop() {
document.removeEventListener("contextmenu", this.contextEvent);
if(this.popoutObserver != undefined) this.popoutObserver.disconnect();
}
}
================================================
FILE: SuppressUserMentions.plugin.js
================================================
//META{"name":"SuppressUserMentions","website":"https://metalloriff.github.io/toms-discord-stuff/","source":"https://github.com/Metalloriff/BetterDiscordPlugins/blob/master/SuppressUserMentions.plugin.js"}*//
class SuppressUserMentions {
getName() { return "SuppressUserMentions"; }
getDescription() { return "Allows you to suppress mentions from specified users without blocking them."; }
getVersion() { return "0.0.2"; }
getAuthor() { return "Metalloriff"; }
getChanges() {
return {
};
}
load() {}
start() {
const libLoadedEvent = () => {
try{ this.onLibLoaded(); }
catch(err) { console.error(this.getName(), "fatal error, plugin could not be started!", err); try { this.stop(); } catch(err) { console.error(this.getName() + ".stop()", err); } }
};
let lib = document.getElementById("NeatoBurritoLibrary");
if (!lib) {
lib = document.createElement("script");
lib.id = "NeatoBurritoLibrary";
lib.type = "text/javascript";
lib.src = "https://rawgit.com/Metalloriff/BetterDiscordPlugins/master/Lib/NeatoBurritoLibrary.js";
document.head.appendChild(lib);
}
this.forceLoadTimeout = setTimeout(libLoadedEvent, 30000);
if (typeof window.NeatoLib !== "undefined") libLoadedEvent();
else lib.addEventListener("load", libLoadedEvent);
}
get settingFields() {
return {
suppressedUsers: { title: "Suppressed user IDs", description: "You can also add and remove suppressed users from the context menu", type: "string", array: true }
};
}
get defaultSettings() {
return {
displayUpdateNotes: true,
suppressedUsers: ["454465635972284428"]
};
}
getSettingsPanel() {
return NeatoLib.Settings.createPanel(this);
}
saveSettings() {
NeatoLib.Settings.save(this);
}
onLibLoaded() {
this.settings = NeatoLib.Settings.load(this);
NeatoLib.Updates.check(this);
//if (this.settings.displayUpdateNotes) NeatoLib.Changelog.compareVersions(this.getName(), this.getChanges());
this.unpatch = NeatoLib.monkeyPatchInternal(NeatoLib.Modules.get("isMentioned"), "isMentioned", e => {
if (this.settings.suppressedUsers.includes(e.args[0].author.id)) return false;
else return e.callDefault();
});
document.addEventListener("contextmenu", this.contextEvent = e => this.onContextMenu(e));
NeatoLib.Events.onPluginLoaded(this);
}
onContextMenu(e) {
if (!e.target.className.includes("username") && !e.target.className.includes("large")) return;
const contextMenu = NeatoLib.ContextMenu.get();
if (!contextMenu) return;
const uid = NeatoLib.ReactData.get(contextMenu).return.return.return.return.memoizedProps.user.id;
contextMenu.insertAdjacentElement("afterBegin", NeatoLib.ContextMenu.createGroup([
!this.settings.suppressedUsers.includes(uid) ? NeatoLib.ContextMenu.createItem("Suppress User Mentions", () => {
this.settings.suppressedUsers.push(uid);
this.saveSettings();
NeatoLib.ContextMenu.close();
}) :
NeatoLib.ContextMenu.createItem("Unsuppress User Mentions", () => {
this.settings.suppressedUsers.splice(this.settings.suppressedUsers.indexOf(uid), 1);
this.saveSettings();
NeatoLib.ContextMenu.close();
})
]));
}
stop() {
this.unpatch();
document.removeEventListener("contextmenu", this.contextEvent);
}
}
================================================
FILE: TheClapBestClapPluginClapEver.plugin.js
================================================
/**
* @name TheClapBestClapPluginClapEver
* @invite yNqzuJa
* @authorLink https://github.com/Metalloriff
* @donate https://www.paypal.me/israelboone
* @website https://metalloriff.github.io/toms-discord-stuff/
* @source https://github.com/Metalloriff/BetterDiscordPlugins/blob/master/TheClapBestClapPluginClapEver.plugin.js
*/
module.exports = (() =>
{
const config =
{
info:
{
name: "TheClapBestClapPluginClapEver",
authors:
[
{
name: "Metalloriff",
discord_id: "264163473179672576",
github_username: "metalloriff",
twitter_username: "Metalloriff"
}
],
version: "2.1.0",
description: "Literally useless, toxic cancer. Write 'clapclap$' at the beginning of your message to separate each word with a clap emoji. Write 'clapclap( :your_emoji: )$' to separate each word with your own custom emoji. 'superclapclap$' or 'superclapclap( :emoji: )$' for every letter instead of every word. 'ra$' to replace every letter with a regional indicator emoji. 'reverse$' to reverse your message. 'b$' to replace every 'b' with the B emoji. 'woke$' to capitalize every other letter. 'owo$' if you have no will to live.",
github: "https://github.com/Metalloriff/BetterDiscordPlugins/blob/master/TheClapBestClapPluginClapEver.plugin.js",
github_raw: "https://raw.githubusercontent.com/Metalloriff/BetterDiscordPlugins/master/TheClapBestClapPluginClapEver.plugin.js"
},
changelog:
[
{
title: "2.1.0",
type: "added",
items:
[
"Added 'woke$' option. This will capitalize every other letter in your message, showing that you are, in fact, the most intelligent and dominant person in chat."
]
}
]
};
return (([Plugin, Api]) => {
const plugin = (Plugin, Api) =>
{
const { DiscordModules, Patcher } = Api;
return class TheClapBestClapPluginClapEver extends Plugin
{
constructor()
{
super();
}
onStart()
{
Patcher.after(DiscordModules.MessageActions, "sendMessage", (_, [, message]) =>
{
const content = message.content.toLowerCase();
const clapclap = (/^clapclap(\S| )*\$/g).exec(content) || (/^superclapclap(\S| )*\$/g).exec(content);
if (clapclap)
{
const filler = clapclap[0].includes("(") && clapclap[0].includes(")")
? clapclap[0].substr(clapclap[0].indexOf("(") + 1, clapclap[0].indexOf(")") - clapclap[0].indexOf("(") - 1)
: " :clap: ";
message.content = message.content.substr(clapclap[0].length, message.content.length)
.split(clapclap[0].startsWith("super") ? "" : " ")
.join(filler);
message.content = filler + message.content + filler;
return;
}
switch (content.split("$")[0])
{
case "ra":
const ra = (/^ra\$/g).exec(content);
message.content = message.content.toLowerCase().substr(ra[0].length, message.content.length)
.split(" ")
.join("\t")
.replace(/[A-Za-z]/g, x => ` :regional_indicator_${x.toLowerCase()}: `);
break;
case "reverse":
const reverse = (/^reverse\$/g).exec(content);
message.content = message.content.substr(reverse[0].length, message.content.length)
.split("")
.reverse()
.join("");
break;
case "owo":
const owo = (/^owo\$/g).exec(content);
message.content = message.content.substr(owo[0].length, message.content.length)
.replace(/r/g, "w")
.replace(/R/g, "W")
.replace(/l/g, "w")
.replace(/L/g, "W")
.replace(/ n/g, " ny")
.replace(/ N/g, " Ny")
.replace(/ove/g, "uv")
.replace(/OVE/g, "UV")
+ " " + ["owo", "OwO", "uwu", "UwU", ">w<", "^w^", "♥w♥"][7 * Math.random() << 0];
break;
case "b":
const b = (/^b\$/g).exec(content);
message.content = message.content.substr(b[0].length, message.content.length)
.replace(/b/g, ":b:");
break;
case "woke":
const woke = (/^woke\$/g).exec(content);
message.content = message.content.substr(woke[0].length, message.content.length)
.replace(/.{2}/gm, c => c[0].toUpperCase() + c[1].toLowerCase());
break;
}
});
}
onStop()
{
Patcher.unpatchAll();
}
}
};
return plugin(Plugin, Api);
})(global.ZeresPluginLibrary.buildPlugin(config));
})();
================================================
FILE: TransitioningBackgrounds.plugin.js
================================================
/**
* @name TransitioningBackgrounds
* @invite yNqzuJa
* @authorLink https://github.com/Metalloriff
* @donate https://www.paypal.me/israelboone
* @website https://metalloriff.github.io/toms-discord-stuff/
* @source https://github.com/Metalloriff/BetterDiscordPlugins/blob/master/TransitioningBackgrounds.plugin.js
*/
/* TODO
Add server-based images.
Slow zoom option.
*/
module.exports = (() =>
{
const ImageSource =
{
Folder: 1
};
const TransitionMethod =
{
FadeInOut: 1,
SlideLeft: 2,
SlideRight: 3,
SlideUp: 4,
SlideDown: 5,
Shrink: 6,
RotateX: 7,
RotateY: 8,
ZoomFade: 9
};
const SortType =
{
Shuffle: 1,
AZ: 2
};
const ImageSizeMode =
{
Auto: "auto",
Cover: "cover",
Contain: "contain",
Initial: "initial",
Stretch: "100% 100%"
};
const config =
{
info:
{
name: "TransitioningBackgrounds",
authors:
[
{
name: "Metalloriff",
discord_id: "264163473179672576",
github_username: "metalloriff",
twitter_username: "Metalloriff"
}
],
version: "2.2.3",
description: "Allows you to set a list of background images, or pick a source, to transitioning between using various animations and sort modes.",
github: "https://github.com/Metalloriff/BetterDiscordPlugins/blob/master/TransitioningBackgrounds.plugin.js",
github_raw: "https://raw.githubusercontent.com/Metalloriff/BetterDiscordPlugins/master/TransitioningBackgrounds.plugin.js"
},
defaultConfig:
[
{
type: "category",
name: "General Settings",
id: "general",
collapsible: true,
shown: false,
settings:
[
{
type: "slider",
name: "Image Life (seconds)",
id: "imageLife",
value: 30,
min: 0,
max: 601,
renderValue: v =>
v <= 600
? Math.round(v) + " seconds"
: "on startup"
},
{
type: "dropdown",
name: "Image Size Mode",
id: "imageSizeMode",
value: ImageSizeMode.Cover,
options:
[
{ label: "Auto", value: ImageSizeMode.Auto },
{ label: "Cover", value: ImageSizeMode.Cover },
{ label: "Contain", value: ImageSizeMode.Contain },
{ label: "Initial", value: ImageSizeMode.Initial },
{ label: "Stretch", value: ImageSizeMode.Stretch }
]
},
{
type: "switch",
name: "Force Transparency",
note: "For use without a theme.",
id: "forceTransparency",
value: false
},
{
type: "slider",
name: "Background Brightness",
id: "backgroundBrightness",
value: 0.5,
min: 0,
max: 1
}
]
},
{
type: "category",
name: "Image Source Settings",
id: "imageSource",
collapsible: true,
shown: false,
settings:
[
{
type: "dropdown",
name: "Image Source",
id: "source",
value: ImageSource.Folder,
options: [
{ label: "Folder", value: ImageSource.Folder }
]
},
{
type: "textbox",
name: "Source Path",
id: "folder",
value: ""
}
]
},
{
type: "category",
name: "Transition Settings",
id: "transition",
collapsible: true,
shown: false,
settings:
[
{
type: "dropdown",
name: "Transition Method",
id: "method",
value: TransitionMethod.FadeInOut,
options:
[
{ label: "Fade In/Out", value: TransitionMethod.FadeInOut },
{ label: "Slide Left", value: TransitionMethod.SlideLeft },
{ label: "Slide Right", value: TransitionMethod.SlideRight },
{ label: "Slide Up", value: TransitionMethod.SlideUp },
{ label: "Slide Down", value: TransitionMethod.SlideDown },
{ label: "Shrink", value: TransitionMethod.Shrink },
{ label: "Rotate X", value: TransitionMethod.RotateX },
{ label: "Rotate Y", value: TransitionMethod.RotateY },
{ label: "Zoom In And Fade Out", value: TransitionMethod.ZoomFade }
]
},
{
type: "slider",
name: "Transition Time (seconds)",
id: "time",
value: 3,
min: 0,
max: 60,
renderValue: v => Math.round(v) + " seconds"
},
{
type: "dropdown",
name: "Sort Method",
id: "sort",
value: SortType.Shuffle,
options: [
{ label: "Shuffle On Start", value: SortType.Shuffle },
{ label: "Alphabetically", value: SortType.AZ }
]
}
]
}
],
changelog:
[
{
title: "changes and bug fixes",
type: "fixed",
items:
[
"Your settings are now based on your user ID."
]
}
]
};
return !global.ZeresPluginLibrary ? class
{
constructor() { this._config = config; }
getName = () => config.info.name;
getAuthor = () => config.info.description;
getVersion = () => 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 (err, res, body) =>
{
if (err) 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 { WebpackModules, DiscordModules: { UserStore: { getCurrentUser } }, PluginUtilities } = Api;
const fs = require("fs");
const path = require("path");
const { Buffer } = require("buffer");
return class TransitioningBackgrounds extends Plugin
{
constructor()
{
super();
}
async showChangelog(footer)
{
try { footer = (await WebpackModules.getByProps("getUser", "acceptAgreements").getUser("264163473179672576")).tag + " | https://discord.gg/yNqzuJa"; }
finally { super.showChangelog(footer); }
}
getDataName = () => this.getName() + "." + getCurrentUser().id;
loadSettings = s => PluginUtilities.loadSettings(this.getDataName(), PluginUtilities.loadSettings(this.getName(), s || this.defaultSettings));
saveSettings = s => PluginUtilities.saveSettings(this.getDataName(), this.settings || s);
getSettingsPanel() {
const panel = this.buildSettingsPanel();
panel.addListener(() => {
this.term();
this.init();
});
return panel.getElement();
}
createElement(type, options)
{
const e = document.createElement(type);
for (let o in options)
{
e[o] = options[o];
}
return e;
}
get CSS()
{
return `
.tb-image
{
display: none;
background-size: ${this.settings.general.imageSizeMode};
background-position: center;
background-repeat: no-repeat;
width: 100%;
height: 100%;
position: absolute;
filter: brightness(${this.settings.general.backgroundBrightness});
}
.tb-active .tb-image
{
display: block;
}
.tb-transition-${TransitionMethod.FadeInOut} { transition: opacity ${this.settings.transition.time}s; opacity: 0 }
.tb-transition-${TransitionMethod.SlideLeft} { transition: transform ${this.settings.transition.time}s; transform: translateX(-100%) }
.tb-transition-${TransitionMethod.SlideRight} { transition: transform ${this.settings.transition.time}s; transform: translateX(100%) }
.tb-transition-${TransitionMethod.SlideUp} { transition: transform ${this.settings.transition.time}s; transform: translateY(-100%) }
.tb-transition-${TransitionMethod.SlideDown} { transition: transform ${this.settings.transition.time}s; transform: translateY(100%) }
.tb-transition-${TransitionMethod.Shrink} { transition: transform ${this.settings.transition.time}s; transform: scale(0) }
.tb-transition-${TransitionMethod.RotateX} { transition: transform ${this.settings.transition.time}s; transform: rotateX(180deg) }
.tb-transition-${TransitionMethod.RotateY} { transition: transform ${this.settings.transition.time}s; transform: rotateY(180deg) }
.tb-transition-${TransitionMethod.ZoomFade} { transition: transform ${this.settings.transition.time}s, opacity ${this.settings.transition.time}s; transform: scale(5); opacity: 1 }
`;
}
get ForceTransparencyCSS()
{
if (!this.settings.general.forceTransparency)
return "";
const opacity = 0.3;
return `
:root
{
--background-primary: rgba(0,0,0,${opacity});
--background-secondary: rgba(0,0,0,${opacity});
--background-secondary-alt: rgba(0,0,0,${opacity});
--background-tertiary: rgba(0,0,0,${opacity});
--background-accent: rgba(0,0,0,${opacity});
--background-floating: rgba(0,0,0,${opacity});
--background-modifier-hover: rgba(0,0,0,0.1);
--background-modifier-selected: rgba(0,0,0,0.3);
--channeltextarea-background: rgba(0,0,0,${opacity});
}
.theme-dark .container-1D34oG
{
background: var(--background-primary);
}
.typeWindows-1za-n7
{
background: black;
margin: 0;
border: 3px solid black;
}
`;
}
onStart = () => this.init();
onStop = () => this.term();
init()
{
if (this.settings.forceTransparency != undefined)
this.settings = this.defaultSettings;
PluginUtilities.addStyle("tb-css", this.CSS);
PluginUtilities.addStyle("tb-ft-css", this.ForceTransparencyCSS);
this.el = this.createElement("div", { id: "tb-container", className: "tb-active" });
this.image0 = this.createElement("div", { id: "tb-image-0", className: "tb-image" });
this.image1 = this.createElement("div", { id: "tb-image-1", className: "tb-image" });
this.el.appendChild(this.image0);
this.el.appendChild(this.image1);
document.getElementsByTagName("html")[0].prepend(this.el);
this.activeImage = this.image1;
this.inactiveImage = this.image0;
this.index = 0;
this.loadedImageNames = null;
if (this.settings.general.imageLife <= 600)
this.loop = setInterval(() => this.main(), this.settings.general.imageLife * 1000);
this.main();
this.updateNextImage();
this.onVisibilityChanged = () =>
{
const container = document.getElementById("tb-container");
container.classList[document.hidden ? "remove" : "add"]("tb-active");
};
document.addEventListener("visibilitychange", this.onVisibilityChanged);
}
set nextImageURL(url)
{
this.inactiveImage.style.backgroundImage = `url(${url})`;
}
shuffle(arr)
{
for (let i = arr.length - 1; i > 0; i--)
{
let r = Math.floor(Math.random() * (i + 1));
let t = arr[i];
arr[i] = arr[r];
arr[r] = t;
}
}
async main()
{
this.activeImage = this.activeImage == this.image0 ? this.image1 : this.image0;
this.inactiveImage = this.activeImage == this.image0 ? this.image1 : this.image0;
this.inactiveImage.classList.remove("tb-transition-" + this.settings.transition.method);
this.activeImage.classList.add("tb-transition-" + this.settings.transition.method);
await new Promise(r => setTimeout(r, this.settings.transition.time));
this.updateNextImage();
}
updateNextImage()
{
switch (this.settings.imageSource.source)
{
case ImageSource.Folder:
if (this.loadedImageNames == null)
{
try
{
this.loadedImageNames = [];
if (this.settings.imageSource.folder != null && this.settings.imageSource.folder.length > 0)
{
const files = fs.readdirSync(this.settings.imageSource.folder, { withFileTypes: true });
for (let { name } of files)
{
if (name.match(/(\.jpg|\.jpeg|\.png|\.gif|\.webp)$/g) != null)
{
this.loadedImageNames.push(name);
}
}
}
switch (this.settings.transition.sort)
{
case SortType.Shuffle:
this.shuffle(this.loadedImageNames);
break;
case SortType.AZ:
this.loadedImageNames = this.loadedImageNames.sort()
break;
}
}
catch (exc)
{
console.error(exc);
BdApi.showConfirmationModal("TransitioningBackgrounds: Error reading files from directory.", exc.toString());
this.loadedImageNames = [];
}
}
if (this.loadedImageNames.length > 0)
{
const fp = path.join(this.settings.imageSource.folder, this.loadedImageNames[this.index]);
fs.readFile(fp, (exc, data) =>
{
if (exc != null)
{
console.error(exc);
return;
}
const ext = path.extname(fp);
const b64 = Buffer.from(data, "binary").toString("base64");
const src = `data:image/${ext.split(".")[1]};base64,${b64}`;
this.nextImageURL = src;
});
}
this.index++;
if (this.index >= this.loadedImageNames.length)
this.index = 0;
break;
}
}
term()
{
this.el.remove();
PluginUtilities.removeStyle("tb-css");
PluginUtilities.removeStyle("tb-ft-css");
document.removeEventListener("visibilitychange", this.onVisibilityChanged);
if (this.loop != null)
clearTimeout(this.loop);
}
}
};
return plugin(Plugin, Api);
})(global.ZeresPluginLibrary.buildPlugin(config));
})();
================================================
FILE: UnreadCountBadges.plugin.js
================================================
//META{"name":"UnreadCountBadges","website":"https://metalloriff.github.io/toms-discord-stuff/","source":"https://github.com/Metalloriff/BetterDiscordPlugins/blob/master/UnreadCountBadges.plugin.js"}*//
class UnreadCountBadges {
getName() { return "UnreadCountBadges"; }
getDescription() { return "Adds an unread count badge on unread servers and channels."; }
getVersion() { return "0.2.7"; }
getAuthor() { return "Metalloriff"; }
getChanges() {
return {
"0.1.1":
`
Redid the way the plugin counts unreads. The upsides of this, is that the counts will have no limit now, will be more accurate, and will not require the channel/server to be loaded. The downside is that it will only show unread messages since the plugin was started.
Fixed messages not being marked as read until you switch channels/servers.
`,
"0.1.2":
`
The last update was a lie, it was far from more accurate. Fixed that.
`,
"0.2.2":
`
Discord broke it, I fixed it, that is all.
`,
"0.2.5":
`
Fixed channel unread styles.
Fixed muted channels never being read.
Fixed the muted channel opacity setting.
`
};
}
load() {}
start() {
let libLoadedEvent = () => {
try{ this.onLibLoaded(); }
catch(err) { console.error(this.getName(), "fatal error, plugin could not be started!", err); try { this.stop(); } catch(err) { console.error(this.getName() + ".stop()", err); } }
};
let lib = document.getElementById("NeatoBurritoLibrary");
if(!lib) {
lib = document.createElement("script");
lib.id = "NeatoBurritoLibrary";
lib.type = "text/javascript";
lib.src = "https://rawgit.com/Metalloriff/BetterDiscordPlugins/master/Lib/NeatoBurritoLibrary.js";
document.head.appendChild(lib);
}
this.forceLoadTimeout = setTimeout(libLoadedEvent, 30000);
if(typeof window.NeatoLib !== "undefined") libLoadedEvent();
else lib.addEventListener("load", libLoadedEvent);
}
getSettingsPanel() {
setTimeout(() => {
NeatoLib.Settings.pushElement(NeatoLib.Settings.Elements.createToggleGroup("ucb-toggles", "Toggles", [
{ title : "Ignore muted servers", value : "ignoreMutedGuilds", setValue : this.settings.ignoreMutedGuilds }
], choice => {
this.settings[choice.value] = !this.settings[choice.value];
this.applySettings();
this.saveSettings();
}), this.getName());
NeatoLib.Settings.pushElement(NeatoLib.Settings.Elements.createNewTextField("Badge color", this.settings.badgeColor, e => {
this.settings.badgeColor = e.target.value;
this.applySettings();
this.saveSettings();
}), this.getName());
NeatoLib.Settings.pushHTML(`
`, this.getName());
for(let example of document.getElementsByClassName("containerDefault-1ZnADq ucb-ex")) {
const wrapper = example.getElementsByClassName("wrapper-KpKNwI")[0],
content = example.getElementsByClassName("content-20Aix8")[0],
text = example.getElementsByClassName("overflowEllipsis-jeThUf")[0];
wrapper.originalClassName = wrapper.className;
content.originalClassName = content.className;
text.originalClassName = text.className;
example.addEventListener("mouseenter", () => {
wrapper.className = "wrapperHoveredText-2geN_M wrapper-KpKNwI";
content.className = "contentHoveredText-2D9B-x content-20Aix8";
text.className = "nameHoveredText-1uO31y name-3M0b8v overflowEllipsis-jeThUf";
});
example.addEventListener("mouseleave", () => {
wrapper.className = wrapper.originalClassName;
content.className = content.originalClassName;
text.className = text.originalClassName;
});
}
NeatoLib.Settings.pushElement(NeatoLib.Settings.Elements.createNewTextField("Muted channel badge opacity", this.settings.mutedChannelBadgeOpacity, e => {
if(isNaN(e.target.value)) return NeatoLib.showToast("Value must be a number", "error");
this.settings.mutedChannelBadgeOpacity = e.target.value;
this.applySettings();
this.saveSettings();
}), this.getName());
NeatoLib.Settings.pushChangelogElements(this);
}, 0);
return NeatoLib.Settings.Elements.pluginNameLabel(this.getName());
}
saveSettings() {
NeatoLib.Settings.save(this);
}
onLibLoaded() {
this.settings = NeatoLib.Settings.load(this, {
displayUpdateNotes : true,
badgeColor : "#7289da",
mutedChannelBadgeOpacity : 0.3,
ignoreMutedGuilds : true
});
NeatoLib.Updates.check(this);
if(this.settings.displayUpdateNotes) NeatoLib.Changelog.compareVersions(this.getName(), this.getChanges());
this.guildModule = NeatoLib.Modules.get(["getGuild", "getGuilds"]);
this.unreadModule = NeatoLib.Modules.get("getUnreadCount");
this.channelModule = NeatoLib.Modules.get(["getChannel", "getChannels"]);
this.muteModule = NeatoLib.Modules.get("isMuted");
this.scrollModule = NeatoLib.Modules.get("isAtBottom");
this.badges = {};
this.channelBadges = {};
this.unreads = {};
this.checkForBottom = () => {
let guild = NeatoLib.getSelectedGuildId();
if(this.scrollModule.isAtBottom(guild)) {
this.updateBadges(guild);
}
};
this.applySettings();
this.switchEvent = () => {
for(let cid in this.channelBadges) {
this.channelBadges[cid].remove();
delete this.channelBadges[cid];
}
this.updateBadges();
let scroller = document.getElementsByClassName("messages scroller")[0], guild = NeatoLib.getSelectedGuild();
if(scroller && guild) scroller.addEventListener("scroll", this.checkForBottom);
};
this.checkForBottomUpdate = setInterval(this.checkForBottom, 500);
NeatoLib.Events.attach("switch", this.switchEvent);
this.unpatchHandleMessage = NeatoLib.monkeyPatchInternal(NeatoLib.Modules.get("handleMessage"), "handleMessage", e => {
try {
const data = e.args[0];
if(data.type == "MESSAGE_CREATE" && data.message) {
if(!this.unreads[data.message.guild_id]) this.unreads[data.message.guild_id] = { total : 0 };
if(!this.muteModule.isGuildOrCategoryOrChannelMuted(data.message.guild_id, data.message.channel_id) || !this.settings.ignoreMutedGuilds) this.unreads[data.message.guild_id].total++;
if(!this.unreads[data.message.guild_id][data.message.channel_id]) this.unreads[data.message.guild_id][data.message.channel_id] = 0;
this.unreads[data.message.guild_id][data.message.channel_id]++;
this.updateBadges(data.message.guild_id);
}
} catch(err) {
console.error(err);
} finally {
return e.callDefault();
}
}, this.getName());
NeatoLib.Events.onPluginLoaded(this);
this.updateBadges();
}
applySettings() {
if(this.styles) this.styles.destroy();
this.styles = NeatoLib.injectCSS(`
.${NeatoLib.getClass("unreadMentionsBar", "scroller")} .${NeatoLib.getClass("lurkingGuild", "badge")}.unread-count-badge {
bottom: 35px;
background-color: ${this.settings.badgeColor};
}
#app-mount .unread-count-channel-badge {
background-color: ${this.settings.badgeColor};
}
.${NeatoLib.Modules.get("wrapperMutedText").wrapperMutedText.split(" ").join(".")} .unread-count-channel-badge {
opacity: ${this.settings.mutedChannelBadgeOpacity};
}
`);
}
updateBadges(guild) {
const guilds = !guild ? this.guildModule.getGuilds() : undefined, selectedGuild = NeatoLib.getSelectedGuild();
const updateUnreadFor = id => {
if(!this.unreads[id]) return;
if(selectedGuild && selectedGuild.id == id) {
const classes = NeatoLib.Modules.get("wrapperDefaultText"), channels = document.getElementsByClassName(classes.wrapper);
for(let i = 0; i < channels.length; i++) {
const cid = NeatoLib.ReactData.getProp(channels[i].parentElement, "channel.id");
if(!cid) continue;
if(this.unreads[id][cid] > 0 && this.unreadModule.getUnreadCount(cid) == 0) {
this.unreads[id].total -= this.unreads[id][cid];
if(this.unreads[id].total < 0) this.unreads[id].total = 0;
this.unreads[id][cid] = 0;
}
if(this.unreads[id][cid] > 0) {
if(this.channelBadges[cid]) this.channelBadges[cid].firstChild.innerText = this.unreads[id][cid];
else this.channelBadges[cid] = channels[i].getElementsByClassName(classes.content)[0].lastChild.appendChild(this.createChannelBadge(this.unreads[id][cid]));
} else if(this.channelBadges[cid]) {
this.channelBadges[cid].remove();
delete this.channelBadges[cid];
}
}
}
if(this.unreads[id].total > 0) {
if(this.badges[id] != undefined) this.badges[id].innerText = this.unreads[id].total;
else this.badges[id] = NeatoLib.DOM.searchForParentElementByClassName(document.querySelector("[style*='" + id + "']"), NeatoLib.getClass("lurkingGuild", "container")).appendChild(this.createBadge(this.unreads[id].total));
} else if(this.badges[id]) {
this.badges[id].remove();
delete this.badges[id];
for(let cid in this.channelBadges) {
this.channelBadges[cid].remove();
delete this.channelBadges[cid];
}
}
};
if(guild) return updateUnreadFor(guild);
else if(guilds) for(let id in guilds) updateUnreadFor(id);
}
createBadge(unreadCount) {
const badge = document.createElement("div");
badge.className = "wrapper-232cHJ " + NeatoLib.getClass("lurkingGuild", "badge") + " unread-count-badge";
badge.innerText = unreadCount;
return badge;
}
createChannelBadge(unreadCount) {
const badge = document.createElement("div");
badge.className = NeatoLib.getClass("containerDragAfter", "iconSpacing");
badge.innerHTML = `${unreadCount}
`;
return badge;
}
stop() {
const css = document.getElementById("uc-css");
if(css) css.remove();
NeatoLib.Events.detach("switch", this.switchEvent);
this.styles.destroy();
for(let id in this.badges) this.badges[id].remove();
for(let id in this.channelBadges) this.channelBadges[id].remove();
this.unpatchHandleMessage();
clearInterval(this.checkForBottomUpdate);
}
}
================================================
FILE: UserBirthdays.plugin.js
================================================
/**
* @name UserBirthdays
* @invite yNqzuJa
* @authorLink https://github.com/metalloriff
* @donate https://www.paypal.me/israelboone
* @website https://metalloriff.github.io/toms-discord-stuff
* @source https://github.com/Metalloriff/BetterDiscordPlugins/blob/master/UserBirthdays.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": "UserBirthdays",
"authors": [{
"name": "Metalloriff",
"discord_id": "264163473179672576",
"github_username": "Metalloriff",
"twitter_username": "Metalloriff"
}],
"version": "3.0.0",
"description": "Allows you to set birthdays for users and get notified when it's a user's birthday.",
"github": "https://github.com/Metalloriff/BetterDiscordPlugins/blob/master/UserBirthdays.plugin.js",
"github_raw": "https://raw.githubusercontent.com/Metalloriff/BetterDiscordPlugins/master/UserBirthdays.plugin.js"
},
"changelog": [{
"title": "Rewritten!",
"type": "improved",
"items": ["UserBirthdays fixed by Danielle#1788"]
}]
};
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 {
Toasts,
PluginUtilities,
DiscordModules
} = Api;
const {
UserStore
} = DiscordModules;
return class UserBirthdays extends Plugin {
onStart() {
this.birthdays = PluginUtilities.loadData(this.getName(), "birthdays", {
"264163473179672576": {
day: "5/20",
hadIn: ""
}
});
const noteItem = (profile, uid) => {
let el = document.createElement("div");
let tt = document.createElement("div");
let birthday = this.birthdays[uid];
tt.className = this.c.tooltip.self;
tt.style = "opacity:0;position:absolute;top:15%;right:10%;transition:opacity 0.5s;animation: none;"
tt.innerHTML = `
Inproper date format!`;
el.appendChild(tt);
el.innerHTML += `
Birthday
`
el.getElementsByTagName("textarea")[0].addEventListener("input", e => {
let v = e.target.value;
let d = new Date(v);
let t = e.target.parentElement.parentElement.getElementsByClassName(this.c.tooltip.self)[0];
if (v.length && d == "Invalid Date") {
t.style.opacity = 1;
} else {
t.style.opacity = 0;
this.setBirthday(uid, v);
}
});
return el;
};
(this.o = new MutationObserver(m => {
for (let i = 0; i < m.length; i++) {
const p = m[i].addedNodes[0];
if (p != null) {
if (p.getElementsByClassName(this.c.userPopout)[0]) {
let uid = p.getElementsByClassName(this.c.userPopout)[0].dataset.userId;
if (uid) {
p.getElementsByClassName(this.c.note.self)[0].appendChild(noteItem(false, uid));
}
}
if (p.getElementsByClassName(this.c.profile.self)[0]) {
p.getElementsByClassName(this.c.profile.self)[0].appendChild(noteItem(true, p.getElementsByClassName(this.c.profile.self)[0].parentElement.parentElement.parentElement.dataset.userId));
}
}
}
})).observe(document.getElementsByClassName(this.c.layerContainer)[1], {
childList: true
});
this.o.observe(document.getElementsByClassName(this.c.layerContainer)[1].previousSibling.previousElementSibling, {
childList: true
});
this.o.observe(document.getElementsByClassName(this.c.popouts)[0], {
childList: true
});
this.loop = setInterval(() => {
const now = new Date();
for (let uid in this.birthdays) {
const _user = UserStore.getUser(uid),
birthday = new Date(this.birthdays[uid].day);
let user;
if (_user) {
user = {
avatar: _user.getAvatarURL(),
tag: _user.tag,
name: _user.username,
id: uid
}
} else if (uid == "264163473179672576") {
user = {
avatar: "https://cdn.discordapp.com/attachments/396895633732272139/707233064031486002/well_frickly_frack.png",
tag: "Metalloriff#2891",
name: "Metalloriff",
id: uid
}
} else {
user = {
avatar: "/assets/f046e2247d730629309457e902d5c5b3.svg",
tag: uid,
name: "Unknown User",
id: uid
}
}
if (now.getMonth() == birthday.getMonth() && now.getDate() == birthday.getDate() &&
(this.birthdays[uid].hadIn == "" || isNaN(this.birthdays[uid].hadIn) || now.getFullYear() != this.birthdays[uid].hadIn)) {
this.createBirthdayItem(user);
this.birthdays[uid].hadIn = now.getFullYear();
PluginUtilities.saveData(this.getName(), "birthdays", this.birthdays);
}
}
}, 60 * 1000)
}
onStop() {
this.o.disconnect();
clearInterval(this.loop);
}
get c() {
return {
app: "app-1q1i1E",
layerContainer: "layerContainer-yqaFcK",
userPopout: "userPopout-xaxa6l",
popouts: "popouts-2bnG9Z",
note: {
self: "note-1oo11U",
title: "bodyTitle-1ySSKn base-1x0h_U size12-3cLvbJ",
textArea: "textarea-2r0oV8 scrollbarGhostHairline-1mSOM1 scrollbar-3dvm_9"
},
profile: {
self: "userInfoSection-q_35fn",
note: "note-367eZJ"
},
tooltip: {
self: "tooltip-2QfLtc tooltipTop-XDDSxx tooltipBlack-PPG47z tooltipDisablePointerEvents-3eaBGN",
pointer: "tooltipPointer-3ZfirK"
}
}
}
setBirthday(uid, day) {
if (day) {
this.birthdays[uid] = {
day: day,
hadIn: ""
}
} else {
delete this.birthdays[uid];
}
PluginUtilities.saveData(this.getName(), "birthdays", this.birthdays);
Toasts.success("Birthday Set!")
}
createBirthdayItem(user) {
let i = document.getElementById("ub-birthday-item-" + user.id);
if (!i) {
i = document.createElement("div");
i.id = "ub-birthday-item-" + user.id;
i.style = `
background: white;
border-radius: 10px;
width: 80%;
height: 150px;
margin-top: 40px;
`;
i.innerHTML = `
${user.tag}
It's ${user.name}'s birthday today!
`;
this.createBirthdayContainer().appendChild(i);
}
return i;
}
createBirthdayContainer() {
let c = document.getElementById("ub-birthday-container");
if (!c) {
c = document.createElement("div");
c.id = "ub-birthday-container";
c.style = `
background: rgba(0, 0, 0, 0.7);
overflow: scroll;
position: absolute;
width: 100%;
height: 100%;
border-top: 50px solid transparent;
z-index: 1000;
opacity: 0;
transition: opacity 0.5s;
display: grid;
justify-items: center;
grid-template-columns: 1fr;
grid-auto-rows: 15%;
`;
c.addEventListener("click", e => {
e.currentTarget.style.opacity = 0;
setTimeout(() => {
c.remove();
}, 600);
});
document.getElementsByClassName(this.c.app)[0].appendChild(c);
setTimeout(() => {
c.style.opacity = 1;
}, 100);
}
return c;
}
};
};
return plugin(Plugin, Api);
})(global.ZeresPluginLibrary.buildPlugin(config));
})();
/*@end@*/
================================================
FILE: VCMuteSounds.plugin.js
================================================
//META{"name":"VCMuteSounds"}*//
class VCMuteSounds {
constructor(){
this.muteSound = new Audio();
this.unmuteSound = new Audio();
this.checkDelay = 100;
this.lastMuteCount = 0;
this.switched = false;
this.checkFunc;
}
getName() { return "Voice Chat Mute Sounds"; }
getDescription() { return "Enables the mute and unmute sound for all users in a voice call when the server/group is selected."; }
getVersion() { return "0.0.3"; }
getAuthor() { return "Metalloriff"; }
load() {}
getSettingsPanel(){
return "Volume: Mute Sound: Unmute Sound: Update Delay (ms):Reset Settings Save & Update "
}
saveSettings(){
var msc = document.getElementById("vcms-msc").value, usc = document.getElementById("vcms-usc").value, vol = document.getElementById("vcms-vol").value / 100, delay = document.getElementById("vcms-delay").value;
this.muteSound.src = msc;
this.muteSound.volume = vol;
this.unmuteSound.src = usc;
this.unmuteSound.volume = vol;
this.checkDelay = delay;
PluginUtilities.saveData("VCMuteSounds", "settings", {volume : vol, muteSoundClip : msc, unmuteSoundClip : usc, checkDelay : delay});
PluginUtilities.showToast("Settings saved!");
}
resetSettings(){
if(document.getElementById("vcms-vol")){
document.getElementById("vcms-vol").value = 50;
document.getElementById("vcms-msc").value = "https://discordapp.com/assets/e4d539271704b87764dc465b1a061abd.mp3";
document.getElementById("vcms-usc").value = "https://discordapp.com/assets/5a000a0d4dff083d12a1d4fc2c7cbf66.mp3";
document.getElementById("vcms-delay").value = 100;
PluginUtilities.showToast("Settings reset to defaults!");
this.saveSettings();
return;
}
this.muteSound.volume = 0.5;
this.muteSound.src = "https://discordapp.com/assets/e4d539271704b87764dc465b1a061abd.mp3";
this.unmuteSound.volume = 0.5;
this.unmuteSound.src = "https://discordapp.com/assets/5a000a0d4dff083d12a1d4fc2c7cbf66.mp3";
this.checkDelay = 100;
}
start() {
var libraryScript = document.getElementById('zeresLibraryScript');
if (!libraryScript) {
libraryScript = document.createElement("script");
libraryScript.setAttribute("type", "text/javascript");
libraryScript.setAttribute("src", "https://rauenzi.github.io/BetterDiscordAddons/Plugins/PluginLibrary.js");
libraryScript.setAttribute("id", "zeresLibraryScript");
document.head.appendChild(libraryScript);
}
if (typeof window.ZeresLibrary !== "undefined") this.initialize();
else libraryScript.addEventListener("load", () => { this.initialize(); });
}
initialize(){
PluginUtilities.checkForUpdate(this.getName(), this.getVersion(), "https://github.com/Metalloriff/BetterDiscordPlugins/raw/master/VCMuteSounds.plugin.js");
this.muteSound = new Audio();
this.unmuteSound = new Audio();
this.resetSettings();
var data = PluginUtilities.loadData("VCMuteSounds", "settings", {volume : 0.5, muteSoundClip : "https://discordapp.com/assets/e4d539271704b87764dc465b1a061abd.mp3",
unmuteSoundClip : "https://discordapp.com/assets/5a000a0d4dff083d12a1d4fc2c7cbf66.mp3", checkDelay : 100});
this.lastMuteCount = 0;
this.muteSound.src = data["muteSoundClip"];
this.muteSound.volume = data["volume"]
this.unmuteSound.src = data["unmuteSoundClip"];
this.unmuteSound.volume = data["volume"];
this.checkDelay = data["checkDelay"];
this.check();
}
onSwitch(){
this.switched = true;
}
check(){
var selectedVoiceChannel = document.getElementsByClassName("wrapperSelectedVoice-1Q1ocJ wrapper-fDmxzK")[0], muteCount = document.getElementsByClassName("callAvatarStatus-3y6S04").length;
if(selectedVoiceChannel != null || muteCount > 0){
if(selectedVoiceChannel != null)
muteCount = selectedVoiceChannel.parentElement.getElementsByClassName("iconSpacing-3jB4W5").length;
if(this.switched == false){
if(muteCount > this.lastMuteCount)
this.muteSound.play();
if(muteCount < this.lastMuteCount)
this.unmuteSound.play();
}
}
this.lastMuteCount = muteCount;
this.switched = false;
this.checkFunc = setTimeout(e => { this.check(e); }, this.checkDelay);
}
stop() {
clearTimeout(this.checkFunc);
}
}
================================================
FILE: VideoExamples/README.md
================================================
================================================
FILE: ViewGuildRelationships.plugin.js
================================================
/**
* @name ViewGuildRelationships
* @invite yNqzuJa
* @authorLink https://github.com/Metalloriff
* @donate https://www.paypal.me/israelboone
* @website https://metalloriff.github.io/toms-discord-stuff/
* @source https://github.com/Metalloriff/BetterDiscordPlugins/blob/master/ViewGuildRelationships.plugin.js.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: 'View Guild Relationships',
authors: [
{
name: "Metalloriff",
discord_id: "264163473179672576",
github_username: "metalloriff",
twitter_username: "Metalloriff"
},
],
version: '0.0.1',
description:
'Adds a View Relationships button to the guild dropdown and context menu that opens a list of all friends, requested friends, and blocked users in the server.',
github:
'https://github.com/Metalloriff/BetterDiscordPlugins/blob/master/ViewGuildRelationships.plugin.js',
github_raw:
'https://raw.githubusercontent.com/Metalloriff/BetterDiscordPlugins/master/ViewGuildRelationships.plugin.js',
},
changelog: [
{
title: 'rewrite',
type: 'added',
items: ['The plugin got rewritten.'],
},
],
};
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 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.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 {
WebpackModules,
PluginUtilities,
DiscordModules,
ReactComponents,
Patcher,
Utilities,
DiscordModules: { React },
} = Api;
const {
listRow,
listRowContent,
listName,
} = WebpackModules.getByProps('header', 'botTag', 'listAvatar');
const Avatar = WebpackModules.getByProps('AnimatedAvatar').default;
const TabComponent = WebpackModules.getByDisplayName('TabBar');
const Modal = WebpackModules.getByProps('CloseButton');
const { MenuItem, MenuGroup } = WebpackModules.getModule(
(m) => m.MenuRadioItem && !m.default
);
const
{
default: ScrollWrapper
} = WebpackModules.getByProps('ScrollerAuto');
const {
tabBarContainer,
tabBarItem,
tabBar,
} = WebpackModules.getByProps(
'root',
'topSectionStreaming',
'topSectionSpotify'
);
const { sizeMedium } = WebpackModules.getByProps(
'responsiveWidthMobile',
'modal',
'sizeSmall'
);
const Item = ({ user, guild, onClose }) => {
return React.createElement('div', {
className: listRow,
onClick: () => {
WebpackModules.getByProps(
'openPrivateChannel'
).openPrivateChannel(user.id);
onClose();
},
children: [
React.createElement(Avatar, {
src: user.getAvatarURL(),
isMobile: false,
isTyping: false,
size: 'SIZE_40',
}),
React.createElement('div', {
className: listRowContent,
style: {
marginLeft: '5px',
},
children: [
React.createElement('div', {
className: listName,
children: user.username,
}),
],
}),
],
});
};
const Tabs = [
{
label: 'Blocked Users',
id: 'BLOCKED_USERS',
element: (props, onClose) => {
var blockedUsers = [];
Object.entries(
DiscordModules.RelationshipStore.getRelationships()
).forEach(([key, value]) => {
if (value == 2)
blockedUsers.push(DiscordModules.UserStore.getUser(key));
});
const blocked = blockedUsers.filter(
(e) =>
!!DiscordModules.GuildMemberStore.getMember(props.id, e.id)
);
return blocked.length == 0
? React.createElement(
'p',
{
style: {
color: 'white',
fontWeight: 'bold',
textAlign: 'center',
},
},
'No Mutual Blocked Users'
)
: blocked.map((e) =>
React.createElement(Item, {
user: e,
guild: props,
onClose,
})
);
},
},
{
label: 'Incoming Friends',
id: 'INCOMING_FRIENDS',
element: (props, onClose) => {
var icomingFriends = [];
Object.entries(
DiscordModules.RelationshipStore.getRelationships()
).forEach(([key, value]) => {
if (value == 3)
icomingFriends.push(DiscordModules.UserStore.getUser(key));
});
const inc = icomingFriends.filter(
(e) =>
!!DiscordModules.GuildMemberStore.getMember(props.id, e.id)
);
return inc.length == 0
? React.createElement(
'p',
{
style: {
color: 'white',
fontWeight: 'bold',
textAlign: 'center',
},
},
'No Mutual Incoming Friends'
)
: inc.map((e) =>
React.createElement(Item, {
user: e,
guild: props,
onClose,
})
);
},
},
{
label: 'Outgoing Friends',
id: 'OUTGOING_FRIENDS',
element: (props, onClose) => {
var outgoingFriends = [];
Object.entries(
DiscordModules.RelationshipStore.getRelationships()
).forEach(([key, value]) => {
if (value == 4)
outgoingFriends.push(DiscordModules.UserStore.getUser(key));
});
const out = outgoingFriends.filter(
(e) =>
!!DiscordModules.GuildMemberStore.getMember(props.id, e.id)
);
return out.length == 0
? React.createElement(
'p',
{
style: {
color: 'white',
fontWeight: 'bold',
textAlign: 'center',
},
},
'No Mutual Outgoing Friends'
)
: out.map((e) =>
React.createElement(Item, {
user: e,
guild: props,
onClose,
})
);
},
},
{
label: 'Mutual Friends',
id: 'MUTUAL_FRIENDS',
element: (props, onClose) => {
var friends = [];
Object.entries(
DiscordModules.RelationshipStore.getRelationships()
).forEach(([key, value]) => {
if (value == 1)
friends.push(DiscordModules.UserStore.getUser(key));
});
const friend = friends.filter(
(e) =>
!!DiscordModules.GuildMemberStore.getMember(props.id, e.id)
);
return friend.length == 0
? React.createElement(
'p',
{
style: {
color: 'white',
fontWeight: 'bold',
textAlign: 'center',
},
},
'No Mutual Friends'
)
: friend.map((e) =>
React.createElement(Item, {
user: e,
guild: props,
onClose,
})
);
},
},
];
class Menu extends React.Component {
constructor(props) {
super(props);
this.state = { selected: Tabs[0].id };
}
render() {
return React.createElement(Modal, {
className: [sizeMedium, 'modal'],
children: [
React.createElement(
'div',
{
className: tabBarContainer,
},
React.createElement(TabComponent.Header, {
className: [TabComponent.Types.TOP, tabBar].join(' '),
children: Tabs.map((e) =>
Object.assign({}, { id: e.id, label: e.label })
).map((e) =>
React.createElement(TabComponent.Item, {
id: e.id,
onClick: () => this.setTab(e.id),
onItemSelect: () => this.setTab(e.id),
children: e.label,
className: tabBarItem,
selectedItem: this.state.selected,
})
),
})
),
React.createElement('div', {
style: {
marginTop: '10px',
},
children: React.createElement(ScrollWrapper, {
style: {
maxHeight: "333px",
},
children: Tabs.find(
(e) => e.id == this.state.selected
).element(this.props.guild, this.props.onClose),
}),
}),
],
});
}
setTab(e) {
this.setState({ selected: e });
}
}
return class ViewGuildRelationships extends Plugin {
constructor() {
super();
}
get css() {
return `.modal {
width: 630px;
}`;
}
onStart() {
PluginUtilities.addStyle(config.info.name, this.css);
const GuildContextMenu = WebpackModules.getModule(
(m) => m.default.displayName === 'GuildContextMenu'
);
if (GuildContextMenu)
Patcher.after(
GuildContextMenu,
'default',
(_, [props], ret) => {
ret.props.children.unshift(
React.createElement(
MenuGroup,
{},
React.createElement(MenuItem, {
id: 'view-guild-relationships',
action: () => {
DiscordModules.ModalStack.push(Menu, {
guild: props.guild,
});
},
label: 'View GuildRelationships',
})
)
);
}
);
}
onStop() {
PluginUtilities.removeStyle(config.info.name);
Patcher.unpatchAll();
}
};
};
return plugin(Plugin, Api);
})(global.ZeresPluginLibrary.buildPlugin(config));
})();
================================================
FILE: VoiceChatNotifications.plugin.js
================================================
/**
* @name VoiceChatNotifications
* @invite yNqzuJa
* @authorLink https://github.com/Metalloriff
* @donate https://www.paypal.me/israelboone
* @website https://metalloriff.github.io/toms-discord-stuff/
* @source https://github.com/Metalloriff/BetterDiscordPlugins/blob/master/VoiceChatNotifications.plugin.js
*/
module.exports = (() =>
{
const config =
{
info:
{
name: "VoiceChatNotifications",
authors:
[
{
name: "Metalloriff",
discord_id: "264163473179672576",
github_username: "metalloriff",
twitter_username: "Metalloriff"
}
],
version: "3.0.1",
description: "Displays notifications when users join/leave, mute/unmute, deafen/undeafen, or are moved in the voice channel you're in.",
github: "https://github.com/Metalloriff/BetterDiscordPlugins/blob/master/VoiceChatNotifications.plugin.js",
github_raw: "https://raw.githubusercontent.com/Metalloriff/BetterDiscordPlugins/master/VoiceChatNotifications.plugin.js"
},
defaultConfig:
[
{
type: "category",
id: "log",
name: "notification settings",
collapsible: true,
shown: true,
settings:
[
{
type: "switch",
id: "connections",
name: "Show join/leave notifications",
value: true
},
{
type: "switch",
id: "selfMute",
name: "Show mute/unmute notifications",
value: true
},
{
type: "switch",
id: "selfDeafen",
name: "Show deafen/undeafen notifications",
value: true
},
{
type: "switch",
id: "moved",
name: "Show user channel move notifications",
value: true
},
{
type: "switch",
id: "mute",
name: "Show server mute/unmute notifications",
value: true
},
{
type: "switch",
id: "deafen",
name: "Show server deafen/undeafen notifications",
value: true
},
{
type: "switch",
id: "selfVideo",
name: "Show user video notifications",
value: true
},
{
type: "switch",
id: "selfStream",
name: "Show user stream notifications",
value: true
},
{
type: "switch",
id: "supressInDnd",
name: "Disable notifications while in do not disturb",
value: true
}
]
}
],
changelog:
[
{
type: "fixed",
title: "3.0.1 bug fix",
items:
[
"Fixed the plugin"
]
},
{
type: "fixed",
title: "3.0 rewrite",
items:
[
"This plugin has been rewritten, if you experience any new bugs, please contact me.",
"Please note that all settings have been reset and you will have to reconfigure them."
]
},
{
type: "added",
title: "new features",
items:
[
"Added user video notifications.",
"Added user stream notifications."
]
}
]
};
return !global.ZeresPluginLibrary ? class
{
constructor() { this._config = config; }
getName = () => config.info.name;
getAuthor = () => config.info.description;
getVersion = () => 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 (err, res, body) =>
{
if (err) 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 { WebpackModules, DiscordModules } = Api;
const { UserStore, ChannelStore, SelectedChannelStore, UserStatusStore } = DiscordModules;
const { getVoiceStates } = WebpackModules.getByProps("getVoiceStates");
return class VoiceChatNotifications extends Plugin
{
constructor()
{
super();
}
async showChangelog(footer)
{
try { footer = (await WebpackModules.getByProps("getUser", "acceptAgreements").getUser("264163473179672576")).tag + " | https://discord.gg/yNqzuJa"; }
finally { super.showChangelog(footer); }
}
getSettingsPanel = () => this.buildSettingsPanel().getElement();
onStart()
{
this.loop = setInterval(() => this.main(), 500);
}
main()
{
const vcid = SelectedChannelStore.getVoiceChannelId();
const vc = vcid == null ? null : ChannelStore.getChannel(vcid);
if (vc == null)
return;
const me = UserStore.getCurrentUser();
const states = getVoiceStates(vc.guild_id);
if (UserStatusStore.getStatus(me.id) != "dnd" || !this.settings.log.supressInDnd)
{
for (let state of Object.values(states))
{
const user = UserStore.getUser(state.userId);
const channel = ChannelStore.getChannel(state.channelId);
if (state.userId == me.id || this.states == null || user == null || channel == null)
continue;
const o = { silent: true, icon: user.getAvatarURL() };
if (this.states[state.userId] == null)
{
if (this.settings.log.connections)
{
new Notification(`${user.username} joined ${channel.name == "" ? "the call" : channel.name}`, o);
}
}
else
{
const lastState = this.states[state.userId];
const diff = Object.keys(state).filter(k => state[k] != lastState[k]);
if (this.settings.log.moves && lastState.channelId != state.channelId)
{
new Notification(`${user.username} moved to ${channel.name}`, o);
continue;
}
for (let d of diff)
{
const v = state[d];
let m = null;
switch (d)
{
case "selfMute":
m = `${user.username} ${v ? "muted" : "unmuted"} themselves`;
break;
case "selfDeafen":
m = `${user.username} ${v ? "deafened" : "undeafened"} themselves`;
break;
case "mute":
m = `${user.username} was ${v ? "muted" : "unmuted"}`;
break;
case "deafen":
m = `${user.username} was ${v ? "deafened" : "undeafened"}`;
break;
case "selfVideo":
m = `${user.username} ${v ? "enabled" : "disabled"} their video`;
break;
case "selfStream":
m = `${user.username} ${v ? "started a" : "stopped their"} stream`;
break;
}
if (m != null && this.settings.log[d] == true)
{
new Notification(m, o);
}
}
}
}
if (this.states != null)
{
for (let state of Object.values(this.states))
{
const user = UserStore.getUser(state.userId);
const channel = ChannelStore.getChannel(state.channelId);
if (state.userId == me.id || this.states == null || user == null || channel == null)
continue;
const o = { silent: true, icon: user.getAvatarURL() };
if (states[state.userId] == null && this.settings.log.connections)
{
new Notification(`${user.username} disconnected from ${channel.name == "" ? "the call" : channel.name}`, o);
}
}
}
}
this.states = states;
}
onStop()
{
clearInterval(this.loop);
}
}
};
return plugin(Plugin, Api);
})(global.ZeresPluginLibrary.buildPlugin(config));
})();
================================================
FILE: VoiceChatPanel.plugin.js
================================================
/**
* @name VoiceChatPanel
* @invite yNqzuJa
* @authorLink https://github.com/Metalloriff
* @donate https://www.paypal.me/israelboone
* @website https://metalloriff.github.io/toms-discord-stuff/
* @source https://github.com/Metalloriff/BetterDiscordPlugins/blob/master/VoiceChatPanel.plugin.js
*/
module.exports = (() =>
{
const config =
{
info:
{
name: "VoiceChatPanel",
authors:
[
{
name: "Metalloriff",
discord_id: "264163473179672576",
github_username: "metalloriff",
twitter_username: "Metalloriff"
}
],
version: "0.0.1",
description: "Adds user voice db beside their name, and adds a history graph for user volumes.",
github: "https://github.com/Metalloriff/BetterDiscordPlugins/blob/master/VoiceChatPanel.plugin.js",
github_raw: "https://raw.githubusercontent.com/Metalloriff/BetterDiscordPlugins/master/VoiceChatPanel.plugin.js"
},
defaultConfig:
[
{
id: "hideSilentUsers",
type: "switch",
name: "Hide Silent Users",
value: true
}
]
};
return !global.ZeresPluginLibrary ? class
{
constructor() { this._config = config; }
getName = () => config.info.name;
getAuthor = () => config.info.description;
getVersion = () => 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 (err, res, body) =>
{
if (err) 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 { WebpackModules, Patcher, DiscordModules, PluginUtilities } = Api;
const { React, UserStore, ChannelStore, SelectedChannelStore } = DiscordModules;
const MediaEngine = WebpackModules.getByProps("getMediaEngine").getMediaEngine();
const { TimelineDataSeries, TimelineGraphView } = WebpackModules.getByProps("TimelineDataSeries");
const ModalStack = WebpackModules.getByProps("openModal");
const VoiceUser = WebpackModules.getByDisplayName("VoiceUser");
const { default: ScrollWrapper } = WebpackModules.getByProps("ScrollerAuto");
const { ModalRoot } = WebpackModules.getByProps("ModalRoot");
const RTCDebugItem = WebpackModules.getByDisplayName("RTCDebugItem");
const PanelButton = WebpackModules.getByDisplayName("PanelButton");
const SwitchItem = WebpackModules.getByDisplayName("SwitchItem");
const icon = e => React.createElement((WebpackModules.find(m => m.id != null && typeof m.keys == "function" && m.keys().includes("./Activity"))(e) || {}).default);
const db = l => l.toLocaleString({}, { minimumFractionDigits: 1, maximumFractionDigits: 1 });
const getVoiceStates = () => WebpackModules.getByProps("getVoiceStatesForChannel").getVoiceStatesForChannel(ChannelStore.getChannel(SelectedChannelStore.getVoiceChannelId()));
let components = [];
let timeline = {};
class Graph extends React.Component
{
constructor()
{
super();
this.state = { dataSeries: new TimelineDataSeries() };
}
componentDidMount()
{
this.interval = setInterval(() => {
if (timeline[this.props.user.id] != null)
{
this.setState({ dataSeries: timeline[this.props.user.id] });
}
}, 500);
}
componentWillUnmount()
{
clearInterval(this.interval);
}
handleRenderGraph(e)
{
if (e != null)
{
this.state.graphView = new TimelineGraphView(e);
this.state.graphView.backgroundColor = "black";
this.state.graphView.textColor = "white";
this.state.graphView.timeOptions = { timeStyle: "short" };
this.state.graphView.addDataSeries(this.state.dataSeries);
this.state.graphView.updateEndDate();
}
}
render()
{
return React.createElement(
"canvas",
{
key: "canvas",
height: 100,
style: { width: "100%" },
ref: e => this.handleRenderGraph(e)
}
);
}
}
class Panel extends React.Component
{
constructor()
{
super();
this.state = BdApi.getPlugin("VoiceChatPanel").settings
}
render()
{
return React.createElement(
"div",
{
children:
[
React.createElement(
ScrollWrapper,
{
style: { maxHeight: 600 },
children:
[
React.createElement(
"div",
{
style: { margin: 10 }
},
React.createElement(
SwitchItem,
{
children: "Hide Silent Users",
note: "",
onChange: e =>
{
const plugin = BdApi.getPlugin("VoiceChatPanel");
plugin.settings.hideSilentUsers = !plugin.settings.hideSilentUsers;
plugin.saveSettings();
this.setState(plugin.settings);
},
value: this.state.hideSilentUsers
}
)
),
...this.props.users.map(user =>
React.createElement(
"div",
{
style: { margin: "5%" },
children:
[
React.createElement(
RTCDebugItem,
{
children: user.username,
renderGraph: () =>
React.createElement(
Graph,
{ user }
),
valueRendered: "timeline"
},
)
]
}
))
]
}
)
]
}
);
}
}
class UserVolumeLabel extends React.Component
{
constructor()
{
super();
this.state = { volume: 0 };
}
componentDidMount()
{
components.push(this);
}
componentWillUnmount()
{
components.splice(components.indexOf(this));
}
render()
{
return this.props.serverMute || this.props.mute
? React.createElement("span")
: React.createElement(
"span",
{
style:
{
fontSize: "14px",
lineHeight: "18px",
fontWeight: 500,
color: this.state.volume > 0.5 ? "red" : "white",
whiteSpace: "nowrap",
},
children: db(this.state.volume)
}
);
}
}
return class VoiceChatPanel extends Plugin
{
constructor()
{
super();
}
async showChangelog(footer)
{
try { footer = (await WebpackModules.getByProps("getUser", "acceptAgreements").getUser("264163473179672576")).tag + " | https://discord.gg/yNqzuJa"; }
finally { super.showChangelog(footer); }
}
getSettingsPanel = () => this.buildSettingsPanel().getElement();
onStart()
{
this.localUserId = UserStore.getCurrentUser().id;
PluginUtilities.addStyle(this.getName(), `
.draggable-1KoBzC { height: auto }
.vcp-button
{
position: absolute;
top: 7px;
left: 135px;
}
`);
Patcher.instead(VoiceUser.prototype, "render", ({ props }, _, e) =>
{
if (!props.speaking && this.settings.hideSilentUsers)
{
return React.createElement("div");
}
const re = e(props);
if (props.user.id != this.localUserId)
{
re.props.children.props.children.splice(3, 0, React.createElement(UserVolumeLabel, props));
}
return re;
});
Patcher.after(WebpackModules.getByDisplayName("RTCConnectionStatus").prototype, "render", ({ props }, _, re) =>
{
re.props.children.push(
React.createElement(
"div",
{ className: "vcp-button" },
React.createElement(
PanelButton,
{
icon: () => icon("./TrendingUp.tsx"),
onClick: () =>
ModalStack.openModal(
props =>
React.createElement(
ModalRoot,
{
...props
},
React.createElement(
Panel,
{
...props,
users: getVoiceStates().filter(user => user.userId != this.localUserId).map(s => UserStore.getUser(s.userId))
}
)
)
),
tooltipText: "Open Audio Graph"
}
)
)
);
});
this.interval = setInterval(() =>
{
let i = 0;
MediaEngine.eachConnection(e =>
{
if (i == 0)
this.tick(e);
i++;
});
}, 500);
}
tick(e)
{
e.getStats().then(stats =>
{
const volumeStates = {};
for (let uid in stats.rtp.inbound)
if (uid != this.localUserId)
{
volumeStates[uid] = stats.rtp.inbound[uid][0].audioLevel;
if (timeline[uid] == null)
timeline[uid] = new TimelineDataSeries();
timeline[uid].addPoint(Date.now(), volumeStates[uid]);
}
for (let component of components)
if (volumeStates[component.props.user.id] != null)
component.setState({ volume: volumeStates[component.props.user.id] });
});
}
onStop()
{
clearInterval(this.interval);
Patcher.unpatchAll();
PluginUtilities.removeStyle(this.getName());
}
}
};
return plugin(Plugin, Api);
})(global.ZeresPluginLibrary.buildPlugin(config));
})();