/**
* @name YABDP4Nitro
* @author Riolubruh
* @version 5.3.3
* @invite EFmGEWAUns
* @source https://github.com/riolubruh/YABDP4Nitro
* @updateUrl https://raw.githubusercontent.com/riolubruh/YABDP4Nitro/main/YABDP4Nitro.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": "YABDP4Nitro",
"authors": [{
"name": "Riolubruh",
"discord_id": "359063827091816448",
"github_username": "riolubruh"
}],
"version": "5.3.3",
"description": "Unlock all screensharing modes, and use cross-server & GIF emotes!",
"github": "https://github.com/riolubruh/YABDP4Nitro",
"github_raw": "https://raw.githubusercontent.com/riolubruh/YABDP4Nitro/main/YABDP4Nitro.plugin.js"
},
changelog: [
{
title: "5.3.3 Emoji Bypass Hotfix",
items: [
"Fixed an issue where emojis did not work as a result of Discord changing the emoji object format (removed emoji.url)."
]
}
],
"main": "YABDP4Nitro.plugin.js"
};
return !global.ZeresPluginLibrary ? class {
constructor() {
this._config = config;
}
getName() {
return config.info.name;
}
getAuthor() {
return config.info.authors.map(a => a.name).join(", ");
}
getDescription() {
return config.info.description;
}
getVersion() {
return config.info.version;
}
load() {
BdApi.UI.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://raw.githubusercontent.com/rauenzi/BDPluginLibrary/master/release/0PluginLibrary.plugin.js");
await new Promise(r => require("fs").writeFile(require("path").join(BdApi.Plugins.folder, "0PluginLibrary.plugin.js"), body, r));
});
}
});
}
start() { }
stop() { }
} : (([Plugin, Api]) => {
const plugin = (Plugin, Api) => {
const {
Patcher,
DiscordModules,
Settings,
Toasts,
Utilities,
WebpackModules,
DiscordClassModules,
PluginUpdater,
Logger
} = Api;
return class YABDP4Nitro extends Plugin {
defaultSettings = {
"emojiSize": 64,
"screenSharing": true,
"emojiBypass": true,
"ghostMode": true,
"emojiBypassForValidEmoji": true,
"PNGemote": true,
"uploadEmotes": true,
"uploadStickers": false,
"CustomFPSEnabled": false,
"CustomFPS": 75,
"ResolutionEnabled": false,
"CustomResolution": 0,
"CustomBitrateEnabled": false,
"minBitrate": -1,
"maxBitrate": -1,
"targetBitrate": -1,
"voiceBitrate": 128,
"ResolutionSwapper": false,
"stickerBypass": false,
"profileV2": false,
"forceStickersUnlocked": false,
"changePremiumType": false,
"videoCodec": 0,
"clientThemes": true,
"lastGradientSettingStore": -1,
"fakeProfileThemes": true,
"removeProfileUpsell": false,
"removeScreenshareUpsell": true,
"fakeProfileBanners": true,
"fakeAvatarDecorations": true,
"unlockAppIcons": false,
"profileEffects": true,
"killProfileEffects": false,
"avatarDecorations": {},
"customPFPs": true,
"experiments": false,
"userPfpIntegration": true,
"userBgIntegration": true
};
settings = Utilities.loadSettings(this.getName(), this.defaultSettings);
getSettingsPanel() {
return Settings.SettingPanel.build(_ => this.saveAndUpdate(), ...[
new Settings.SettingGroup("Screen Share Features").append(...[
new Settings.Switch("High Quality Screensharing", "1080p/Source @ 60fps screensharing. Enable if you want to use any Screen Share related options.", this.settings.screenSharing, value => this.settings.screenSharing = value),
new Settings.Switch("Custom Screenshare Resolution", "Choose your own screen share resolution!", this.settings.ResolutionEnabled, value => this.settings.ResolutionEnabled = value),
new Settings.Textbox("Resolution", "The custom resolution you want (in pixels)", this.settings.CustomResolution,
value => {
value = parseInt(value, 10);
this.settings.CustomResolution = value;
}),
new Settings.Switch("Custom Screenshare FPS", "Choose your own screen share FPS!", this.settings.CustomFPSEnabled, value => this.settings.CustomFPSEnabled = value),
new Settings.Textbox("FPS", "", this.settings.CustomFPS,
value => {
value = parseInt(value);
this.settings.CustomFPS = value;
}),
new Settings.Switch("Stream Settings Quick Swapper", "Adds a button that will let you switch your resolution quickly!", this.settings.ResolutionSwapper, value => this.settings.ResolutionSwapper = value),
new Settings.Switch("Custom Bitrate", "Choose the bitrate for your streams!", this.settings.CustomBitrateEnabled, value => this.settings.CustomBitrateEnabled = value),
new Settings.Textbox("Minimum Bitrate", "The minimum bitrate (in kbps).", this.settings.minBitrate,
value => {
value = parseFloat(value);
this.settings.minBitrate = value;
}),
new Settings.Textbox("Maximum Bitrate", "The maximum bitrate (in kbps).", this.settings.maxBitrate,
value => {
value = parseFloat(value);
this.settings.maxBitrate = value;
}),
new Settings.Textbox("Target Bitrate", "The target bitrate (in kbps).", this.settings.targetBitrate,
value => {
value = parseFloat(value);
this.settings.targetBitrate = value;
}),
new Settings.Textbox("Voice Audio Bitrate", "Allows you to change the voice bitrate to whatever you want. Does not allow you to go over the voice channel's set bitrate but it does allow you to go much lower. (bitrate in kbps).", this.settings.voiceBitrate,
value => {
value = parseFloat(value);
this.settings.voiceBitrate = value;
}),
new Settings.Dropdown("Preferred Video Codec", "Changes the screen share video codec to the one set.", this.settings.videoCodec, [
{label: "Default/Disabled", value: 0},
{label: "H.265", value: 1},
{label: "H.264", value: 2},
{label: "VP8", value: 3},
{label: "VP9", value: 4}], value => this.settings.videoCodec = value, {searchable: true}
)
]),
new Settings.SettingGroup("Emojis").append(
new Settings.Switch("Nitro Emotes Bypass", "Enable or disable using the emoji bypass.", this.settings.emojiBypass, value => this.settings.emojiBypass = value),
new Settings.Dropdown("Size", "The size of the emoji in pixels.", this.settings.emojiSize, [
{label: "32px (Default small/inline)", value: 32},
{label: "48px (Recommended, default large)", value: 48},
{label: "16px", value: 16},
{label: "24px", value: 24},
{label: "40px", value: 40},
{label: "56px", value: 56},
{label: "64px", value: 64},
{label: "80px", value: 80},
{label: "96px", value: 96},
{label: "128px (Max emoji size)", value: 128},
{label: "256px (Max GIF emoji size)", value: 256}
],
value => {
if (isNaN(value)) {
value = 48;
}
this.settings.emojiSize = value
}, {searchable: true}
),
new Settings.Switch("Ghost Mode", "Abuses ghost message bug to hide the emoji url.", this.settings.ghostMode, value => this.settings.ghostMode = value),
new Settings.Switch("Don't Use Emote Bypass if Emote is Unlocked", "Disable to use emoji bypass even if bypass is not required for that emoji.", this.settings.emojiBypassForValidEmoji, value => this.settings.emojiBypassForValidEmoji = value),
new Settings.Switch("Use PNG instead of WEBP", "Use the PNG version of emoji for higher quality!", this.settings.PNGemote, value => this.settings.PNGemote = value),
new Settings.Switch("Upload Emotes as Images", "Upload emotes as image(s) after message is sent. (Overrides linking emotes)", this.settings.uploadEmotes, value => this.settings.uploadEmotes = value),
new Settings.Switch("Sticker Bypass", "Enable or disable using the sticker bypass. I recommend using An00nymushun's DiscordFreeStickers over this. Animated APNG/WEBP/Lottie Stickers will not animate.", this.settings.stickerBypass, value => this.settings.stickerBypass = value),
new Settings.Switch("Upload Stickers", "Upload stickers in the same way as emotes.", this.settings.uploadStickers, value => this.settings.uploadStickers = value),
new Settings.Switch("Force Stickers Unlocked", "Enable to cause Stickers to be unlocked.", this.settings.forceStickersUnlocked, value => this.settings.forceStickersUnlocked = value)
),
new Settings.SettingGroup("Profile").append(
new Settings.Switch("Profile Accents", "When enabled, you will see (almost) all users with the new Nitro-exclusive look for profiles (the sexier look). When disabled, the default behavior is used. Does not allow you to update your profile accent.", this.settings.profileV2, value => this.settings.profileV2 = value),
new Settings.Switch("Fake Profile Themes", "Uses invisible 3y3 encoding to allow profile theming by hiding the colors in your bio.", this.settings.fakeProfileThemes, value => this.settings.fakeProfileThemes = value),
new Settings.Switch("Fake Profile Banners", "Uses invisible 3y3 encoding to allow setting profile banners by hiding the image URL in your bio. Only supports Imgur URLs for security reasons.", this.settings.fakeProfileBanners, value => this.settings.fakeProfileBanners = value),
new Settings.Switch("UserBG Integration", "Downloads and parses the UserBG JSON database so that UserBG banners will appear for you.", this.settings.userBgIntegration, value => this.settings.userBgIntegration = value),
new Settings.Switch("Fake Avatar Decorations", "Uses invisible 3y3 encoding to allow setting avatar decorations by hiding information in your bio and/or your custom status.", this.settings.fakeAvatarDecorations, value => this.settings.fakeAvatarDecorations = value),
new Settings.Switch("Fake Profile Effects", "Uses invisible 3y3 encoding to allow setting profile effects by hiding information in your bio.", this.settings.profileEffects, value => this.settings.profileEffects = value),
new Settings.Switch("Kill Profile Effects", "Hate profile effects? Enable this and they'll be gone. All of them. Overrides all profile effects.", this.settings.killProfileEffects, value => this.settings.killProfileEffects = value),
new Settings.Switch("Fake Profile Pictures", "Uses invisible 3y3 encoding to allow setting custom profile pictures by hiding an image URL IN YOUR CUSTOM STATUS. Only supports Imgur URLs for security reasons.", this.settings.customPFPs, value => this.settings.customPFPs = value),
new Settings.Switch("UserPFP Integration", "Imports the UserPFP database so that people who have profile pictures in the UserPFP database will appear with their UserPFP profile picture. There's little reason to disable this.", this.settings.userPfpIntegration, value => this.settings.userPfpIntegration = value)
),
new Settings.SettingGroup("Miscellaneous").append(
new Settings.Switch("Change PremiumType", "This is now optional. Enabling this may help compatibility for certain things or harm it. SimpleDiscordCrypt requires this to be enabled to have the emoji bypass work. Only enable this if you don't have Nitro.", this.settings.changePremiumType, value => this.settings.changePremiumType = value),
new Settings.Switch("Gradient Client Themes", "Allows you to use Nitro-exclusive Client Themes.", this.settings.clientThemes, value => this.settings.clientThemes = value),
new Settings.Switch("Remove Profile Customization Upsell", "Removes the \"Try It Out\" upsell in the profile customization screen and replaces it with the Nitro variant. Note: does not allow you to use Nitro customization on Server Profiles as the API disallows this.", this.settings.removeProfileUpsell, value => this.settings.removeProfileUpsell = value),
new Settings.Switch("Remove Screen Share Nitro Upsell", "Removes the Nitro upsell in the Screen Share quality option menu.", this.settings.removeScreenshareUpsell, value => this.settings.removeScreenshareUpsell = value),
new Settings.Switch("App Icons", "Unlocks app icons. Warning: enabling this will force \"Change Premium Type\" to be enabled. Buggy.", this.settings.unlockAppIcons, value => this.settings.unlockAppIcons = value),
new Settings.Switch("Experiments", "Unlocks experiments. Use at your own risk.", this.settings.experiments, value => this.settings.experiments = value)
)
])
}
saveAndUpdate(){ //Saves and updates settings and runs functions
Utilities.saveSettings(this.getName(), this.settings);
BdApi.Patcher.unpatchAll("YABDP4Nitro");
Patcher.unpatchAll();
if(this.settings.changePremiumType){
try{
if(!(this.originalNitroStatus > 1)){
this.currentUser.premiumType = 1;
setTimeout(() => {
if(this.settings.changePremiumType){
this.currentUser.premiumType = 1;
}
}, 10000);
}
}
catch(err){
Logger.err(this.getName(), "An error occurred changing premium type." + err);
}
}
if(this.settings.CustomFPS == 15) this.settings.CustomFPS = 16;
if(this.settings.CustomFPS == 30) this.settings.CustomFPS = 31;
if(this.settings.CustomFPS == 5) this.settings.CustomFPS = 6;
if(document.getElementById("qualityButton")) document.getElementById("qualityButton").remove();
if(document.getElementById("qualityMenu")) document.getElementById("qualityMenu").remove();
if(document.getElementById("qualityInput")) document.getElementById("qualityInput").remove();
if(this.settings.ResolutionSwapper){
try{
this.buttonCreate(); //Fast Quality Button and Menu
}catch(err){
console.error(err);
}
try{
document.getElementById("qualityInput").addEventListener("input", this.updateQuick);
document.getElementById("qualityInputFPS").addEventListener("input", this.updateQuick);
if(!this.settings.ResolutionSwapper){
if(document.getElementById("qualityButton") != undefined) document.getElementById("qualityButton").style.display = 'none';
if(document.getElementById("qualityMenu") != undefined) document.getElementById("qualityMenu").style.display = 'none';
}
}catch(err){
console.error(err);
}
}
if(this.settings.stickerBypass){
try{
this.stickerSending()
}catch(err){
console.error(err)
}
}
if(this.settings.emojiBypass){
try{
this.emojiBypass();
if(this.emojiMods == undefined) this.emojiMods = WebpackModules.getByProps("isEmojiFilteredOrLocked");
BdApi.Patcher.instead("YABDP4Nitro", this.emojiMods, "isEmojiFilteredOrLocked", () => {
return false
});
BdApi.Patcher.instead("YABDP4Nitro", this.emojiMods, "isEmojiDisabled", () => {
return false
});
BdApi.Patcher.instead("YABDP4Nitro", this.emojiMods, "isEmojiFiltered", () => {
return false
});
BdApi.Patcher.instead("YABDP4Nitro", this.emojiMods, "isEmojiPremiumLocked", () => {
return false
});
BdApi.Patcher.instead("YABDP4Nitro", this.emojiMods, "getEmojiUnavailableReason", () => {
return
});
}catch(err){
console.error(err);
}
}
if(this.settings.profileV2){
try{
BdApi.Patcher.after("YABDP4Nitro", this.userProfileMod, "getUserProfile", (_,args,ret) => {
if(ret == undefined) return;
ret.premiumType = 2;
});
}catch(err){
console.error(err);
}
}
if(this.settings.screenSharing){
try{
this.customVideoSettings(); //Unlock stream buttons, apply custom resolution and fps, and apply stream quality bypasses
}catch(err){
Logger.err(this.getName(), "Error occurred during customVideoSettings() " + err);
}
try{
this.videoQualityModule(); //Custom bitrate, fps, resolution module
}catch(err){
Logger.err(this.getName(), "Error occurred during videoQualityModule() " + err);
}
}
if(this.settings.forceStickersUnlocked){
if(this.stickerSendabilityModule == undefined) this.stickerSendabilityModule = WebpackModules.getByProps("isSendableSticker");
BdApi.Patcher.instead("YABDP4Nitro", this.stickerSendabilityModule, "getStickerSendability", () => {
return 0
});
BdApi.Patcher.instead("YABDP4Nitro", this.stickerSendabilityModule, "isSendableSticker", () => {
return true
});
}
if(this.settings.clientThemes){
try{
this.clientThemes();
}catch(err){
console.warn("[YABDP4Nitro] " + err);
}
}
if(this.settings.fakeProfileThemes){
try{
this.decodeAndApplyProfileColors();
this.encodeProfileColors();
}catch(err){
Logger.err(this.getName(), "Error occurred running fakeProfileThemes bypass. " + err);
}
}
if(this.hasAddedScreenshareUpsellStyle && !this.settings.removeScreenshareUpsell){
try{
BdApi.DOM.removeStyle("YABDP4Nitro")
}catch(err){
Logger.warn(this.getName(), err);
}
}
if(this.settings.removeScreenshareUpsell && !this.hasAddedScreenshareUpsellStyle){
try{
BdApi.DOM.addStyle("YABDP4Nitro",`
[class*="upsellBanner"] {
display: none;
visibility: hidden;
}`);
this.hasAddedScreenshareUpsellStyle = true;
}catch(err){
Logger.err(this.getName(), err);
}
}
if(this.settings.fakeProfileBanners){
try{
this.bannerUrlDecoding();
this.bannerUrlEncoding(this.secondsightifyEncodeOnly);
this.bannerUrlDecodingPreview();
}catch(err){
Logger.err(this.getName(), "What the fuck happened? In fakeProfileBanners: " + err);
}
}
if(this.settings.fakeAvatarDecorations){
try{
this.fakeAvatarDecorations(this);
}catch(err){
Logger.err(this.getName(), "An error occurred during fakeAvatarDecorations() " + err);
}
}
if(this.settings.unlockAppIcons){
this.appIcons();
}
if(this.settings.profileEffects){
try{
this.profileFX(this.secondsightifyEncodeOnly);
}catch(err){
console.error(err);
}
}
if(this.settings.killProfileEffects){
try{
this.killProfileFX();
}catch(err){
Logger.err(this.getName(), "Error occured during killProfileFX() " + err);
}
}
try{
this.honorBadge();
}catch(err){
Logger.err(this.getName(), "An error occurred during honorBadge() " + err);
}
if(this.settings.customPFPs){
try{
this.customProfilePictureDecoding();
this.customProfilePictureEncoding(this.secondsightifyEncodeOnly);
}catch(err){
Logger.err(this.getName(), "An error occurred during customProfilePicture decoding/encoding. " + err);
}
}
if(this.settings.experiments){
try{
this.experiments();
}catch(err){
Logger.err(this.getName(), "Error occurred in experiments() " + err);
}
}
if(this.settings.unlockAppIcons || this.settings.changePremiumType || this.settings.experiments){ //account panel breaking shit workaround
if(this.accountPanelRenderer == undefined) this.accountPanelRenderer = WebpackModules.getAllByProps("default").filter(obj => obj.default.toString().includes("useIsHomeSelected"))[0];
BdApi.Patcher.after("YABDP4Nitro", this.accountPanelRenderer, "default", (_,args,ret) => {
if(this.settings.unlockAppIcons || this.settings.changePremiumType) ret.props.currentUser.premiumType = 1;
if(this.settings.experiments) ret.props.currentUser.flags |= 1;
if(this.settings.ResolutionSwapper && (document.getElementById("qualityButton") == undefined || document.getElementById("qualityInputFPS") == undefined)){
this.buttonCreate();
document.getElementById("qualityInput").addEventListener("input", this.updateQuick);
document.getElementById("qualityInputFPS").addEventListener("input", this.updateQuick);
if(!this.settings.ResolutionSwapper){
if(document.getElementById("qualityButton") != undefined) document.getElementById("qualityButton").style.display = 'none';
if(document.getElementById("qualityMenu") != undefined) document.getElementById("qualityMenu").style.display = 'none';
}
}
});
}
BdApi.Patcher.instead("YABDP4Nitro", this.canUserUseMod, "canUserUse", (_, [feature, user], originalFunction) => {
if(this.settings.emojiBypass && (feature.name == "emojisEverywhere" || feature.name == "animatedEmojis")){
return true;
}
if(this.settings.appIcons && feature.name == 'appIcons'){
return true;
}
if(this.settings.removeProfileUpsell && feature.name == 'profilePremiumFeatures'){
return true;
}
if(this.settings.clientThemes && feature.name == 'clientThemes'){
return true;
}
return originalFunction(feature, user);
});
} //End of saveAndUpdate()
experiments(){
if(this.hasAppliedExperiments) return;
//Code graciously stolen from https://gist.github.com/MeguminSama/2cae24c9e4c335c661fa94e72235d4c4?permalink_comment_id=4952988#gistcomment-4952988
try{
let cache; webpackChunkdiscord_app.push([["wp_isdev_patch"], {}, r => cache=r.c]);
let UserStore = Object.values(cache).find(m => m?.exports?.default?.getUser).exports.default;
let actions = Object.values(UserStore._dispatcher._actionHandlers._dependencyGraph.nodes);
let user = UserStore.getCurrentUser();
actions.find(n => n.name === "ExperimentStore").actionHandler.CONNECTION_OPEN({
type: "CONNECTION_OPEN", user: {flags: user.flags |= 1}, experiments: [],
});
actions.find(n => n.name === "DeveloperExperimentStore").actionHandler.CONNECTION_OPEN();
webpackChunkdiscord_app.pop(); user.flags &= ~1; "done";
this.hasAppliedExperiments = true;
}catch(err){
//console.warn(err);
}
}
clientThemes(){
if(this.clientThemesModule == undefined) this.clientThemesModule = BdApi.Webpack.getModule(BdApi.Webpack.Filters.byProps("isPreview"));
//delete isPreview property so that we can set our own
delete this.clientThemesModule.isPreview;
//this property basically unlocks the client theme buttons
Object.defineProperty(this.clientThemesModule, "isPreview", { //Enabling the nitro theme settings
value: false,
configurable: true,
enumerable: true,
writable: true,
});
if(this.themesModule == undefined) this.themesModule = WebpackModules.getByProps("saveClientTheme");
if(this.gradientSettingModule == undefined) this.gradientSettingModule = WebpackModules.getByProps("resetPreviewClientTheme");
//Patching saveClientTheme function.
BdApi.Patcher.instead("YABDP4Nitro", this.themesModule, "saveClientTheme", (_,args) => {
if(args[0].backgroundGradientPresetId == undefined){
//If this number is -1, that indicates to the plugin that the current theme we're setting to is not a gradient nitro theme.
this.settings.lastGradientSettingStore = -1;
//if user is trying to set the theme to the default dark theme
if(args[0].theme == 'dark'){
//dispatch settings update to change to dark theme
this.dispatcher.dispatch({
type: "SELECTIVELY_SYNCED_USER_SETTINGS_UPDATE",
changes: {
appearance: {
shouldSync: false, //prevent sync to stop discord api from butting in. Since this is not a nitro theme, shouldn't this be set to true? Idk, but I'm not touching it lol.
settings: {
theme: 'dark', //default dark theme
developerMode: true //genuinely have no idea what this does.
}
}
}
})
//get rid of gradient theming.
this.gradientSettingModule.resetPreviewClientTheme();
return;
}
//if user is trying to set the theme to the default light theme
if(args[0].theme == 'light'){
//dispatch settings update event to change to light theme
this.dispatcher.dispatch({
type: "SELECTIVELY_SYNCED_USER_SETTINGS_UPDATE",
changes: {
appearance: {
shouldSync: false, //prevent sync to stop discord api from butting in
settings: {
theme: 'light', //default light theme
developerMode: true
}
}
}
})
}
return;
}else{ //gradient themes
//Store the last gradient setting used in settings
this.settings.lastGradientSettingStore = args[0].backgroundGradientPresetId;
//dispatch settings update event to change to the gradient the user chose
this.dispatcher.dispatch({
type: "SELECTIVELY_SYNCED_USER_SETTINGS_UPDATE",
changes: {
appearance: {
shouldSync: false, //prevent sync to stop discord api from butting in
settings: {
theme: args[0].theme, //gradient themes are based off of either dark or light, args[0].theme stores this information
clientThemeSettings: {
backgroundGradientPresetId: args[0].backgroundGradientPresetId //preset ID for the gradient theme
},
developerMode: true
}
}
}
});
//update background gradient preset to the one that was just chosen.
this.gradientSettingModule.updateBackgroundGradientPreset(this.settings.lastGradientSettingStore);
}
}); //End of saveClientTheme patch.
//If last appearance choice was a nitro client theme
if(this.settings.lastGradientSettingStore != -1){
//This line sets the gradient on plugin save and load.
this.gradientSettingModule.updateBackgroundGradientPreset(this.settings.lastGradientSettingStore);
}
if(this.accountSwitchModule == undefined) this.accountSwitchModule = WebpackModules.getByProps("startSession");
//startSession patch. This function runs upon switching accounts.
BdApi.Patcher.after("YABDP4Nitro", this.accountSwitchModule, "startSession", () => {
//If last appearance choice was a nitro client theme
if(this.settings.lastGradientSettingStore != -1){
//Restore gradient on account switch
this.gradientSettingModule.updateBackgroundGradientPreset(this.settings.lastGradientSettingStore);
}
});
} //End of clientThemes()
customProfilePictureDecoding(){
if(this.getAvatarUrlModule == undefined) this.getAvatarUrlModule = WebpackModules.getByPrototypes("getAvatarURL").prototype;
BdApi.Patcher.instead("YABDP4Nitro", this.getAvatarUrlModule, "getAvatarURL", (user, [userId, size, shouldAnimate], originalFunction) => {
//userpfp closer integration
//if we haven't fetched userPFP database yet and it's enabled
if((!this.fetchedUserPfp || this.userPfps == undefined) && this.settings.userPfpIntegration){
const userPfpJsonUrl = "https://raw.githubusercontent.com/UserPFP/UserPFP/main/source/data.json";
// download userPfp data
BdApi.Net.fetch(userPfpJsonUrl)
// parse as json
.then(res => res.json())
// store res.avatars in this.userPfps
.then(res => this.userPfps = res.avatars);
//set fetchedUserPfp flag to true.
this.fetchedUserPfp = true;
}
//if userPfp database is not undefined, has been fetched, and is enabled
if((this.userPfps != undefined && this.fetchedUserPfp) && this.settings.userPfpIntegration){
//and this user is in the userPfp database,
if(this.userPfps[user.id] != undefined){
//return UserPFP profile picture URL.
return this.userPfps[user.id];
}
}
if(DiscordModules.UserStatusStore.getActivities(user.id).length > 0){
//get user activities
let activities = DiscordModules.UserStatusStore.getActivities(user.id);
//if user does not have a custom status, return original function.
if(activities[0].name != "Custom Status") return originalFunction(userId, size, shouldAnimate);
//if user does have a custom status, assign it to customStatus variable.
let customStatus = activities[0].state;
//checking if anything went wrong
if(customStatus == undefined) return originalFunction(userId, size, shouldAnimate);
//decode any 3y3 text
let revealedText = this.secondsightifyRevealOnly(String(customStatus));
//if there is no 3y3 encoded text, return original function.
if(revealedText == undefined) return originalFunction(userId, size, shouldAnimate);
//This regex matches /P{*} . (Do not fuck with this)
let regex = /P\{[^}]*\}/;
//Check if there are any matches in the custom status.
let matches = revealedText.toString().match(regex);
//if not, return orig function
if(matches == undefined) return originalFunction(userId, size, shouldAnimate);
if(matches == "") return originalFunction(userId, size, shouldAnimate);
//if there is a match, take the first match and remove the starting "P{ and ending "}"
let matchedText = matches[0].replace("P{", "").replace("}", "");
//look for a file extension. If omitted, fallback to .gif .
if(!String(matchedText).endsWith(".gif") && !String(matchedText).endsWith(".png") && !String(matchedText).endsWith(".jpg") && !String(matchedText).endsWith(".jpeg") && !String(matchedText).endsWith(".webp")){
matchedText += ".gif"; //No supported file extension detected. Falling back to a default file extension.
}
//add this user to the list of users who have thee YABDP4Nitro user badge if we haven't added them already.
if(!this.badgeUserIDs.includes(user.id)) this.badgeUserIDs.push(user.id);
//return imgur url
return `https://i.imgur.com/${matchedText}`;
}
//if user does not have any activities active, return original function.
return originalFunction(userId, size, shouldAnimate);
})
}
//Custom PFP profile customization buttons and encoding code.
async customProfilePictureEncoding(secondsightifyEncodeOnly){
//wait for avatar customization section renderer to be loaded
await BdApi.Webpack.waitForModule(BdApi.Webpack.Filters.byStrings("USER_SETTINGS_RESET_AVATAR"));
//store avatar customization section renderer module
if(this.customPFPSettingsRenderMod == undefined) this.customPFPSettingsRenderMod = WebpackModules.getAllByProps("default").filter((obj) => obj.default.toString().includes("USER_SETTINGS_RESET_AVATAR"))[0];
BdApi.Patcher.after("YABDP4Nitro", this.customPFPSettingsRenderMod, "default", (_,[args],ret) => {
//don't need to do anything if this is the "Try out Nitro" flow.
if(args.isTryItOutFlow) return;
ret.props.children.props.children.push(
BdApi.React.createElement("input", {
id: "profilePictureUrlInput",
style: {
width: "30%",
height: "20%",
maxHeight: "50%",
marginTop: "5px",
marginLeft: "5px"
},
placeholder: "Imgur URL"
})
);
//Create and append Copy PFP 3y3 button.
ret.props.children.props.children.push(
BdApi.React.createElement("button", {
children: "Copy PFP 3y3",
className: `${this.buttonClassModule.button} ${this.buttonClassModule.lookFilled} ${this.buttonClassModule.colorBrand} ${this.buttonClassModule.sizeSmall} ${this.buttonClassModule.grow}`,
id: "profilePictureButton",
style: {
marginLeft: "10px",
whiteSpace: "nowrap"
},
onClick: async function(){ //on copy pfp 3y3 button click
//grab text from pfp url input textarea.
let profilePictureUrlInputValue = String(document.getElementById("profilePictureUrlInput").value);
//empty, skip.
if(profilePictureUrlInputValue == "") return;
if(profilePictureUrlInputValue == undefined) return;
//clean up string to encode
let stringToEncode = "" + profilePictureUrlInputValue
//clean up URL
.replace("http://", "") //remove protocol
.replace("https://", "")
.replace("i.imgur.com","imgur.com")
let encodedStr = ""; //initialize encoded string as empty string
stringToEncode = String(stringToEncode); //make doubly sure stringToEncode is a string
//if url seems correct
if(stringToEncode.toLowerCase().startsWith("imgur.com")){
//Check for album or gallery URL
if(stringToEncode.replace("imgur.com/","").startsWith("a/") || stringToEncode.replace("imgur.com/","").startsWith("gallery/")){
//Album URL, what follows is all to get the direct image link, since the album URL is not a direct link to the file.
//Fetch imgur album page
const parser = new DOMParser();
stringToEncode = await BdApi.Net.fetch(("https://" + stringToEncode), {
method: "GET",
mode: "cors"
}).then(res => res.text()
/*This next part is interesting, so a code explanation follows:
* First, we're parsing the HTML of the Imgur Album using the DOM parser we initialized before.
* Then, find the meta tag in the HTML that has the og:image property
* Finally, we take the content of the og:image meta tag. This is the direct URL to the file that we want.
* The need to do this makes it necessary for this function to be async. */
.then(res => parser.parseFromString(res, "text/html").querySelector('[property="og:image"]').content));
stringToEncode = stringToEncode.replace("http://", "") //get rid of protocol
.replace("https://", "")
.replace("i.imgur.com","imgur.com")
.split("?")[0]; //remove any URL parameters since we don't want or need them
}
//add starting "P{" , remove "imgur.com/" , and add ending "}"
stringToEncode = "P{" + stringToEncode.replace("imgur.com/","") + "}"
//finally encode the string, adding a space before it so nothing fucks up
encodedStr = " " + secondsightifyEncodeOnly(stringToEncode);
//let the user know what has happened
Toasts.info("3y3 copied to clipboard!");
//If this is not an Imgur URL, yell at the user.
}else if(stringToEncode.toLowerCase().startsWith("imgur.com") == false){
Toasts.warning("Please use Imgur!");
return
}
//if somehow none of the previous code ran, this is the last protection against an error. If this runs, something has probably gone horribly wrong.
if(encodedStr == "") return;
//Do this stupid shit that Chrome forces you to do to copy text to the clipboard.
const clipboardTextElem = document.createElement("textarea"); //create a textarea
clipboardTextElem.style.position = 'fixed'; //this is so that the rest of the document doesn't try to format itself to fit a textarea in it
clipboardTextElem.value = encodedStr; //add the encoded string to the textarea
document.body.appendChild(clipboardTextElem); //add the textarea to the document
clipboardTextElem.select(); //focus the textarea?
clipboardTextElem.setSelectionRange(0, 99999); //select all of the text in the textarea
document.execCommand('copy'); //finally send the copy command
document.body.removeChild(clipboardTextElem); //get rid of the evidence
} //end copy pfp 3y3 click event
}) //end of react createElement
); //end of element push
}); //end of patch
} //End of customProfilePictureEncoding()
//Apply custom badges.
honorBadge(){
// Use CSS to select badge elements via aria-label and change them to the correct icon.
BdApi.DOM.addStyle("YABDP4NitroBadges", `
a[aria-label="A fellow YABDP4Nitro user!"] img {
content: url("https://raw.githubusercontent.com/riolubruh/riolubruh.github.io/main/badge.png") !important;
}
a[aria-label="YABDP4Nitro Creator!"] img, a[aria-label="YABDP4Nitro Contributor!"] img {
content: url("https://i.imgur.com/bYGGXnq.gif") !important;
}`);
//User profile badge patches
BdApi.Patcher.after("YABDP4Nitro", this.userProfileMod, "getUserProfile", (_,args,ret) => {
//bad data checks
if(ret == undefined) return;
if(ret.userId == undefined) return;
if(ret.badges == undefined) return;
const badgesList = []; //list of the currently processed user's badge IDs
for(let i = 0; i < ret.badges.length; i++){ //for each of currently processed user's badges
badgesList.push(ret.badges[i].id); //add each of this user's badge IDs to badgesList
}
//if list of users that should have yabdp_user badge includes current user, and they don't already have the badge applied,
if(this.badgeUserIDs.includes(ret.userId) && !badgesList.includes("yabdp_user")){
//add the yabdp user badge to the user's list of badges.
ret.badges.push({
id: "yabdp_user",
icon: "2ba85e8026a8614b640c2837bcdfe21b", //Nitro icon, gets replaced later.
description: "A fellow YABDP4Nitro user!",
link: "https://github.com/riolubruh/YABDP4Nitro" //this link opens upon clicking the badge.
});
}
//if this user is Riolubruh, and they don't already have the badge applied,
if(ret.userId == "359063827091816448" && !badgesList.includes("yabdp_creator")){
//add the yabdp creator badge to riolubruh's list of badges.
ret.badges.push({
id: "yabdp_creator",
icon: "2ba85e8026a8614b640c2837bcdfe21b", //Nitro icon, gets replaced later.
description: "YABDP4Nitro Creator!",
link: "https://github.com/riolubruh/YABDP4Nitro" //this link opens upon clicking the badge.
});
}
//List of Discord User IDs of people who have made contributions to the plugin
//Special thanks to the following gamers:
// Weblure, Kozhura_ubezhishe_player_fly , and Moeefa !
const specialThanks = ["122072911455453184", "760274365853335563" , "482224256730791967"];
//if the currently processed user is included in specialThanks, and they don't already have the badge applied,
if(specialThanks.includes(ret.userId) && !badgesList.includes("yabdp_contributor")){
//add the yabdp contributor badge to the contributor's list of badges
ret.badges.push({
id: "yabdp_contributor",
icon: "2ba85e8026a8614b640c2837bcdfe21b", //Nitro icon, gets replaced later.
description: "YABDP4Nitro Contributor!",
link: "https://github.com/riolubruh/YABDP4Nitro#contributors" //this link opens upon clicking the badge.
});
}
}); //End of user profile badge patches
} //End of honorBadge()
secondsightifyRevealOnly(t) {
if([...t].some(x => (0xe0000 < x.codePointAt(0) && x.codePointAt(0) < 0xe007f))) {
// 3y3 text detected. Revealing...
return (t => ([...t].map(x => (0xe0000 < x.codePointAt(0) && x.codePointAt(0) < 0xe007f) ? String.fromCodePoint(x.codePointAt(0) - 0xe0000) : x).join("")))(t)
}else {
// no encoded text found, returning
return
}
}
secondsightifyEncodeOnly(t) {
if([...t].some(x => (0xe0000 < x.codePointAt(0) && x.codePointAt(0) < 0xe007f))) {
// 3y3 text detected. returning...
return
}else {
//3y3 text detected. revealing...
return (t => [...t].map(x => (0x00 < x.codePointAt(0) && x.codePointAt(0) < 0x7f) ? String.fromCodePoint(x.codePointAt(0)+0xe0000) : x).join(""))(t)
}
}
//Everything related to Fake Profile Effects.
async profileFX(secondsightifyEncodeOnly){
if(this.settings.killProfileEffects) return; //profileFX is mutually exclusive with killProfileEffects (obviously)
//wait for profile effects module
await BdApi.Webpack.waitForModule(BdApi.Webpack.Filters.byProps("profileEffects", "tryItOutId"));
//try to get profile effects data
if(this.profileEffects == undefined) this.profileEffects = WebpackModules.getByProps("profileEffects", "tryItOutId").profileEffects;
//if profile effects data hasn't been fetched by the client yet
if(this.profileEffects == undefined){
//make the client fetch profile effects
await WebpackModules.getByProps("fetchUserProfileEffects").fetchUserProfileEffects().then(() => {
//then wait for the effects to be fetched and store them
this.profileEffects = WebpackModules.getByProps("profileEffects", "tryItOutId").profileEffects;
});
} else if(this.profileEffects.length == 0){
await WebpackModules.getByProps("fetchUserProfileEffects").fetchUserProfileEffects().then(() => {
this.profileEffects = WebpackModules.getByProps("profileEffects", "tryItOutId").profileEffects;
});
}
let profileEffectIdList = new Array();
for(let i = 0; i < this.profileEffects.length; i++){
profileEffectIdList.push(this.profileEffects[i].id);
}
BdApi.Patcher.after("YABDP4Nitro", this.userProfileMod, "getUserProfile", (_,args,ret) => {
//error prevention
if(ret == undefined) return;
if(ret.bio == undefined) return;
//reveal 3y3 encoded text. this string will also include the rest of the bio
let revealedText = this.secondsightifyRevealOnly(ret.bio);
if(revealedText == undefined) return;
//if profile effect 3y3 is detected
if(revealedText.includes("/fx")){
let position = revealedText.indexOf("/fx");
if(position == undefined) return;
//find the 2 characters after the /fx and parse int
let effectIndex = parseInt(revealedText.slice(position+3, position+5));
//ignore invalid data
if(isNaN(effectIndex)) return;
//ignore if the profile effect id does not point to an actual profile effect
if(profileEffectIdList[effectIndex] == undefined) return;
//set the profile effect
ret.profileEffectId = profileEffectIdList[effectIndex];
//if for some reason we dont know what this user's ID is, stop here
if(args[0] == undefined) return;
//otherwise add them to the list of users who show up with the YABDP4Nitro user badge
if(!this.badgeUserIDs.includes(args[0])) this.badgeUserIDs.push(args[0]);
}
}); //end of getUserProfile patch.
//override 2023-08_profile_effects experiment to forcibly enable profile effects.
if(this.experimentsModule == undefined) this.experimentsModule = WebpackModules.getByProps("getExperimentOverrides");
if(this.experimentsModule.getExperimentOverrides()['2023-08_profile_effects'] == undefined){
this.dispatcher.dispatch({
type: "EXPERIMENT_OVERRIDE_BUCKET",
experimentId: "2023-08_profile_effects",
experimentBucket: 1
});
}
//wait for profile effect section renderer to be loaded.
await BdApi.Webpack.waitForModule(BdApi.Webpack.Filters.byStrings("openProfileEffectModal"));
//fetch the module now that it's loaded
if(this.profileEffectSectionRenderer == undefined) this.profileEffectSectionRenderer = WebpackModules.getAllByProps("default").filter((obj) => obj.default.toString().includes("openProfileEffectModal"))[0];
//patch profile effect section renderer function to run the following code after the function runs
BdApi.Patcher.after("YABDP4Nitro", this.profileEffectSectionRenderer, "default", (_, [args], ret) => {
//if this is the tryItOut flow, don't do anything.
if(args.isTryItOutFlow) return;
let profileEffectChildren = [];
//for each profile effect
for(let i = 0; i < this.profileEffects.length; i++){
//get preview image url
let previewURL = this.profileEffects[i].config.thumbnailPreviewSrc;
let title = this.profileEffects[i].config.title;
//encode 3y3
let encodedText = secondsightifyEncodeOnly("/fx" + i); //fx0, fx1, etc.
//javascript that runs onclick for each profile effect button
let copyDecoration3y3 = function(){
const clipboardTextElem = document.createElement("textarea");
clipboardTextElem.style.position = "fixed";
clipboardTextElem.value = ` ${encodedText}`;
document.body.appendChild(clipboardTextElem);
clipboardTextElem.select();
clipboardTextElem.setSelectionRange(0, 99999);
document.execCommand("copy"); ZLibrary.Toasts.info("3y3 copied to clipboard!");
document.body.removeChild(clipboardTextElem);
}
profileEffectChildren.push(
BdApi.React.createElement("img", {
className: "riolubruhsSecretStuff",
onClick: copyDecoration3y3,
src: previewURL,
title,
style: {
width: "22.5%",
cursor: "pointer",
marginBottom: "0.5em",
marginLeft: "0.5em",
backgroundColor: "var(--background-tertiary)"
}
})
);
//add newline every 4th profile effect
if((i+1) % 4 == 0){
profileEffectChildren.push(
BdApi.React.createElement("br")
);
}
}
//Profile Effects Modal
function EffectsModal() {
const elem = BdApi.React.createElement("div", {
style: {
width: "100%",
display: "block",
color: "white",
whiteSpace: "nowrap",
overflow: "visible",
marginTop: ".5em"
},
children: profileEffectChildren
});
return elem;
}
//Append Change Effect button
ret.props.children.props.children.push(
//self explanatory create react element
BdApi.React.createElement("button", {
children: "Change Effect [YABDP4Nitro]",
className: `${this.buttonClassModule.button} ${this.buttonClassModule.lookFilled} ${this.buttonClassModule.colorBrand} ${this.buttonClassModule.sizeSmall} ${this.buttonClassModule.grow}`,
size: "sizeSmall__71a98",
id: "changeProfileEffectButton",
style: {
width: "100px",
height: "32px",
color: "white",
marginLeft: "10px"
},
onClick: () => {
BdApi.showConfirmationModal("Change Profile Effect (YABDP4Nitro)", BdApi.React.createElement(EffectsModal));
}
})
);
}); //end patch of profile effect section renderer function
} //End of profileFX()
killProfileFX(){ //self explanatory
BdApi.Patcher.after("YABDP4Nitro", this.userProfileMod, "getUserProfile", (_,args,ret) => {
if(ret == undefined) return;
if(ret.profileEffectID == undefined) return;
//self explanatory
ret.profileEffectID = undefined;
});
}
//Everything related to fake avatar decorations.
async fakeAvatarDecorations(self){
//remove old format
if(Array.isArray(self.settings.avatarDecorations)){
self.settings.avatarDecorations = new Object();
Utilities.saveSettings(self.getName(), self.settings);
}
//keep track of profiles downloaded
BdApi.Patcher.after("YABDP4Nitro", self.userProfileMod, "getUserProfile", (_,args,ret) => {
if(ret == undefined) return;
if(ret.userId == undefined) return;
if(self.downloadedUserProfiles.includes(args[0])) return;
self.downloadedUserProfiles.push(ret.userId);
});
//apply decorations
BdApi.Patcher.after("YABDP4Nitro", DiscordModules.UserStore, "getUser", (_,args,ret) => {
//basic error checking
if(args == undefined) return;
if(args[0] == undefined) return;
if(ret == undefined) return;
let avatarDecorations = self.settings.avatarDecorations;
function getRevealedText(self){
let revealedTextLocal = ""; //init empty string with local scope
//if this user's profile has been downloaded
if(self.downloadedUserProfiles.includes(args[0])){
//get the user's profile from the cached user profiles
let userProfile = self.userProfileMod.getUserProfile(args[0]);
//if their bio is empty, move on to the next check.
if(userProfile.bio != undefined){
//reveal 3y3 encoded text
revealedTextLocal = self.secondsightifyRevealOnly(String(userProfile.bio));
//if there's no 3y3 text, move on to the next check.
if(revealedTextLocal != undefined){
if(String(revealedTextLocal).includes("/a")){
//return bio with the 3y3 decoded
return revealedTextLocal;
}
}
}
}
if(DiscordModules.UserStatusStore.getActivities(args[0]).length > 0){
//grab user's activities (this includes custom status)
let activities = DiscordModules.UserStatusStore.getActivities(args[0]);
//if they don't have a custom status, stop processing.
if(activities[0].name != "Custom Status") return;
//otherwise, grab the text from the custom status
let customStatus = activities[0].state;
//if something has gone horribly wrong, stop processing.
if(customStatus == undefined) return;
//finally reveal 3y3 encoded text
revealedTextLocal = self.secondsightifyRevealOnly(String(customStatus));
//return custom status with the 3y3 decoded
return revealedTextLocal;
}
}
let revealedText = getRevealedText(this);
//if nothing's returned, or an empty string is returned, stop processing.
if(revealedText == undefined) return;
if(revealedText == "") return;
//get position of /a
//let position = revealedText.indexOf("/a");
//if(position == undefined) return;
//Matches the characters "/a" and any numbers after the a
const regex = /\/a\d+/;
let matches = revealedText.toString().match(regex);
if(matches == undefined) return;
let firstMatch = matches[0];
if(firstMatch == undefined) return;
//slice off the /a and just store the ID number
let assetId = firstMatch.slice(2);
//if this decoration is not in the list, return
if(avatarDecorations[assetId] == undefined) return;
//if this user does not have an avatar decoration, or the avatar decoration data does not match the one in the avatar decorations array,
if(ret.avatarDecorationData == undefined || ret.avatarDecorationData?.asset != avatarDecorations[assetId]){
//set avatar decoration data to fake avatar decoration
ret.avatarDecorationData = {
asset: avatarDecorations[assetId],
sku_id: "1144003461608906824" //dummy sku id
}
//add user to the list of users to show with the YABDP4Nitro user badge we haven't already.
if(!this.badgeUserIDs.includes(ret.id)) this.badgeUserIDs.push(ret.id);
}
}); //end of getUser patch for avatar decorations
//wait for shop module to be loaded
await BdApi.Webpack.waitForModule(BdApi.Webpack.Filters.byProps("default", "useFetchPurchases"));
let products = [];
let items = [];
BdApi.Webpack.getStore("CollectiblesCategoryStore").products.forEach((item) => {
products.push(item)
});
products.forEach(product => {
product.items.forEach(item => {
if(item.asset != undefined){
Object.assign(self.settings.avatarDecorations)[item.id] = item.asset;
}
})
});
//trigger decorations fetch
WebpackModules.getByProps("fetchCollectiblesCategories").fetchCollectiblesCategories();
//Wait for avatar decor customization section render module to be loaded.
await BdApi.Webpack.waitForModule(BdApi.Webpack.Filters.byStrings("useGuildMemberAndUserPendingAvatarDecoration"));
//Avatar decoration customization section render module/function.
if(this.decorationCustomizationSectionMod == undefined) this.decorationCustomizationSectionMod = WebpackModules.getAllByProps("default").filter((obj) => obj.default.toString().includes("useGuildMemberAndUserPendingAvatarDecoration"))[0];
//Avatar decoration customization section patch
BdApi.Patcher.after("YABDP4Nitro", this.decorationCustomizationSectionMod, "default", (_,[args],ret) => {
//don't run if this is the try out nitro flow.
if(args.isTryItOutFlow) return;
//push change decoration button
ret.props.children[0].props.children.push(
BdApi.React.createElement("button", {
id: "decorationButton",
children: "Change Decoration [YABDP4Nitro]",
style: {
width: "100px",
height: "50px",
color: "white",
borderRadius: "3px",
marginLeft: "5px",
},
className: `${this.buttonClassModule.button} ${this.buttonClassModule.lookFilled} ${this.buttonClassModule.colorBrand} ${this.buttonClassModule.sizeSmall} ${this.buttonClassModule.grow}`,
onClick: () => {
BdApi.showConfirmationModal("Change Avatar Decoration (YABDP4Nitro)", BdApi.React.createElement(DecorModal));
}
})
);
let listOfDecorationIds = Object.keys(BdApi.getData("YABDP4Nitro", "settings").avatarDecorations);
let avatarDecorationChildren = [];
//for each avatar decoration
for(let i = 0; i < listOfDecorationIds.length; i++){
//text to encode to 3y3
let encodedText = self.secondsightifyEncodeOnly("/a" + listOfDecorationIds[i]); // /a[id]
//javascript that runs onclick for each avatar decoration button
let copyDecoration3y3 = function() {
const clipboardTextElem = document.createElement("textarea");
clipboardTextElem.style.position = "fixed";
clipboardTextElem.value = ` ${encodedText}`;
document.body.appendChild(clipboardTextElem);
clipboardTextElem.select();
clipboardTextElem.setSelectionRange(0, 99999);
document.execCommand("copy");
ZLibrary.Toasts.info("3y3 copied to clipboard!"); document.body.removeChild(clipboardTextElem);
}
let child = BdApi.React.createElement("img", {
style: {
width: "23%",
cursor: "pointer",
marginLeft: "5px",
marginBottom: "10px",
borderRadius: "4px",
backgroundColor: "var(--background-tertiary)"
},
onClick: copyDecoration3y3,
src: "https://cdn.discordapp.com/avatar-decoration-presets/" + this.settings.avatarDecorations[listOfDecorationIds[i]] + ".png?size=64"
});
avatarDecorationChildren.push(child);
//add newline every 4th decoration
if((i+1) % 4 == 0){
//avatarDecorationsHTML += "
"
avatarDecorationChildren.push(BdApi.React.createElement("br"));
}
}
function DecorModal() {
return BdApi.React.createElement("div", {
style: {
width: "100%",
display: "block",
color: "white",
whiteSpace: "nowrap",
overflow: "visible",
marginTop: ".5em"
},
children: avatarDecorationChildren
});
}
}); //end patch of profile decoration section renderer function
} //End of fakeAvatarDecorations()
async UploadEmote(url, channelIdLmao, msg, emoji, runs){
if(this.Uploader == undefined) this.Uploader = WebpackModules.getByProps("uploadFiles", "upload");
if(emoji === undefined){
let emoji;
}
if(msg === undefined){
let msg;
}
let extension = ".gif";
if(!emoji.animated) {
extension = ".png";
if(!this.settings.PNGemote) {
extension = ".webp";
}
}
//Download emote by URL, convert to blob, then convert to File object
let file = await fetch(url).then(r => r.blob()).then(blobFile => new File([blobFile], (emoji.name + extension)))
file.platform = 1; // Not exactly sure what this does, but it should be set to 1.
file.spoiler = false; //not marked as spoiler.
if(this.CloudUploader == undefined) this.CloudUploader = WebpackModules.getByProps("CloudUpload", "CloudUploadStatus");
//Start file upload
let fileUp = new this.CloudUploader.CloudUpload({file:file,isClip:false,isThumbnail:false,platform:1}, channelIdLmao, false, 0);
fileUp.isImage = true;
//Options for the upload
let uploadOptions = new Object();
uploadOptions.channelId = channelIdLmao; //Upload to current channel
uploadOptions.uploads = [fileUp]; //The file from before
uploadOptions.draftType = 0; // Not sure what this does.
uploadOptions.options = { stickerIds: [] }; //No stickers in the message
//Message attached to the upload.
uploadOptions.parsedMessage = { channelId: channelIdLmao, content: msg[1].content, tts: false, invalidEmojis:[] }
//if this is not the first emoji uploaded
if(runs > 1){
//make the message attached to the upload have no text
uploadOptions.parsedMessage = { channelId: channelIdLmao, content: "", tts: false, invalidEmojis:[] }
}
try{
await this.Uploader.uploadFiles(uploadOptions); //finally finish the process of uploading
}catch(err){
console.error(err);
}
}
//Whether we should skip the emoji bypass for a given emoji.
// true = skip bypass
// false = perform bypass
emojiBypassForValidEmoji(emoji, currentChannelId){
if(this.settings.emojiBypassForValidEmoji){
if( (DiscordModules.SelectedGuildStore.getLastSelectedGuildId() == emoji.guildId && !emoji.animated
&& (DiscordModules.ChannelStore.getChannel(currentChannelId.toString()).type <= 0 || DiscordModules.ChannelStore.getChannel(currentChannelId.toString()).type == 11) && emoji.available)
//If emoji is from current guild, not animated, and we are actually in a guild channel,
//and emoji is "available" (could be unavailable due to Server Boost level dropping), cancel emoji bypass
|| emoji.managed){
// OR if emoji is "managed" (emoji.managed = whether the emoji is managed by a Twitch integration)
return true;
}
}
return false;
}
customVideoSettings() { //Unlock stream buttons, apply custom resolution and fps, and apply stream quality bypasses
if(this.StreamButtons == undefined) this.StreamButtons = WebpackModules.getByProps("ApplicationStreamFPSButtons", "ApplicationStreamResolutionButtons");
//If you're trying to figure this shit out yourself, I recommend uncommenting the line below.
//console.log(this.StreamButtons);
//If custom resolution is enabled and the resolution is not set to 0,
if(this.settings.ResolutionEnabled && this.settings.CustomResolution != 0){
//some of these properties are marked as read only, but they still allow you to delete them
//so any time you see "delete", what we're doing is bypassing the read-only thing by deleting it and immediately remaking it.
delete this.StreamButtons.ApplicationStreamResolutions.RESOLUTION_1440;
//Change 1440p resolution internally to custom resolution
this.StreamButtons.ApplicationStreamResolutions.RESOLUTION_1440 = this.settings.CustomResolution;
//********************************** Requirements below this point*************************************
this.StreamButtons.ApplicationStreamSettingRequirements[4].resolution = this.settings.CustomResolution;
this.StreamButtons.ApplicationStreamSettingRequirements[5].resolution = this.settings.CustomResolution;
this.StreamButtons.ApplicationStreamSettingRequirements[6].resolution = this.settings.CustomResolution;
//************************************Buttons below this point*****************************************
//Set resolution button value to custom resolution
this.StreamButtons.ApplicationStreamResolutionButtons[2].value = this.settings.CustomResolution;
delete this.StreamButtons.ApplicationStreamResolutionButtons[2].label;
//Set label of resolution button to custom resolution. This one is used in the popup window that appears before you start streaming.
this.StreamButtons.ApplicationStreamResolutionButtons[2].label = this.settings.CustomResolution.toString();
//Set value of button with suffix label to custom resolution
this.StreamButtons.ApplicationStreamResolutionButtonsWithSuffixLabel[3].value = this.settings.CustomResolution;
delete this.StreamButtons.ApplicationStreamResolutionButtonsWithSuffixLabel[3].label;
//Set label of button with suffix label to custom resolution with "p" after it, ex: "1440p"
//This one is used in the dropdown kind of menu after you've started streaming
this.StreamButtons.ApplicationStreamResolutionButtonsWithSuffixLabel[3].label = this.settings.CustomResolution + "p";
}
//If custom resolution tick is disabled or custom resolution is set to 0,
if(!this.settings.ResolutionEnabled || this.settings.CustomResolution == 0){
//Reset all values to defaults.
delete this.StreamButtons.ApplicationStreamResolutions.RESOLUTION_1440
this.StreamButtons.ApplicationStreamResolutions.RESOLUTION_1440 = 1440;
this.StreamButtons.ApplicationStreamSettingRequirements[4].resolution = 1440;
this.StreamButtons.ApplicationStreamSettingRequirements[5].resolution = 1440;
this.StreamButtons.ApplicationStreamSettingRequirements[6].resolution = 1440;
this.StreamButtons.ApplicationStreamResolutionButtons[2].value = 1440;
delete this.StreamButtons.ApplicationStreamResolutionButtons[2].label;
this.StreamButtons.ApplicationStreamResolutionButtons[2].label = "1440";
this.StreamButtons.ApplicationStreamResolutionButtonsWithSuffixLabel[3].value = 1440;
delete this.StreamButtons.ApplicationStreamResolutionButtonsWithSuffixLabel[3].label;
this.StreamButtons.ApplicationStreamResolutionButtonsWithSuffixLabel[3].label = "1440p";
}
//Removes stream setting requirements
function removeQualityParameters(x){
try{
delete x.quality
}catch(err){
}
try{
delete x.guildPremiumTier
}catch(err){
}
}
/*Remove each of the stream setting requirements
(which basically just tell your client what premiumType / guildPremiumTier you need to access that resolution)
removing the setting requirements makes it default to thinking that every premiumType can use it.*/
this.StreamButtons.ApplicationStreamSettingRequirements.forEach(removeQualityParameters);
function replace60FPSRequirements(x) {
if(x.fps != 30 && x.fps != 15 && x.fps != 5) x.fps = BdApi.getData("YABDP4Nitro","settings").CustomFPS;
}
function restore60FPSRequirements(x) {
if(x.fps != 30 && x.fps != 15 && x.fps != 5) x.fps = 60;
}
//If Custom FPS is enabled and does not equal 60,
if(this.settings.CustomFPSEnabled && this.CustomFPS != 60){
//remove FPS nitro requirements
this.StreamButtons.ApplicationStreamSettingRequirements.forEach(replace60FPSRequirements);
//set suffix label button value to the custom number
this.StreamButtons.ApplicationStreamFPSButtonsWithSuffixLabel[2].value = this.settings.CustomFPS;
delete this.StreamButtons.ApplicationStreamFPSButtonsWithSuffixLabel[2].label;
//set button suffix label with the correct number with " FPS" after it. ex: "75 FPS". This one is used in the dropdown kind of menu
this.StreamButtons.ApplicationStreamFPSButtonsWithSuffixLabel[2].label = this.settings.CustomFPS + " FPS";
//set fps button value to the correct number.
this.StreamButtons.ApplicationStreamFPSButtons[2].value = this.settings.CustomFPS;
delete this.StreamButtons.ApplicationStreamFPSButtons[2].label;
//set fps button label to the correct number. This one is used in the popup window that appears before you start streaming.
this.StreamButtons.ApplicationStreamFPSButtons[2].label = this.settings.CustomFPS;
this.StreamButtons.ApplicationStreamFPS.FPS_60 = this.settings.CustomFPS;
}
//If custom FPS toggle is disabled, or custom fps is set to the default of 60,
if(!this.settings.CustomFPSEnabled || this.CustomFPS == 60){
//Reset all values to defaults.
this.StreamButtons.ApplicationStreamSettingRequirements.forEach(restore60FPSRequirements);
this.StreamButtons.ApplicationStreamFPSButtonsWithSuffixLabel[2].value = 60;
delete this.StreamButtons.ApplicationStreamFPSButtonsWithSuffixLabel[2].label;
this.StreamButtons.ApplicationStreamFPSButtonsWithSuffixLabel[2].label = "60 FPS";
this.StreamButtons.ApplicationStreamFPSButtons[2].value = 60;
delete this.StreamButtons.ApplicationStreamFPSButtons[2].label;
this.StreamButtons.ApplicationStreamFPSButtons[2].label = 60;
this.StreamButtons.ApplicationStreamFPS.FPS_60 = 60;
}
} //End of customVideoSettings()
emojiBypass(){
//Upload Emotes Method
if(this.settings.uploadEmotes) {
BdApi.Patcher.instead("YABDP4Nitro", DiscordModules.MessageActions, "_sendMessage", (_, msg, send) => {
//fix polls
if(msg[2].activityAction)
if(msg[2].poll != undefined){
send(msg[0], msg[1], msg[2], msg[3]);
return;
}
const currentChannelId = msg[0];
let runs = 0; //number of times the uploader has run for this message
msg[1].validNonShortcutEmojis.forEach(emoji => {
if(this.emojiBypassForValidEmoji(emoji, currentChannelId)) return; //Unlocked emoji. Skip.
if(emoji.type == "UNICODE") return; //If this "emoji" is actually a unicode character, it doesn't count. Skip bypassing if so.
if(this.settings.PNGemote) {
emoji.forcePNG = true; //replace WEBP with PNG if the option is enabled.
}
let emojiUrl = DiscordModules.AvatarDefaults.getEmojiURL(emoji);
if(emojiUrl.startsWith("/assets/")) return; //System emoji. Skip.
//If there is a backslash (\) before the emote we are processing,
if(msg[1].content.includes("\\<" + emoji.allNamesString.replace(/~\b\d+\b/g, "") + emoji.id + ">")){
//remove the backslash
msg[1].content = msg[1].content.replace(("\\<" + emoji.allNamesString.replace(/~\b\d+\b/g, "") + emoji.id + ">"), ("<" + emoji.allNamesString.replace(/~\b\d+\b/g, "") + emoji.id + ">"));
//and skip bypass for that emote
return;
}
runs++; // increment number of times the uploader has run for this message.
//remove existing URL parameters and add custom URL parameters for user's size preference. quality is always lossless.
emojiUrl = emojiUrl.split("?")[0] + `?size=${this.settings.emojiSize}&quality=lossless`;
//remove emote from message.
msg[1].content = msg[1].content.replace(`<${emoji.animated ? "a" : ""}${emoji.allNamesString.replace(/~\b\d+\b/g, "")}${emoji.id}>`, "");
//upload emote
this.UploadEmote(emojiUrl, currentChannelId, msg, emoji, runs);
});
if((msg[1].content !== undefined && (msg[1].content != "" || msg[2].activityAction != undefined)) && runs == 0) {
send(msg[0], msg[1], msg[2], msg[3]);
}
});
}
//Ghost mode method
if(this.settings.ghostMode && !this.settings.uploadEmotes) {
BdApi.Patcher.before("YABDP4Nitro", DiscordModules.MessageActions, "sendMessage", (_, [currentChannelId, msg]) => {
let emojiGhostIteration = 0; // dummy value we add to the end of the URL parameters to make the same emoji appear more than once despite having the same URL.
msg.validNonShortcutEmojis.forEach(emoji => {
if(this.emojiBypassForValidEmoji(emoji, currentChannelId)){
return
}
if(emoji.type == "UNICODE") return;
if(this.settings.PNGemote) {
emoji.forcePNG = true;
}
let emojiUrl = DiscordModules.AvatarDefaults.getEmojiURL(emoji);
if(emojiUrl.startsWith("/assets/")){
return
}
if(msg.content.includes("\\<" + emoji.allNamesString.replace(/~\b\d+\b/g, "") + emoji.id + ">")){
msg.content = msg.content.replace(("\\<" + emoji.allNamesString.replace(/~\b\d+\b/g, "") + emoji.id + ">"), ("<" + emoji.allNamesString.replace(/~\b\d+\b/g, "") + emoji.id + ">"));
return //If there is a backslash before the emoji, skip it.
}
//if ghost mode is not required
if(msg.content.replace(`<${emoji.animated ? "a" : ""}${emoji.allNamesString.replace(/~\b\d+\b/g, "")}${emoji.id}>`, "") == "") {
msg.content = msg.content.replace(`<${emoji.animated ? "a" : ""}${emoji.allNamesString.replace(/~\b\d+\b/g, "")}${emoji.id}>`, emojiUrl.split("?")[0] + `?size=${this.settings.emojiSize}&quality=lossless `)
return;
}
emojiGhostIteration++; //increment dummy value
const ghostmodetext = "|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| _ _ _ _ _ "
//if message already has ghostmodetext.
if(msg.content.includes(ghostmodetext)) {
//remove processed emoji from the message
msg.content = msg.content.replace(`<${emoji.animated ? "a" : ""}${emoji.allNamesString.replace(/~\b\d+\b/g, "")}${emoji.id}>`, ""),
//add to the end of the message
msg.content += " " + emojiUrl.split("?")[0] + `?size=${this.settings.emojiSize}&quality=lossless&${emojiGhostIteration} `
return
}
//if message doesn't already have ghostmodetext, remove processed emoji and add it to the end of the message with the ghost mode text
msg.content = msg.content.replace(`<${emoji.animated ? "a" : ""}${emoji.allNamesString.replace(/~\b\d+\b/g, "")}${emoji.id}>`, ""), msg.content += ghostmodetext + "\n" + emojiUrl.split("?")[0] + `?size=${this.settings.emojiSize}&quality=lossless `
return
})
});
return
}
//Original method
if(!this.settings.ghostMode && !this.settings.uploadEmotes) {
BdApi.Patcher.before("YABDP4Nitro", DiscordModules.MessageActions, "sendMessage", (_, [currentChannelId, msg]) => {
//refer to previous bypasses for comments on what this all is for.
let emojiGhostIteration = 0;
msg.validNonShortcutEmojis.forEach(emoji => {
if(this.emojiBypassForValidEmoji(emoji, currentChannelId)) return;
if(emoji.type == "UNICODE") return;
if(this.settings.PNGemote) {
emoji.forcePNG = true;
}
let emojiUrl = DiscordModules.AvatarDefaults.getEmojiURL(emoji);
if(emojiUrl.startsWith("/assets/")) return;
if(msg.content.includes("\\<" + emoji.allNamesString.replace(/~\b\d+\b/g, "") + emoji.id + ">")){
msg.content = msg.content.replace(("\\<" + emoji.allNamesString.replace(/~\b\d+\b/g, "") + emoji.id + ">"), ("<" + emoji.allNamesString.replace(/~\b\d+\b/g, "") + emoji.id + ">"));
return //If there is a backslash before the emoji, skip it.
}
emojiGhostIteration++;
msg.content = msg.content.replace(`<${emoji.animated ? "a" : ""}${emoji.allNamesString.replace(/~\b\d+\b/g, "")}${emoji.id}>`, emojiUrl.split("?")[0] + `?size=${this.settings.emojiSize}&quality=lossless&${emojiGhostIteration} `)
})
});
//editing message in classic mode
BdApi.Patcher.before("YABDP4Nitro", DiscordModules.MessageActions, "editMessage", (_, obj) => {
let msg = obj[2].content
if(msg.search(/\d{18}/g) == -1) return;
if(msg.includes(":ENC:")) return; //Fix jank with editing SimpleDiscordCrypt encrypted messages.
msg.match(/|<:.+?:\d{18}>/g).forEach(idfkAnymore => {
obj[2].content = obj[2].content.replace(idfkAnymore, `https://cdn.discordapp.com/emojis/${idfkAnymore.match(/\d{18}/g)[0]}?size=${this.settings.emojiSize}`)
})
});
return
}
} //End of emojiBypass()
updateQuick(){ //Function that runs when the resolution/fps quick menu is changed.
//Refer to customVideoSettings function for comments on what this all does, since this code is just a copy-paste from there.
const settings = BdApi.getData("YABDP4Nitro","settings");
parseInt(document.getElementById("qualityInput").value);
settings.CustomResolution = parseInt(document.getElementById("qualityInput").value);
parseInt(document.getElementById("qualityInputFPS").value);
settings.CustomFPS = parseInt(document.getElementById("qualityInputFPS").value);
if(parseInt(document.getElementById("qualityInputFPS").value) == 15) settings.CustomFPS = 16;
if(parseInt(document.getElementById("qualityInputFPS").value) == 30) settings.CustomFPS = 31;
if(parseInt(document.getElementById("qualityInputFPS").value) == 5) settings.CustomFPS = 6;
const StreamButtons = WebpackModules.getByProps("ApplicationStreamFPSButtons", "ApplicationStreamResolutionButtons");
if(settings.ResolutionEnabled && settings.CustomResolution != 0){
delete StreamButtons.ApplicationStreamResolutions.RESOLUTION_1440
StreamButtons.ApplicationStreamResolutions.RESOLUTION_1440 = settings.CustomResolution;
StreamButtons.ApplicationStreamSettingRequirements[4].resolution = settings.CustomResolution;
StreamButtons.ApplicationStreamSettingRequirements[5].resolution = settings.CustomResolution;
StreamButtons.ApplicationStreamSettingRequirements[6].resolution = settings.CustomResolution;
StreamButtons.ApplicationStreamResolutionButtons[2].value = settings.CustomResolution;
delete StreamButtons.ApplicationStreamResolutionButtons[2].label;
StreamButtons.ApplicationStreamResolutionButtons[2].label = settings.CustomResolution.toString();
StreamButtons.ApplicationStreamResolutionButtonsWithSuffixLabel[3].value = settings.CustomResolution;
delete StreamButtons.ApplicationStreamResolutionButtonsWithSuffixLabel[3].label;
StreamButtons.ApplicationStreamResolutionButtonsWithSuffixLabel[3].label = settings.CustomResolution + "p";
}
if(!settings.ResolutionEnabled || (settings.CustomResolution == 0)){
delete StreamButtons.ApplicationStreamResolutions.RESOLUTION_1440
StreamButtons.ApplicationStreamResolutions.RESOLUTION_1440 = 1440;
StreamButtons.ApplicationStreamSettingRequirements[4].resolution = 1440;
StreamButtons.ApplicationStreamSettingRequirements[5].resolution = 1440;
StreamButtons.ApplicationStreamSettingRequirements[6].resolution = 1440;
StreamButtons.ApplicationStreamResolutionButtons[2].value = 1440;
delete StreamButtons.ApplicationStreamResolutionButtons[2].label;
StreamButtons.ApplicationStreamResolutionButtons[2].label = "1440";
StreamButtons.ApplicationStreamResolutionButtonsWithSuffixLabel[3].value = 1440;
delete StreamButtons.ApplicationStreamResolutionButtonsWithSuffixLabel[3].label;
StreamButtons.ApplicationStreamResolutionButtonsWithSuffixLabel[3].label = "1440p";
}
function replace60FPSRequirements(x) {
if(x.fps != 30 && x.fps != 15 && x.fps != 5) x.fps = BdApi.getData("YABDP4Nitro","settings").CustomFPS;
}
function restore60FPSRequirements(x) {
if(x.fps != 30 && x.fps != 15 && x.fps != 5) x.fps = 60;
}
if(settings.CustomFPSEnabled){
if(this.CustomFPS != 60){
StreamButtons.ApplicationStreamSettingRequirements.forEach(replace60FPSRequirements);
StreamButtons.ApplicationStreamFPSButtonsWithSuffixLabel[2].value = settings.CustomFPS;
delete StreamButtons.ApplicationStreamFPSButtonsWithSuffixLabel[2].label;
StreamButtons.ApplicationStreamFPSButtonsWithSuffixLabel[2].label = settings.CustomFPS + " FPS";
StreamButtons.ApplicationStreamFPSButtons[2].value = settings.CustomFPS;
delete StreamButtons.ApplicationStreamFPSButtons[2].label;
StreamButtons.ApplicationStreamFPSButtons[2].label = settings.CustomFPS;
StreamButtons.ApplicationStreamFPS.FPS_60 = settings.CustomFPS;
}
}
if(!settings.CustomFPSEnabled || this.CustomFPS == 60){
StreamButtons.ApplicationStreamSettingRequirements.forEach(restore60FPSRequirements);
StreamButtons.ApplicationStreamFPSButtonsWithSuffixLabel[2].value = 60;
delete StreamButtons.ApplicationStreamFPSButtonsWithSuffixLabel[2].label;
StreamButtons.ApplicationStreamFPSButtonsWithSuffixLabel[2].label = 60 + " FPS";
StreamButtons.ApplicationStreamFPSButtons[2].value = 60;
delete StreamButtons.ApplicationStreamFPSButtons[2].label;
StreamButtons.ApplicationStreamFPSButtons[2].label = 60;
StreamButtons.ApplicationStreamFPS.FPS_60 = 60;
}
} //End of updateQuick()
videoQualityModule(){ //Custom Bitrates, FPS, Resolution
if(this.videoOptionFunctions == undefined) this.videoOptionFunctions = BdApi.Webpack.getByPrototypeKeys("updateVideoQuality").prototype;
if(this.settings.CustomBitrateEnabled){
BdApi.Patcher.before("YABDP4Nitro", this.videoOptionFunctions, "updateVideoQuality", (e) => {
if(this.settings.minBitrate > 0){
//Minimum Bitrate
e.framerateReducer.sinkWants.qualityOverwrite.bitrateMin = (this.settings.minBitrate * 1000);
e.videoQualityManager.qualityOverwrite.bitrateMin = (this.settings.minBitrate * 1000);
e.videoQualityManager.options.videoBitrateFloor = (this.settings.minBitrate * 1000);
e.videoQualityManager.options.videoBitrate.min = (this.settings.minBitrate * 1000);
e.videoQualityManager.options.desktopBitrate.min = (this.settings.minBitrate * 1000);
}
if(this.settings.maxBitrate > 0){
//Maximum Bitrate
e.framerateReducer.sinkWants.qualityOverwrite.bitrateMax = (this.settings.maxBitrate * 1000);
e.videoQualityManager.qualityOverwrite.bitrateMax = (this.settings.maxBitrate * 1000);
e.videoQualityManager.options.videoBitrate.max = (this.settings.maxBitrate * 1000);
e.videoQualityManager.options.desktopBitrate.max = (this.settings.maxBitrate * 1000);
}
if(this.settings.targetBitrate > 0){
//Target Bitrate
e.framerateReducer.sinkWants.qualityOverwrite.bitrateTarget = (this.settings.targetBitrate * 1000);
e.videoQualityManager.qualityOverwrite.bitrateTarget = (this.settings.targetBitrate * 1000);
e.videoQualityManager.options.desktopBitrate.target = (this.settings.targetBitrate * 1000);
}
if(this.settings.voiceBitrate != 128){
//Audio Bitrate
e.voiceBitrate = this.settings.voiceBitrate * 1000;
e.conn.setTransportOptions({
encodingVoiceBitRate: e.voiceBitrate
});
}
});
}
//Video quality bypasses if Custom FPS is enabled.
if(this.settings.CustomFPSEnabled){
BdApi.Patcher.before("YABDP4Nitro", this.videoOptionFunctions, "updateVideoQuality", (e) => {
if(e.stats?.camera !== undefined) return; //if camera is enabled, don't fuck with fps
//Most of this is pretty self-explanatory.
e.videoQualityManager.options.videoBudget.framerate = this.settings.CustomFPS;
e.videoQualityManager.options.videoCapture.framerate = this.settings.CustomFPS;
/*for(const ladder in e.videoQualityManager.ladder.ladder) {
e.videoQualityManager.ladder.ladder[ladder].framerate = this.settings.CustomFPS;
//e.videoQualityManager.ladder.ladder[ladder].mutedFramerate = parseInt(this.settings.CustomFPS / 2);
}
for(const ladder of e.videoQualityManager.ladder.orderedLadder){
ladder.framerate = this.settings.CustomFPS;
//ladder.mutedFramerate = parseInt(this.settings.CustomFPS / 2);
}
e.videoQualityManager.connection.remoteVideoSinkWants = this.settings.CustomFPS;*/
});
}
//If screen sharing bypasses are enabled,
if(this.settings.screenSharing){
BdApi.Patcher.before("YABDP4Nitro", this.videoOptionFunctions, "updateVideoQuality", (e) => {
//Ensure video quality parameters match the stream parameters.
const videoQuality = new Object({
width: e.videoStreamParameters[0].maxResolution.width,
height: e.videoStreamParameters[0].maxResolution.height,
framerate: e.videoStreamParameters[0].maxFrameRate,
});
//Ensure video budget quality parameters match stream parameters
e.videoQualityManager.options.videoBudget = videoQuality;
//Ensure video capture quality parameters match stream parameters
e.videoQualityManager.options.videoCapture = videoQuality;
//Ensure pixel budget matches stream resolution.
/*e.videoQualityManager.ladder.pixelBudget = (videoQuality.height * videoQuality.width);
//What follows is the word 'ladder' repeated way too many times.
for(const ladder in e.videoQualityManager.ladder.ladder) {
e.videoQualityManager.ladder.ladder[ladder].width = videoQuality.width * (ladder / 100);
e.videoQualityManager.ladder.ladder[ladder].height = videoQuality.height * (ladder / 100);
}
for(const ladder of e.videoQualityManager.ladder.orderedLadder){
ladder.width = videoQuality.width * (ladder.wantValue / 100);
ladder.height = videoQuality.height * (ladder.wantValue / 100);
ladder.pixelCount = ladder.width * ladder.height;
}*/
});
}
if(this.settings.videoCodec > 0){ // Video codecs
BdApi.Patcher.before("YABDP4Nitro", this.videoOptionFunctions, "updateVideoQuality", (e) => {
//This code determines what codec was chosen
let isCodecH265 = false;
let isCodecH264 = false;
let isCodecAV1 = false;
let isCodecVP8 = false;
let isCodecVP9 = false;
switch(this.settings.videoCodec){
case 1:
isCodecH265 = true;
break;
case 2:
isCodecH264 = true;
break;
case 3:
isCodecAV1 = true;
break;
case 4:
isCodecVP8 = true;
break;
case 5:
isCodecVP9 = true;
break;
}
//This code determines what priorities to set each codec to based on which one was chosen by the user.
let currentHighestNum = 1;
function setPriority(codec){
switch(codec){
case 0:
if(isCodecH265){
return 1;
break;
}else{
currentHighestNum += 1;
return currentHighestNum;
}
break;
case 1:
if(isCodecH264){
return 1;
break;
}else{
currentHighestNum += 1;
return currentHighestNum;
}
break;
case 2:
if(isCodecAV1){
return 1;
break;
}else{
currentHighestNum += 1;
return currentHighestNum;
}
break;
case 3:
if(isCodecVP8){
return 1;
break;
}else{
currentHighestNum += 1;
return currentHighestNum;
}
break;
case 4:
if(isCodecVP9){
return 1;
break;
}else{
currentHighestNum += 1;
return currentHighestNum;
}
break;
}
}
//and this code sets the priorities based on the outputs of setPriority.
if(e.codecs != undefined && e.codecs[1]?.decode != undefined){
e.codecs[1].decode = isCodecH265; //H.265
e.codecs[1].encode = isCodecH265;
e.codecs[1].priority = parseInt(setPriority(0));
e.codecs[2].decode = isCodecH264; //H.264
e.codecs[2].encode = isCodecH264;
e.codecs[2].priority = parseInt(setPriority(1));
e.codecs[3].decode = isCodecVP8; //VP8
e.codecs[3].encode = isCodecVP8;
e.codecs[3].priority = parseInt(setPriority(2));
e.codecs[4].decode = isCodecVP9; //VP9
e.codecs[4].encode = isCodecVP9;
e.codecs[4].priority = parseInt(setPriority(3));
}
});
}
} //End of videoQualityModule()
buttonCreate(){ //Creates the FPS and Resolution Swapper
let qualityButton = document.createElement('button');
qualityButton.id = 'qualityButton';
qualityButton.className = `${this.buttonClassModule.lookFilled} ${this.buttonClassModule.colorBrand}`;
qualityButton.innerHTML = 'Quality
';
qualityButton.style.position = "absolute";
qualityButton.style.zIndex = "2";
qualityButton.style.bottom = "0";
qualityButton.style.left = "50%";
qualityButton.style.transform = "translateX(-50%)"
qualityButton.style.height = "15px";
qualityButton.style.width = "48px";
qualityButton.style.verticalAlign = "middle";
qualityButton.style.textAlign = "left";
qualityButton.style.borderTopLeftRadius = "5px";
qualityButton.style.borderTopRightRadius = "4px";
qualityButton.style.borderBottomLeftRadius = "4px";
qualityButton.style.borderBottomRightRadius = "4px";
qualityButton.onclick = function(){
if(qualityMenu.style.visibility == "hidden") {
qualityMenu.style.visibility = "visible";
}else{
qualityMenu.style.visibility = "hidden";
}
}
try{
document.getElementsByClassName(DiscordClassModules.AccountDetails.container)[0].appendChild(qualityButton);
}catch(err){
console.error("[YABDP4Nitro] What the fuck happened..? During buttonCreate() " + err);
}
let qualityMenu = document.createElement('div');
qualityMenu.id = 'qualityMenu';
qualityMenu.style.visibility = 'hidden';
qualityMenu.style.position = "relative";
qualityMenu.style.zIndex = "1";
qualityMenu.style.bottom = "410%";
qualityMenu.style.left = "-59%";
qualityMenu.style.height = "20px";
qualityMenu.style.width = "100px";
qualityMenu.onclick = function(event){
event.stopPropagation();
}
document.getElementById("qualityButton").appendChild(qualityMenu);
let qualityInput = document.createElement('input');
qualityInput.id = 'qualityInput';
qualityInput.type = 'text';
qualityInput.placeholder = 'Resolution';
qualityInput.style.width = "33%";
qualityInput.style.zIndex = "1";
qualityInput.value = this.settings.CustomResolution;
qualityMenu.appendChild(qualityInput);
let qualityInputFPS = document.createElement('input');
qualityInputFPS.id = 'qualityInputFPS';
qualityInputFPS.type = 'text';
qualityInputFPS.placeholder = 'FPS';
qualityInputFPS.style.width = "33%";
qualityInputFPS.style.zIndex = "1";
qualityInputFPS.value = this.settings.CustomFPS;
qualityMenu.appendChild(qualityInputFPS);
} //End of buttonCreate()
async stickerSending(){
if(this.currentChannelIdMod == undefined) this.currentChannelIdMod = WebpackModules.getByProps("getLastChannelFollowingDestination");
if(this.stickerSendabilityModule == undefined) this.stickerSendabilityModule = WebpackModules.getByProps("StickerSendability","getStickerSendability","isSendableSticker");
BdApi.Patcher.instead("YABDP4Nitro", this.stickerSendabilityModule, "getStickerSendability", () => {
return 0
});
BdApi.Patcher.instead("YABDP4Nitro", this.stickerSendabilityModule, "isSendableSticker", () => {
return true
});
BdApi.Patcher.instead("YABDP4Nitro", DiscordModules.MessageActions, "sendStickers", (_,b) => {
let stickerID = b[1][0];
let stickerURL = "https://media.discordapp.net/stickers/" + stickerID + ".png?size=4096&quality=lossless"
let currentChannelId = this.currentChannelIdMod.getChannelId();
if(this.settings.uploadStickers){
let emoji = new Object();
emoji.animated = false;
emoji.name = b[0];
let msg = [undefined,{content: ""}]
this.UploadEmote(stickerURL, currentChannelId, [undefined,{content:""}], emoji)
return
}
if(!this.settings.uploadStickers){
let messageContent = {content: stickerURL, tts: false, invalidEmojis:[], validNonShortcutEmojis:[]}
DiscordModules.MessageActions.sendMessage(currentChannelId, messageContent, undefined, {})
}
});
}
decodeAndApplyProfileColors(){
BdApi.Patcher.after("YABDP4Nitro", this.userProfileMod, "getUserProfile", (_,args,ret) => {
if(ret == undefined) return;
if(ret.bio == null) return;
const colorString = ret.bio.match(
/\u{e005b}\u{e0023}([\u{e0061}-\u{e0066}\u{e0041}-\u{e0046}\u{e0030}-\u{e0039}]+?)\u{e002c}\u{e0023}([\u{e0061}-\u{e0066}\u{e0041}-\u{e0046}\u{e0030}-\u{e0039}]+?)\u{e005d}/u,
);
if(colorString == null) return;
let parsed = [...colorString[0]].map((c) => String.fromCodePoint(c.codePointAt(0) - 0xe0000)).join("");
let colors = parsed
.substring(1, parsed.length - 1)
.split(",")
.map(x => parseInt(x.replace("#", "0x"), 16));
ret.themeColors = colors;
ret.premiumType = 2;
});
}
//Everything that has to do with the GUI and encoding of the fake profile colors 3y3 shit.
//Replaced DOM manipulation with React patching 4/2/2024
async encodeProfileColors(primary, accent) {
//wait for theme color picker module to be loaded
await BdApi.Webpack.waitForModule(BdApi.Webpack.Filters.byProps("getTryItOutThemeColors"));
//wait for color picker renderer module to be loaded
await BdApi.Webpack.waitForModule(BdApi.Webpack.Filters.byStrings("__invalid_profileThemesSection"));
if(this.colorPickerRendererMod == undefined) this.colorPickerRendererMod = WebpackModules.getAllByProps("default").filter(obj => obj.default.toString().includes("__invalid_profileThemesSection"))[0];
BdApi.Patcher.after("YABDP4Nitro", this.colorPickerRendererMod, "default", (_, args, ret) => {
ret.props.children.props.children.push( //append copy colors 3y3 button
BdApi.React.createElement("button", {
id: "copy3y3button",
children: "Copy Colors 3y3",
className: `${this.buttonClassModule.button} ${this.buttonClassModule.lookFilled} ${this.buttonClassModule.colorBrand} ${this.buttonClassModule.sizeSmall} ${this.buttonClassModule.grow}`,
style: {
marginLeft: "10px",
marginTop: "10px"
},
onClick: () => {
let themeColors = null;
try{
themeColors = ZLibrary.WebpackModules.getByProps("getTryItOutThemeColors").getAllTryItOut().tryItOutThemeColors
}catch(err){
console.warn(err);
}
if(themeColors == null){
try{
themeColors = ZLibrary.WebpackModules.getByProps("getTryItOutThemeColors").getAllPending().pendingThemeColors;
}catch(err){
console.error(err);
}
}
if(themeColors == undefined){
Toasts.warning("Nothing has been copied. Is the selected color identical to your current color?");
return
}
const primary = themeColors[0];
const accent = themeColors[1];
let message = `[#${primary.toString(16).padStart(6, "0")},#${accent.toString(16).padStart(6, "0")}]`;
const padding = "";
let encoded = Array.from(message)
.map(x => x.codePointAt(0))
.filter(x => x >= 0x20 && x <= 0x7f)
.map(x => String.fromCodePoint(x + 0xe0000))
.join("");
let encodedStr = ((padding || "") + " " + encoded);
//do this stupid shit Chrome makes you do to copy text to the clipboard.
const clipboardTextElem = document.createElement("textarea");
clipboardTextElem.style.position = 'fixed';
clipboardTextElem.value = encodedStr;
document.body.appendChild(clipboardTextElem);
clipboardTextElem.select();
clipboardTextElem.setSelectionRange(0, 99999);
document.execCommand('copy');
Toasts.info("3y3 copied to clipboard!");
document.body.removeChild(clipboardTextElem);
}
})
);
});
} //End of encodeProfileColors()
//Commented to hell and back on 3/6/2024
bannerUrlDecoding(){ //Decode 3y3 from profile bio and apply fake banners.
//if userBg integration is enabled, and we havent already downloaded & parsed userBg data,
if(this.settings.userBgIntegration && !this.fetchedUserBg){
//userBg database url.
const userBgJsonUrl = "https://raw.githubusercontent.com/Discord-Custom-Covers/usrbg/master/dist/usrbg.json";
//download, then parse json
fetch(userBgJsonUrl).then(res => res.text().then(str => JSON.parse(str).forEach(obj => {
this.userBgs[obj.uid] = obj.img; //add each entry to an object with {userId: imgURL} format
})));
//mark db as fetched so we only fetch it once per load of the plugin
this.fetchedUserBg = true;
}
if(this.bannerUrlModule == undefined) this.bannerUrlModule = WebpackModules.getByPrototypes("getBannerURL");
//Patch getUserBannerURL function
BdApi.Patcher.before("YABDP4Nitro", this.bannerUrlModule, "getUserBannerURL", (_, args) => {
args[0].canAnimate = true;
});
//Patch getBannerURL function
BdApi.Patcher.instead("YABDP4Nitro", this.bannerUrlModule.prototype, "getBannerURL", (user, [args], ogFunction) => {
let profile = user._userProfile;
//Returning ogFunction with the same arguments that were passed to this function will do the vanilla check for a legit banner.
if(profile == undefined) return ogFunction(args);
if(this.settings.userBgIntegration){ //if userBg integration is enabled
//check userBg database for this user
let userBg = this.userBgs[user.userId];
//if user is in userBg database,
if(userBg != undefined){
profile.banner = "funky_kong_is_epic"; //set banner id to fake value
profile.premiumType = 2; //set this profile to appear with premium rendering
return userBg; //return userBg banner URL and exit.
}
}
//do original function if we don't have the user's bio
if(profile.bio == undefined) return ogFunction(args);
//reveal 3y3 encoded text, store as parsed
let parsed = this.secondsightifyRevealOnly(profile.bio);
//if there is no 3y3 encoded text, return original function
if(parsed == undefined) return ogFunction(args);
//This regex matches /B{*} . Do not touch unless you know what you are doing.
let regex = /B\{[^}]*\}/;
//find banner url in parsed bio
let matches = parsed.toString().match(regex);
//if there's no matches, return original function
if(matches == undefined) return ogFunction(args);
if(matches == "") return ogFunction(args);
//if there is matched text, grab the first match, replace the starting "B{" and ending "}" to get the clean filename
let matchedText = matches[0].replace("B{", "").replace("}", "");
//Checking for file extension.
if(!String(matchedText).endsWith(".gif") && !String(matchedText).endsWith(".png") && !String(matchedText).endsWith(".jpg") && !String(matchedText).endsWith(".jpeg") && !String(matchedText).endsWith(".webp")){
matchedText += ".gif"; //Fallback to a default file extension if one is not found.
}
//set banner id to fake value
profile.banner = "funky_kong_is_epic"
//set this profile to appear with premium rendering
profile.premiumType = 2;
//add this user to the list of users that show with the YABDP4Nitro user badge if we haven't aleady.
if(!this.badgeUserIDs.includes(user.userId)) this.badgeUserIDs.push(user.userId);
//return final banner URL.
return `https://i.imgur.com/${matchedText}`;
}); //End of patch for getBannerURL
if(this.profileRenderer == undefined) this.profileRenderer = WebpackModules.getAllByProps("default").filter((obj) => obj.default.toString().includes("PRESS_PREMIUM_UPSELL"))[0]
BdApi.Patcher.before("YABDP4Nitro", this.profileRenderer, "default", (_,args) => {
if(args == undefined) return;
if(args[0]?.displayProfile?.banner == undefined) return;
//if this user's banner is a fake banner
if(args[0].displayProfile.banner == "funky_kong_is_epic"){
//don't show upsell
args[0].showPremiumBadgeUpsell = false;
}
});
BdApi.Patcher.after("YABDP4Nitro", this.profileRenderer, "default", (_,args,ret) => {
if(args == undefined) return;
if(args[0]?.displayProfile?.banner == undefined) return;
if(ret == undefined) return;
if(ret.props?.hasBanner == undefined) return;
//if this user's banner is a fake banner
if(args[0].displayProfile.banner == "funky_kong_is_epic"){
//tell the profile renderer to show them as having a banner.
ret.props.hasBanner = true;
}
});
} //End of bannerUrlDecoding()
//Make buttons in profile customization settings, encode imgur URLs and copy to clipboard
//Documented/commented and partially rewritten to use React patching on 3/6/2024
async bannerUrlEncoding(secondsightifyEncodeOnly){
//wait for banner customization renderer module to be loaded
await BdApi.Webpack.waitForModule(BdApi.Webpack.Filters.byStrings("USER_SETTINGS_PROFILE_BANNER"));
if(this.profileBannerSectionRenderer == undefined) this.profileBannerSectionRenderer = WebpackModules.getAllByProps("default").filter((obj) => obj.default.toString().includes("USER_SETTINGS_PROFILE_BANNER"))[0];
BdApi.Patcher.after("YABDP4Nitro", this.profileBannerSectionRenderer, "default", (_, args, ret) => {
args[0].showPremiumIcon = false;
//create and append profileBannerUrlInput input element.
ret.props.children.props.children.push(
BdApi.React.createElement("input", {
id: "profileBannerUrlInput",
placeholder: "Imgur URL",
style: {
width: "30%",
height: "20%",
maxHeight: "50%",
marginLeft: "10px",
marginTop: "5px"
}
})
);
ret.props.children.props.children.push( //append Copy 3y3 button
//create react element
BdApi.React.createElement("button", {
id: "profileBannerButton",
children: "Copy Banner 3y3",
className: `${this.buttonClassModule.button} ${this.buttonClassModule.lookFilled} ${this.buttonClassModule.colorBrand} ${this.buttonClassModule.sizeSmall} ${this.buttonClassModule.grow}`,
size: "sizeSmall__71a98",
style: {
whiteSpace: "nowrap",
marginLeft: "10px"
},
onClick: async function() { //Upon clicking Copy 3y3 button
//grab text from banner URL input textarea
let profileBannerUrlInputValue = String(document.getElementById("profileBannerUrlInput").value);
//if it's empty, stop processing.
if(profileBannerUrlInputValue == "") return;
if(profileBannerUrlInputValue == undefined) return;
//clean up string to encode
let stringToEncode = "" + profileBannerUrlInputValue
.replace("http://", "") //get rid of protocol
.replace("https://", "")
.replace("i.imgur.com","imgur.com"); //change i.imgur.com to imgur.com
let encodedStr = ""; //initialize encoded string as empty string
stringToEncode = String(stringToEncode); //make doubly sure stringToEncode is a string
//if url seems correct
if(stringToEncode.toLowerCase().startsWith("imgur.com")){
//Check for album or gallery URL
if(stringToEncode.replace("imgur.com/","").startsWith("a/") || stringToEncode.replace("imgur.com/","").startsWith("gallery/")){
//Album URL, what follows is all to get the direct image link, since the album URL is not a direct link to the file.
const parser = new DOMParser(); //initialize DOM parser
//Fetch imgur album page
stringToEncode = await BdApi.Net.fetch(("https://" + stringToEncode), {
method: "GET",
mode: "cors"
}).then(res => res.text() //use result as text
/*This next part is interesting, so a code explanation follows:
* First, we're parsing the HTML of the Imgur Album using the DOM parser we initialized before.
* Then, find the meta tag in the HTML that has the og:image property
* Finally, we take the content of the og:image meta tag. This is the direct URL to the file that we want.
* The need to do this makes it necessary for this function to be async.
*/
.then(res => parser.parseFromString(res, "text/html").querySelector('[property="og:image"]').content));
//clean up string to encode
stringToEncode = stringToEncode.replace("http://", "") //get rid of protocol
.replace("https://", "") //get rid of protocol
.replace("i.imgur.com","imgur.com") //change i.imgur.com to imgur.com
.split("?")[0]; //remove any URL parameters since we don't want or need them
}
//add starting "B{" , remove "imgur.com/" , and add ending "}"
stringToEncode = "B{" + stringToEncode.replace("imgur.com/","") + "}"
//finally encode the string, adding a space before it so nothing fucks up
encodedStr = " " + secondsightifyEncodeOnly(stringToEncode);
//let the user know what has happened
Toasts.info("3y3 copied to clipboard!");
//If this is not an Imgur URL, yell at the user.
}else if(stringToEncode.toLowerCase().startsWith("imgur.com") == false){
Toasts.warning("Please use Imgur!");
return;
}
//if somehow none of the previous code ran, this is the last protection against an error. If this runs, something has probably gone horribly wrong.
if(encodedStr == "") return;
//Do this stupid shit that Chrome forces you to do to copy text to the clipboard.
const clipboardTextElem = document.createElement("textarea"); //create a textarea
clipboardTextElem.style.position = 'fixed'; //this is so that the rest of the document doesn't try to format itself to fit a textarea in it
clipboardTextElem.value = encodedStr; //add the encoded string to the textarea
document.body.appendChild(clipboardTextElem); //add the textarea to the document
clipboardTextElem.select(); //focus the textarea?
clipboardTextElem.setSelectionRange(0, 99999); //select all of the text in the textarea
document.execCommand('copy'); //finally send the copy command
document.body.removeChild(clipboardTextElem); //get rid of the evidence
} //end of onClick function
}) //end of react createElement
); //end of profileBannerButton element push
}); //end of patched function
} //End of bannerUrlEncoding()
bannerUrlDecodingPreview(){
if(this.profileCustomizationModule == undefined) this.profileCustomizationModule = WebpackModules.getByProps("getTryItOutThemeColors");
BdApi.Patcher.after("YABDP4Nitro", this.profileCustomizationModule, "getAllPending", (_, args, ret) => {
let user = this.currentUser;
let userProfile = this.userProfileMod.getUserProfile(user.id);
if(userProfile == undefined) return;
let parsed = this.secondsightifyRevealOnly(userProfile.bio);
if(parsed == undefined) return;
let regex = /B\{[^}]*\}/;
let matches = parsed.toString().match(regex);
if(matches == undefined) return;
if(matches == "") return;
let matchedText = matches[0].replace("B{", "").replace("}", "");
if(!String(matchedText).endsWith(".gif") && !String(matchedText).endsWith(".png") && !String(matchedText).endsWith(".jpg") && !String(matchedText).endsWith(".jpeg") && !String(matchedText).endsWith(".webp") && !String(matchedText).endsWith(".mp4") && !String(matchedText).endsWith(".tiff") && !String(matchedText).endsWith(".avi") && !String(matchedText).endsWith(".webm")){
matchedText += ".gif"; //No supported file extension detected.
//Falling back to default file extension
}
ret.pendingBanner = `https://i.imgur.com/${matchedText}`;
});
}
appIcons(){
this.settings.changePremiumType = true; //Forcibly enable premiumType. Couldn't find a workaround, sry.
try{
if(!(this.originalNitroStatus > 1)){
this.currentUser.premiumType = 1;
setTimeout(() => {
if(this.settings.changePremiumType){
this.currentUser.premiumType = 1;
}
}, 10000);
}
}
catch(err){
Logger.err(this.getName(), "Error occurred changing premium type. " + err);
}
if(this.appIconModule == undefined) this.appIconModule = WebpackModules.getByProps("getCurrentDesktopIcon");
delete this.appIconModule.isUpsellPreview;
Object.defineProperty(this.appIconModule, "isUpsellPreview", {
value: false,
configurable: true,
enumerable: true,
writable: true,
});
delete this.appIconModule.isEditorOpen;
Object.defineProperty(this.appIconModule, "isEditorOpen", {
value: false,
configurable: true,
enumerable: true,
writable: true,
});
if(this.appIconButtonsModule == undefined) this.appIconButtonsModule = WebpackModules.getAllByProps("default").filter((obj) => obj.default.toString().includes("renderCTAButtons"))[0];
BdApi.Patcher.before("YABDP4Nitro", this.appIconButtonsModule, "default", (_,args) => {
args[0].disabled = false; //force buttons clickable
});
}
onStart() {
PluginUpdater.checkForUpdate(this.getName(), this.getVersion(), this._config.info.github_raw);
this.currentUser = WebpackModules.getByProps("getCurrentUser").getCurrentUser();
this.originalNitroStatus = this.currentUser.premiumType;
this.previewInitial = BdApi.Webpack.getModule(BdApi.Webpack.Filters.byProps("isPreview")).isPreview;
this.userBgs = {};
this.downloadedUserProfiles = [];
this.badgeUserIDs = [];
this.fetchedUserBg = false;
this.fetchedUserPfp = false;
this.userProfileMod = WebpackModules.getByProps("getUserProfile");
this.buttonClassModule = WebpackModules.getByProps("lookFilled", "button", "contents");
this.dispatcher = WebpackModules.getByProps("subscribe", "dispatch");
this.canUserUseMod = WebpackModules.getByProps("canUserUse");
this.saveAndUpdate();
}
onStop() {
this.currentUser.premiumType = this.originalNitroStatus;
Patcher.unpatchAll();
BdApi.Patcher.unpatchAll("YABDP4Nitro");
if(document.getElementById("qualityButton")) document.getElementById("qualityButton").remove();
if(document.getElementById("qualityMenu")) document.getElementById("qualityMenu").remove();
if(document.getElementById("qualityInput")) document.getElementById("qualityInput").remove();
if(document.getElementById("copy3y3button")) document.getElementById("copy3y3button").remove();
if(document.getElementById("profileBannerButton")) document.getElementById("profileBannerButton").remove();
if(document.getElementById("profileBannerUrlInput")) document.getElementById("profileBannerUrlInput").remove();
if(document.getElementById("decorationButton")) document.getElementById("decorationButton").remove();
if(document.getElementById("changeProfileEffectButton")) document.getElementById("changeProfileEffectButton").remove();
if(document.getElementById("profilePictureUrlInput")) document.getElementById("profilePictureUrlInput").remove();
if(document.getElementById("profilePictureButton")) document.getElementById("profilePictureButton").remove();
BdApi.DOM.removeStyle("YABDP4Nitro");
BdApi.DOM.removeStyle("YABDP4NitroBadges");
this.userBgs = {};
}
};
};
return plugin(Plugin, Api);
})(global.ZeresPluginLibrary.buildPlugin(config));
})();
/*@end@*/