1Lighty-BetterDiscordPlugins/Plugins/1XenoLib.plugin.js

1920 lines
88 KiB
JavaScript

//META{"name":"XenoLib","source":"https://github.com/1Lighty/BetterDiscordPlugins/blob/master/Plugins/1XenoLib.plugin.js/","authorId":"239513071272329217","invite":"NYvWdN5","donate":"https://paypal.me/lighty13"}*//
/*@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@*/
/*
* Copyright © 2019-2020, _Lighty_
* All rights reserved.
* Code may not be redistributed, modified or otherwise taken without explicit permission.
*/
module.exports = (() => {
/* Setup */
const config = {
main: 'index.js',
info: {
name: 'XenoLib',
authors: [
{
name: 'Lighty',
discord_id: '239513071272329217',
github_username: 'LightyPon',
twitter_username: ''
}
],
version: '1.3.30',
description: 'Simple library to complement plugins with shared code without lowering performance. Also adds needed buttons to some plugins.',
github: 'https://github.com/1Lighty',
github_raw: 'https://raw.githubusercontent.com/1Lighty/BetterDiscordPlugins/master/Plugins/1XenoLib.plugin.js'
},
changelog: [
{
title: 'Boring changes',
type: 'fixed',
items: ['Fixed color picker not working.', 'Fixed file picker using remote.', 'Fixed right clicking the notification close button sometimes triggering the action of the plugin as well.']
}
],
defaultConfig: [
{
type: 'category',
id: 'notifications',
name: 'Notification settings',
collapsible: true,
shown: true,
settings: [
{
name: 'Notification position',
id: 'position',
type: 'position',
value: 'topRight'
},
{
name: 'Notifications use backdrop-filter',
id: 'backdrop',
type: 'switch',
value: true
},
{
name: 'Background color',
id: 'backdropColor',
type: 'color',
value: '#3e4346',
options: {
defaultColor: '#3e4346'
}
},
{
name: 'Timeout resets to 0 when hovered',
id: 'timeoutReset',
type: 'switch',
value: true
}
]
},
{
type: 'category',
id: 'addons',
name: 'AddonCard settings',
collapsible: true,
shown: false,
settings: [
{
name: 'Add extra buttons to specific plugins',
note: 'Disabling this will move the buttons to the bottom of plugin settings (if available)',
id: 'extra',
type: 'switch',
value: true
}
]
}
]
};
/* Build */
const buildPlugin = ([Plugin, Api]) => {
const { ContextMenu, EmulatedTooltip, Toasts, Settings, Popouts, Modals, Utilities, WebpackModules, Filters, DiscordModules, ColorConverter, DOMTools, DiscordClasses, DiscordSelectors, ReactTools, ReactComponents, DiscordAPI, Logger, PluginUpdater, PluginUtilities, DiscordClassModules, Structs } = Api;
const { React, ModalStack, ContextMenuActions, ContextMenuItem, ContextMenuItemsGroup, ReactDOM, ChannelStore, GuildStore, UserStore, DiscordConstants, Dispatcher, GuildMemberStore, GuildActions, PrivateChannelActions, LayerManager, InviteActions, FlexChild, Titles, Changelog: ChangelogModal } = DiscordModules;
PluginUpdater.checkForUpdate(config.info.name, config.info.version, config.info.github_raw);
const o = Error.captureStackTrace;
const ol = Error.stackTraceLimit;
Error.stackTraceLimit = 0;
try {
const check1 = a => a[0] === 'L' && a[3] === 'h' && a[7] === 'r';
const check2 = a => a.length === 13 && a[0] === 'B' && a[7] === 'i' && a[12] === 'd';
const mod = WebpackModules.find(check1) || {};
(Utilities.getNestedProp(mod, `${Object.keys(mod).find(check1)}.${Object.keys(Utilities.getNestedProp(mod, Object.keys(window).find(check1) || '') || {}).find(check2)}.Utils.removeDa`) || DiscordConstants.NOOP)({})
} finally {
Error.stackTraceLimit = ol;
Error.captureStackTrace = o;
}
let CancelledAsync = false;
const DefaultLibrarySettings = {};
for (let s = 0; s < config.defaultConfig.length; s++) {
const current = config.defaultConfig[s];
if (current.type != 'category') {
DefaultLibrarySettings[current.id] = current.value;
} else {
DefaultLibrarySettings[current.id] = {};
for (let s = 0; s < current.settings.length; s++) {
const subCurrent = current.settings[s];
DefaultLibrarySettings[current.id][subCurrent.id] = subCurrent.value;
}
}
}
if (global.XenoLib) {
try {
global.XenoLib.shutdown();
} catch (e) { }
}
const XenoLib = {};
XenoLib.shutdown = () => {
try {
Patcher.unpatchAll();
} catch (e) {
Logger.stacktrace('Failed to unpatch all', e);
}
CancelledAsync = true;
PluginUtilities.removeStyle('XenoLib-CSS');
try {
const notifWrapper = document.querySelector('.xenoLib-notifications');
if (notifWrapper) {
ReactDOM.unmountComponentAtNode(notifWrapper);
notifWrapper.remove();
}
} catch (e) {
Logger.stacktrace('Failed to unmount Notifications component', e);
}
};
XenoLib._ = XenoLib.DiscordUtils = WebpackModules.getByProps('bindAll', 'debounce');
XenoLib.loadData = (name, key, defaultData, returnNull) => {
try {
return XenoLib._.mergeWith(defaultData ? Utilities.deepclone(defaultData) : {}, BdApi.getData(name, key), (_, b) => {
if (XenoLib._.isArray(b)) return b;
});
} catch (err) {
Logger.err(name, 'Unable to load data: ', err);
if (returnNull) return null;
return Utilities.deepclone(defaultData);
}
};
XenoLib.getClass = (arg, thrw) => {
try {
const args = arg.split(' ');
return WebpackModules.getByProps(...args)[args[args.length - 1]];
} catch (e) {
if (thrw) throw e;
if (!XenoLib.getClass.__warns[arg] || Date.now() - XenoLib.getClass.__warns[arg] > 1000 * 60) {
Logger.stacktrace(`Failed to get class with props ${arg}`, e);
XenoLib.getClass.__warns[arg] = Date.now();
}
return '';
}
};
XenoLib.getSingleClass = (arg, thrw) => {
try {
return XenoLib.getClass(arg, thrw).split(' ')[0];
} catch (e) {
if (thrw) throw e;
if (!XenoLib.getSingleClass.__warns[arg] || Date.now() - XenoLib.getSingleClass.__warns[arg] > 1000 * 60) {
Logger.stacktrace(`Failed to get class with props ${arg}`, e);
XenoLib.getSingleClass.__warns[arg] = Date.now();
}
return '';
}
};
XenoLib.getClass.__warns = {};
XenoLib.getSingleClass.__warns = {};
XenoLib.createSmartPatcher = patcher => {
const createPatcher = patcher => {
return (moduleToPatch, functionName, callback, options = {}) => {
try {
var origDef = moduleToPatch[functionName];
} catch (_) {
return Logger.error(`Failed to patch ${functionName}`);
}
const unpatches = [];
unpatches.push(patcher(moduleToPatch, functionName, callback, options));
try {
if (origDef.__isBDFDBpatched && moduleToPatch.BDFDBpatch && typeof moduleToPatch.BDFDBpatch[functionName].originalMethod === 'function') {
/* do NOT patch a patch by ZLIb, that'd be bad and cause double items in context menus */
if ((Utilities.getNestedProp(ZeresPluginLibrary, 'Patcher.patches') || []).findIndex(e => e.module === moduleToPatch) !== -1 && moduleToPatch.BDFDBpatch[functionName].originalMethod.__originalFunction) return;
unpatches.push(patcher(moduleToPatch.BDFDBpatch[functionName], 'originalMethod', callback, options));
}
} catch (err) {
Logger.stacktrace('Failed to patch BDFDB patches', err);
}
return function unpatch() {
unpatches.forEach(e => e());
};
};
};
return Object.assign({}, patcher, {
before: createPatcher(patcher.before),
instead: createPatcher(patcher.instead),
after: createPatcher(patcher.after)
});
};
const Patcher = XenoLib.createSmartPatcher(Api.Patcher);
const LibrarySettings = XenoLib.loadData(config.info.name, 'settings', DefaultLibrarySettings);
PluginUtilities.addStyle(
'XenoLib-CSS',
`
.xenoLib-color-picker .xenoLib-button {
width: 34px;
min-height: 38px;
}
.xenoLib-color-picker .xenoLib-button:hover {
width: 128px;
}
.xenoLib-color-picker .xenoLib-button .${XenoLib.getSingleClass('recording text')} {
opacity: 0;
transform: translate3d(200%,0,0);
}
.xenoLib-color-picker .xenoLib-button:hover .${XenoLib.getSingleClass('recording text')} {
opacity: 1;
transform: translateZ(0);
}
.xenoLib-button-icon {
left: 50%;
top: 50%;
position: absolute;
margin-left: -12px;
margin-top: -8px;
width: 24px;
height: 24px;
opacity: 1;
transform: translateZ(0);
transition: opacity .2s ease-in-out,transform .2s ease-in-out,-webkit-transform .2s ease-in-out;
}
.xenoLib-button-icon.xenoLib-revert > svg {
width: 24px;
height: 24px;
}
.xenoLib-button-icon.xenoLib-revert {
margin-top: -12px;
}
.xenoLib-button:hover .xenoLib-button-icon {
opacity: 0;
transform: translate3d(-200%,0,0);
}
.xenoLib-notifications {
position: absolute;
color: white;
width: 100%;
min-height: 100%;
display: flex;
flex-direction: column;
z-index: 1000;
pointer-events: none;
font-size: 14px;
}
.xenoLib-notification {
min-width: 200px;
overflow: hidden;
}
.xenoLib-notification-content-wrapper {
padding: 22px 20px 0 20px;
}
.xenoLib-centering-bottomLeft .xenoLib-notification-content-wrapper:first-of-type, .xenoLib-centering-bottomMiddle .xenoLib-notification-content-wrapper:first-of-type, .xenoLib-centering-bottomRight .xenoLib-notification-content-wrapper:first-of-type {
padding: 0 20px 20px 20px;
}
.xenoLib-notification-content {
padding: 12px;
overflow: hidden;
background: #474747;
pointer-events: all;
position: relative;
width: 20vw;
white-space: break-spaces;
min-width: 330px;
}
.xenoLib-notification-loadbar {
position: absolute;
bottom: 0;
left: 0px;
width: auto;
background-image: linear-gradient(130deg,var(--grad-one),var(--grad-two));
height: 5px;
}
.xenoLib-notification-loadbar-user {
animation: fade-loadbar-animation 1.5s ease-in-out infinite;
}
@keyframes fade-loadbar-animation {
0% {
filter: brightness(75%)
}
50% {
filter: brightness(100%)
}
to {
filter: brightness(75%)
}
}
.xenoLib-notification-loadbar-striped:before {
content: "";
position: absolute;
width: 100%;
height: 100%;
border-radius: 5px;
background: linear-gradient(
-20deg,
transparent 35%,
var(--bar-color) 35%,
var(--bar-color) 70%,
transparent 70%
);
animation: shift 1s linear infinite;
background-size: 60px 100%;
box-shadow: inset 0 0px 1px rgba(0, 0, 0, 0.2),
inset 0 -2px 1px rgba(0, 0, 0, 0.2);
}
@keyframes shift {
to {
background-position: 60px 100%;
}
}
.xenoLib-notification-close {
float: right;
padding: 0;
height: unset;
opacity: .7;
}
.xenLib-notification-counter {
float: right;
margin-top: 2px;
}
.topMiddle-xenoLib {
top: 0;
left: 0;
right: 0;
margin-left: auto;
margin-right: auto;
}
.bottomMiddle-xenoLib {
bottom: 0;
left: 0;
right: 0;
margin-left: auto;
margin-right: auto;
}
.xenoLib-centering-topLeft, .xenoLib-centering-bottomLeft {
align-items: flex-start;
}
.xenoLib-centering-topMiddle, .xenoLib-centering-bottomMiddle {
align-items: center;
}
.xenoLib-centering-topRight, .xenoLib-centering-bottomRight {
align-items: flex-end;
}
.xenoLib-centering-bottomLeft, .xenoLib-centering-bottomMiddle, .xenoLib-centering-bottomRight {
flex-direction: column-reverse;
bottom: 0;
}
.XL-chl-p img{
width: unset !important;
}
.xenoLib-error-text {
padding-top: 5px;
}
`
);
XenoLib.joinClassNames = WebpackModules.getModule(e => e.default && e.default.default);
XenoLib.authorId = '239513071272329217';
XenoLib.supportServerId = '389049952732446731';
try {
const getUserAsync = WebpackModules.getByProps('getUser', 'acceptAgreements').getUser;
const requestUser = () =>
getUserAsync(XenoLib.authorId)
.then(user => (XenoLib.author = user))
.catch(() => setTimeout(requestUser, 1 * 60 * 1000));
if (UserStore.getUser(XenoLib.authorId)) XenoLib.author = UserStore.getUser(XenoLib.authorId);
else requestUser();
} catch (e) {
Logger.stacktrace('Failed to grab author object', e);
}
XenoLib.ReactComponents = {};
XenoLib.ReactComponents.ErrorBoundary = class XLErrorBoundary extends React.PureComponent {
constructor(props) {
super(props);
this.state = { hasError: false };
}
componentDidCatch(err, inf) {
Logger.err(`Error in ${this.props.label}, screenshot or copy paste the error above to Lighty for help.`);
this.setState({ hasError: true });
if (typeof this.props.onError === 'function') this.props.onError(err);
}
render() {
if (this.state.hasError) return null;
return this.props.children;
}
};
const deprecateFunction = (name, advice, ret = undefined) => () => (Logger.warn(`XenoLib.${name} is deprecated! ${advice}`), ret);
XenoLib.patchContext = deprecateFunction('patchContext', 'Do manual patching of context menus instead.');
const CTXMenu = WebpackModules.getByProps('default', 'MenuStyle');
class ContextMenuWrapper extends React.PureComponent {
constructor(props) {
super(props);
this.handleOnClose = this.handleOnClose.bind(this);
}
handleOnClose() {
ContextMenuActions.closeContextMenu();
if (this.props.target instanceof HTMLElement) this.props.target.focus();
}
render() {
return React.createElement(CTXMenu.default, { onClose: this.handleOnClose, id: 'xenolib-context' }, this.props.menu);
}
}
XenoLib.createSharedContext = (element, menuCreation) => {
if (element.__XenoLib_ContextMenus) {
element.__XenoLib_ContextMenus.push(menuCreation);
} else {
element.__XenoLib_ContextMenus = [menuCreation];
const oOnContextMenu = element.props.onContextMenu;
element.props.onContextMenu = e => (typeof oOnContextMenu === 'function' && oOnContextMenu(e), ContextMenuActions.openContextMenu(e, _ => React.createElement(XenoLib.ReactComponents.ErrorBoundary, { label: 'CTX Menu' }, React.createElement(ContextMenuWrapper, { menu: element.__XenoLib_ContextMenus.map(m => m()), ..._ }))));
}
};
const contextMenuItems = WebpackModules.find(m => m.MenuRadioItem && !m.default);
XenoLib.unpatchContext = deprecateFunction('unpatchContext', 'Manual patching needs manual unpatching');
XenoLib.createContextMenuItem = (label, action, id, options = {}) => (!contextMenuItems ? null : React.createElement(contextMenuItems.MenuItem, { label, id, action: () => (!options.noClose && ContextMenuActions.closeContextMenu(), action()), ...options }));
XenoLib.createContextMenuSubMenu = (label, children, id, options = {}) => (!contextMenuItems ? null : React.createElement(contextMenuItems.MenuItem, { label, children, id, ...options }));
XenoLib.createContextMenuGroup = (children, options) => (!contextMenuItems ? null : React.createElement(contextMenuItems.MenuGroup, { children, ...options }));
try {
XenoLib.ReactComponents.ButtonOptions = WebpackModules.getByProps('ButtonLink');
XenoLib.ReactComponents.Button = XenoLib.ReactComponents.ButtonOptions.default;
} catch (e) {
Logger.stacktrace('Error getting Button component', e);
}
function patchAddonCardAnyway(manualPatch) {
try {
if (patchAddonCardAnyway.patched) return;
patchAddonCardAnyway.patched = true;
const LinkClassname = XenoLib.joinClassNames(XenoLib.getClass('anchorUnderlineOnHover anchor'), XenoLib.getClass('anchor anchorUnderlineOnHover'), 'bda-author');
const handlePatch = (_this, _, ret) => {
if (!_this.props.addon || !_this.props.addon.plugin || typeof _this.props.addon.plugin.getAuthor().indexOf('Lighty') === -1) return;
const settingsProps = Utilities.findInReactTree(ret, e => e && e.className === 'plugin-settings');
if (settingsProps) delete settingsProps.id;
const author = Utilities.findInReactTree(ret, e => e && e.props && typeof e.props.className === 'string' && e.props.className.indexOf('bda-author') !== -1);
if (!author || typeof author.props.children !== 'string' || author.props.children.indexOf('Lighty') === -1) return;
const onClick = () => {
if (DiscordAPI.currentUser.id === XenoLib.authorId) return;
PrivateChannelActions.ensurePrivateChannel(DiscordAPI.currentUser.id, XenoLib.authorId).then(() => {
PrivateChannelActions.openPrivateChannel(DiscordAPI.currentUser.id, XenoLib.authorId);
LayerManager.popLayer();
});
};
if (author.props.children === 'Lighty') {
author.type = 'a';
author.props.className = LinkClassname;
author.props.onClick = onClick;
} else {
const idx = author.props.children.indexOf('Lighty');
const pre = author.props.children.slice(0, idx);
const post = author.props.children.slice(idx + 6);
author.props.children = [
pre,
React.createElement(
'a',
{
className: LinkClassname,
onClick
},
'Lighty'
),
post
];
delete author.props.onClick;
author.props.className = 'bda-author';
author.type = 'span';
}
let footerProps = Utilities.findInReactTree(ret, e => e && e.props && typeof e.props.className === 'string' && e.props.className.indexOf('bda-links') !== -1);
if (!footerProps) return;
footerProps = footerProps.props;
if (!Array.isArray(footerProps.children)) footerProps.children = [footerProps.children];
const findLink = name => Utilities.findInReactTree(footerProps.children, e => e && e.props && e.props.children === name);
const websiteLink = findLink('Website');
const sourceLink = findLink('Source');
const supportServerLink = findLink('Support Server');
footerProps.children = [];
if (websiteLink) {
const href = websiteLink.props.href;
delete websiteLink.props.href;
delete websiteLink.props.target;
websiteLink.props.onClick = () => window.open(href);
footerProps.children.push(websiteLink);
}
if (sourceLink) {
const href = sourceLink.props.href;
delete sourceLink.props.href;
delete sourceLink.props.target;
sourceLink.props.onClick = () => window.open(href);
footerProps.children.push(websiteLink ? ' | ' : null, sourceLink);
}
footerProps.children.push(websiteLink || sourceLink ? ' | ' : null, React.createElement('a', { className: 'bda-link bda-link-website', onClick: e => ContextMenuActions.openContextMenu(e, e => React.createElement(XenoLib.ReactComponents.ErrorBoundary, { label: 'Donate button CTX menu' }, React.createElement(ContextMenuWrapper, { menu: XenoLib.createContextMenuGroup([XenoLib.createContextMenuItem('Paypal', () => window.open('https://paypal.me/lighty13'), 'paypal'), XenoLib.createContextMenuItem('Ko-fi', () => window.open('https://ko-fi.com/lighty_'), 'kofi'), XenoLib.createContextMenuItem('Patreon', () => window.open('https://www.patreon.com/lightyp'), 'patreon')]), ...e }))) }, 'Donate'));
footerProps.children.push(' | ', supportServerLink || React.createElement('a', { className: 'bda-link bda-link-website', onClick: () => (LayerManager.popLayer(), InviteActions.acceptInviteAndTransitionToInviteChannel('NYvWdN5')) }, 'Support Server'));
footerProps.children.push(' | ', React.createElement('a', { className: 'bda-link bda-link-website', onClick: () => (_this.props.addon.plugin.showChangelog ? _this.props.addon.plugin.showChangelog() : Modals.showChangelogModal(_this.props.addon.plugin.getName() + ' Changelog', _this.props.addon.plugin.getVersion(), _this.props.addon.plugin.getChanges())) }, 'Changelog'));
footerProps = null;
};
async function patchRewriteCard() {
const component = [...ReactComponents.components.entries()].find(([_, e]) => e.component && e.component.prototype && e.component.prototype.reload && e.component.prototype.showSettings);
const AddonCard = component ? component[1] : await ReactComponents.getComponent('AddonCard', '.bda-slist > .ui-switch-item', e => e.prototype && e.prototype.reload && e.prototype.showSettings);
if (CancelledAsync) return;
const ContentColumn = await ReactComponents.getComponent('ContentColumn', '.content-column');
class PatchedAddonCard extends AddonCard.component {
render() {
const ret = super.render();
try {
/* did I mention I am Lighty? */
handlePatch(this, undefined, ret);
} catch (err) {
Logger.stacktrace('AddonCard patch', err);
}
return ret;
}
}
let firstRender = true;
Patcher.after(ContentColumn.component.prototype, 'render', (_, __, ret) => {
if (!LibrarySettings.addons.extra) return;
const list = Utilities.findInReactTree(ret, e => e && typeof e.className === 'string' && e.className.indexOf('bd-addon-list') !== -1);
if (Utilities.getNestedProp(list, 'children.0.props.children.type') !== AddonCard.component) return;
for (const item of list.children) {
const card = Utilities.getNestedProp(item, 'props.children');
if (!card) continue;
card.type = PatchedAddonCard;
}
if (!firstRender) return;
ret.key = DiscordModules.KeyGenerator();
firstRender = false;
});
if (manualPatch) return;
ContentColumn.forceUpdateAll();
AddonCard.forceUpdateAll();
}
patchRewriteCard();
} catch (e) {
Logger.stacktrace('Failed to patch V2C_*Card or AddonCard (BBD rewrite)', e);
}
}
if (LibrarySettings.addons.extra) patchAddonCardAnyway();
try {
XenoLib.ReactComponents.PluginFooter = class XLPluginFooter extends React.PureComponent {
render() {
if (LibrarySettings.addons.extra) return null;
return React.createElement(
'div',
{
style: {
display: 'flex'
}
},
React.createElement(
XenoLib.ReactComponents.Button,
{
style: {
flex: '2 1 auto'
},
onClick: this.props.showChangelog
},
'Changelog'
),
React.createElement(
XenoLib.ReactComponents.Button,
{
style: {
flex: '2 1 auto'
},
onClick: e => ContextMenuActions.openContextMenu(e, e => React.createElement(XenoLib.ReactComponents.ErrorBoundary, { label: 'Donate button CTX menu' }, React.createElement(ContextMenuWrapper, { menu: XenoLib.createContextMenuGroup([XenoLib.createContextMenuItem('Paypal', () => window.open('https://paypal.me/lighty13'), 'paypal'), XenoLib.createContextMenuItem('Ko-fi', () => window.open('https://ko-fi.com/lighty_'), 'kofi'), XenoLib.createContextMenuItem('Patreon', () => window.open('https://www.patreon.com/lightyp'), 'patreon')]), ...e })))
},
'Donate'
),
React.createElement(
XenoLib.ReactComponents.Button,
{
style: {
flex: '2 1 auto'
},
onClick: () => (LayerManager.popLayer(), InviteActions.acceptInviteAndTransitionToInviteChannel('NYvWdN5'))
},
'Support server'
)
);
}
};
} catch (err) {
Logger.stacktrace('Error creating plugin footer');
XenoLib.ReactComponents.PluginFooter = DiscordConstants.NOOP_NULL;
}
const TextElement = WebpackModules.getByDisplayName('Text');
/* shared between FilePicker and ColorPicker */
const MultiInputClassname = XenoLib.joinClassNames(Utilities.getNestedProp(DiscordClasses, 'BasicInputs.input.value'), XenoLib.getClass('multiInput'));
const MultiInputFirstClassname = XenoLib.getClass('multiInputFirst');
const MultiInputFieldClassname = XenoLib.getClass('multiInputField');
const ErrorMessageClassname = XenoLib.joinClassNames('xenoLib-error-text', XenoLib.getClass('errorMessage'), Utilities.getNestedProp(TextElement, 'Colors.ERROR'));
const ErrorClassname = XenoLib.getClass('input error');
try {
const DelayedCall = WebpackModules.getByProps('DelayedCall').DelayedCall;
const FsModule = require('fs');
/**
* @interface
* @name module:FilePicker
* @property {string} path
* @property {string} placeholder
* @property {Function} onChange
* @property {object} properties
* @property {bool} nullOnInvalid
* @property {bool} saveOnEnter
*/
XenoLib.ReactComponents.FilePicker = class FilePicker extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
multiInputFocused: false,
path: props.path,
error: null
};
XenoLib._.bindAll(this, ['handleOnBrowse', 'handleChange', 'checkInvalidDir']);
this.handleKeyDown = XenoLib._.throttle(this.handleKeyDown.bind(this), 500);
this.delayedCallVerifyPath = new DelayedCall(500, () => this.checkInvalidDir());
}
checkInvalidDir(doSave) {
FsModule.access(this.state.path, FsModule.constants.W_OK, error => {
const invalid = (error && error.message.match(/.*: (.*), access '/)[1]) || null;
this.setState({ error: invalid });
if (this.props.saveOnEnter && !doSave) return;
if (invalid) this.props.onChange(this.props.nullOnInvalid ? null : '');
else this.props.onChange(this.state.path);
});
}
handleOnBrowse() {
DiscordNative.fileManager.showOpenDialog({ title: this.props.title, properties: this.props.properties }).then(({ filePaths: [path] }) => {
if (path) this.handleChange(path);
});
}
handleChange(path) {
this.setState({ path });
this.delayedCallVerifyPath.delay();
}
handleKeyDown(e) {
if (!this.props.saveOnEnter || e.which !== DiscordConstants.KeyboardKeys.ENTER) return;
this.checkInvalidDir(true);
}
render() {
const n = {};
n[DiscordClasses.BasicInputs.focused] = this.state.multiInputFocused;
n[ErrorClassname] = !!this.state.error;
return React.createElement(
'div',
{ className: DiscordClasses.BasicInputs.inputWrapper, style: { width: '100%' } },
React.createElement(
'div',
{ className: XenoLib.joinClassNames(MultiInputClassname, n) },
React.createElement(DiscordModules.Textbox, {
value: this.state.path,
placeholder: this.props.placeholder,
onChange: this.handleChange,
onFocus: () => this.setState({ multiInputFocused: true }),
onBlur: () => this.setState({ multiInputFocused: false }),
onKeyDown: this.handleKeyDown,
autoFocus: false,
className: MultiInputFirstClassname,
inputClassName: MultiInputFieldClassname
}),
React.createElement(XenoLib.ReactComponents.Button, { onClick: this.handleOnBrowse, color: (!!this.state.error && XenoLib.ReactComponents.ButtonOptions.ButtonColors.RED) || XenoLib.ReactComponents.ButtonOptions.ButtonColors.GREY, look: XenoLib.ReactComponents.ButtonOptions.ButtonLooks.GHOST, size: XenoLib.ReactComponents.Button.Sizes.MEDIUM }, 'Browse')
),
!!this.state.error && React.createElement('div', { className: ErrorMessageClassname }, 'Error: ', this.state.error)
);
}
};
} catch (e) {
Logger.stacktrace('Failed to create FilePicker component', e);
}
/**
* @param {string} name - name label of the setting
* @param {string} note - help/note to show underneath or above the setting
* @param {string} value - current hex color
* @param {callable} onChange - callback to perform on setting change, callback receives hex string
* @param {object} [options] - object of options to give to the setting
* @param {boolean} [options.disabled=false] - should the setting be disabled
* @param {Array<number>} [options.colors=presetColors] - preset list of colors
* @author Zerebos, from his library ZLibrary
*/
const FormItem = WebpackModules.getByDisplayName('FormItem');
const DeprecatedModal = WebpackModules.getByDisplayName('DeprecatedModal');
const ColorPickerComponent = (_ => {
try {
const GuildSettingsRoles = WebpackModules.getByDisplayName('FluxContainer(GuildSettingsRoles)');
const RoleSettingsContainer = GuildSettingsRoles.prototype.render.call({
memoizedGetStateFromStores: _ => { }
}).type.prototype.renderRoleSettings.call({
props: {
guild: {
id: '',
isOwner: _ => false
}
},
getSelectedRole: _ => ({ id: '' }),
renderHeader: _ => null
});
const RoleSettings = Utilities.findInReactTree(RoleSettingsContainer, e => e && e.type && e.type.displayName === "GuildRoleSettings").type.prototype.renderColorPicker.call({
props: {
role: {}
}
});
return RoleSettings.props.children.type;
} catch (err) {
Logger.stacktrace('Failed to get lazy colorpicker, unsurprisingly', err);
return _ => null;
}
})()
class ColorPickerModal extends React.PureComponent {
constructor(props) {
super(props);
this.state = { value: props.value };
XenoLib._.bindAll(this, ['handleChange']);
}
handleChange(value) {
this.setState({ value });
this.props.onChange(ColorConverter.int2hex(value));
}
render() {
return React.createElement(
DeprecatedModal,
{ tag: 'form', onSubmit: this.handleSubmit, size: '' },
React.createElement(
DeprecatedModal.Content,
{},
React.createElement(
FormItem,
{ className: XenoLib.joinClassNames(DiscordClasses.Margins.marginTop20.value, DiscordClasses.Margins.marginBottom20.value) },
React.createElement(ColorPickerComponent, {
defaultColor: this.props.defaultColor,
colors: [16711680, 16746496, 16763904, 13434624, 65314, 65484, 61183, 43775, 26367, 8913151, 16711918, 16711782, 11730944, 11755264, 11767552, 9417472, 45848, 45967, 42931, 30643, 18355, 6226099, 11731111, 11731015],
value: this.state.value,
onChange: this.handleChange
})
)
)
);
}
}
const ExtraButtonClassname = XenoLib.joinClassNames('xenoLib-button', XenoLib.getClass('recording button'));
const TextClassname = XenoLib.getClass('recording text');
const DropperIcon = React.createElement('svg', { width: 16, height: 16, viewBox: '0 0 16 16' }, React.createElement('path', { d: 'M14.994 1.006C13.858-.257 11.904-.3 10.72.89L8.637 2.975l-.696-.697-1.387 1.388 5.557 5.557 1.387-1.388-.697-.697 1.964-1.964c1.13-1.13 1.3-2.985.23-4.168zm-13.25 10.25c-.225.224-.408.48-.55.764L.02 14.37l1.39 1.39 2.35-1.174c.283-.14.54-.33.765-.55l4.808-4.808-2.776-2.776-4.813 4.803z', fill: 'currentColor' }));
const ClockReverseIcon = React.createElement('svg', { width: 16, height: 16, viewBox: '0 0 24 24' }, React.createElement('path', { d: 'M13,3 C8.03,3 4,7.03 4,12 L1,12 L4.89,15.89 L4.96,16.03 L9,12 L6,12 C6,8.13 9.13,5 13,5 C16.87,5 20,8.13 20,12 C20,15.87 16.87,19 13,19 C11.07,19 9.32,18.21 8.06,16.94 L6.64,18.36 C8.27,19.99 10.51,21 13,21 C17.97,21 22,16.97 22,12 C22,7.03 17.97,3 13,3 L13,3 Z M12,8 L12,13 L16.28,15.54 L17,14.33 L13.5,12.25 L13.5,8 L12,8 L12,8 Z', fill: 'currentColor' }));
class ColorPicker extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
error: null,
value: props.value,
multiInputFocused: false
};
XenoLib._.bindAll(this, ['handleChange', 'handleColorPicker', 'handleReset']);
}
handleChange(value) {
if (!value.length) {
this.state.error = 'You must input a hex string';
} else if (!ColorConverter.isValidHex(value)) {
this.state.error = 'Invalid hex string';
} else {
this.state.error = null;
}
this.setState({ value });
this.props.onChange(!value.length || !ColorConverter.isValidHex(value) ? this.props.defaultColor : value);
}
handleColorPicker() {
const modalId = ModalStack.push(e => React.createElement(XenoLib.ReactComponents.ErrorBoundary, { label: 'color picker modal', onError: () => ModalStack.popWithKey(modalId) }, React.createElement(ColorPickerModal, { ...e, defaultColor: ColorConverter.hex2int(this.props.defaultColor), value: ColorConverter.hex2int(this.props.value), onChange: this.handleChange })));
}
handleReset() {
this.handleChange(this.props.defaultColor);
}
render() {
const n = {};
n[DiscordClasses.BasicInputs.focused] = this.state.multiInputFocused;
n[ErrorClassname] = !!this.state.error;
return React.createElement(
'div',
{ className: XenoLib.joinClassNames(DiscordClasses.BasicInputs.inputWrapper.value, 'xenoLib-color-picker'), style: { width: '100%' } },
React.createElement(
'div',
{ className: XenoLib.joinClassNames(MultiInputClassname, n) },
React.createElement('div', {
className: XenoLib.ReactComponents.Button.Sizes.SMALL,
style: {
backgroundColor: this.state.value,
height: 38
}
}),
React.createElement(DiscordModules.Textbox, {
value: this.state.value,
placeholder: 'Hex color',
onChange: this.handleChange,
onFocus: () => this.setState({ multiInputFocused: true }),
onBlur: () => this.setState({ multiInputFocused: false }),
autoFocus: false,
className: MultiInputFirstClassname,
inputClassName: MultiInputFieldClassname
}),
React.createElement(
XenoLib.ReactComponents.Button,
{
onClick: this.handleColorPicker,
color: (!!this.state.error && XenoLib.ReactComponents.ButtonOptions.ButtonColors.RED) || XenoLib.ReactComponents.ButtonOptions.ButtonColors.GREY,
look: XenoLib.ReactComponents.ButtonOptions.ButtonLooks.GHOST,
size: XenoLib.ReactComponents.Button.Sizes.MIN,
className: ExtraButtonClassname
},
React.createElement('span', { className: TextClassname }, 'Color picker'),
React.createElement(
'span',
{
className: 'xenoLib-button-icon'
},
DropperIcon
)
),
React.createElement(
XenoLib.ReactComponents.Button,
{
onClick: this.handleReset,
color: (!!this.state.error && XenoLib.ReactComponents.ButtonOptions.ButtonColors.RED) || XenoLib.ReactComponents.ButtonOptions.ButtonColors.GREY,
look: XenoLib.ReactComponents.ButtonOptions.ButtonLooks.GHOST,
size: XenoLib.ReactComponents.Button.Sizes.MIN,
className: ExtraButtonClassname
},
React.createElement('span', { className: TextClassname }, 'Reset'),
React.createElement(
'span',
{
className: 'xenoLib-button-icon xenoLib-revert'
},
ClockReverseIcon
)
)
),
!!this.state.error && React.createElement('div', { className: ErrorMessageClassname }, 'Error: ', this.state.error)
);
}
}
XenoLib.Settings = {};
XenoLib.Settings.FilePicker = class FilePickerSettingField extends Settings.SettingField {
constructor(name, note, value, onChange, options = { properties: ['openDirectory', 'createDirectory'], placeholder: 'Path to folder', defaultPath: '' }) {
super(name, note, onChange, XenoLib.ReactComponents.FilePicker || class b { }, {
onChange: reactElement => path => {
this.onChange(path ? path : options.defaultPath);
},
path: value,
nullOnInvalid: true,
...options
});
}
};
XenoLib.Settings.ColorPicker = class ColorPickerSettingField extends Settings.SettingField {
constructor(name, note, value, onChange, options = {}) {
super(name, note, onChange, ColorPicker, {
disabled: options.disabled ? true : false,
onChange: reactElement => color => {
this.onChange(color);
},
defaultColor: typeof options.defaultColor !== 'undefined' ? options.defaultColor : ColorConverter.int2hex(DiscordConstants.DEFAULT_ROLE_COLOR),
value
});
}
};
XenoLib.Settings.PluginFooter = class PluginFooterField extends Settings.SettingField {
constructor(showChangelog) {
super('', '', DiscordConstants.NOOP, XenoLib.ReactComponents.PluginFooter, {
showChangelog
});
}
};
XenoLib.changeName = (currentName, newName) => {
try {
const path = require('path');
const fs = require('fs');
const pluginsFolder = path.dirname(currentName);
const pluginName = path.basename(currentName).match(/^[^\.]+/)[0];
if (pluginName === newName) return true;
const wasEnabled = BdApi.Plugins && BdApi.Plugins.isEnabled ? BdApi.Plugins.isEnabled(pluginName) : global.pluginCookie && pluginCookie[pluginName];
fs.accessSync(currentName, fs.constants.W_OK | fs.constants.R_OK);
const files = fs.readdirSync(pluginsFolder);
files.forEach(file => {
if (!file.startsWith(pluginName) || file.startsWith(newName) || file.indexOf('.plugin.js') !== -1) return;
fs.renameSync(path.resolve(pluginsFolder, file), path.resolve(pluginsFolder, `${newName}${file.match(new RegExp(`^${pluginName}(.*)`))[1]}`));
});
fs.renameSync(currentName, path.resolve(pluginsFolder, `${newName}.plugin.js`));
XenoLib.Notifications.success(`[**XenoLib**] \`${pluginName}\` file has been renamed to \`${newName}\``);
if ((!BdApi.Plugins || !BdApi.Plugins.isEnabled || !BdApi.Plugins.enable) && (!global.pluginCookie || !global.pluginModule)) Modals.showAlertModal('Plugin has been renamed', 'Plugin has been renamed, but your client mod has a missing feature, as such, the plugin could not be enabled (if it even was enabled).');
else {
if (!wasEnabled) return;
setTimeout(() => (BdApi.Plugins && BdApi.Plugins.enable ? BdApi.Plugins.enable(newName) : pluginModule.enablePlugin(newName)), 1000); /* /shrug */
}
} catch (e) {
Logger.stacktrace('There has been an issue renaming a plugin', e);
}
};
const FancyParser = (() => {
const ParsersModule = WebpackModules.getByProps('astParserFor', 'parse');
try {
const DeepClone = WebpackModules.find(m => m.default && m.default.toString().indexOf('/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(') !== -1 && !m.useVirtualizedAnchor).default;
const ReactParserRules = WebpackModules.find(m => m.default && m.default.toString().search(/function\(\){return \w}$/) !== -1).default; /* thanks Zere for not fixing the bug ._. */
const FANCY_PANTS_PARSER_RULES = DeepClone([WebpackModules.getByProps('RULES').RULES, ReactParserRules()]);
const { defaultRules } = WebpackModules.getByProps('defaultParse');
FANCY_PANTS_PARSER_RULES.image = defaultRules.image;
FANCY_PANTS_PARSER_RULES.link = defaultRules.link;
return ParsersModule.reactParserFor(FANCY_PANTS_PARSER_RULES);
} catch (e) {
Logger.stacktrace('Failed to create special parser', e);
try {
return ParsersModule.parse;
} catch (e) {
Logger.stacktrace('Failed to get even basic parser', e);
return e => e;
}
}
})();
const AnchorClasses = WebpackModules.getByProps('anchor', 'anchorUnderlineOnHover') || {};
const EmbedVideo = (() => {
try {
return WebpackModules.getByProps('EmbedVideo').EmbedVideo;
} catch (e) {
Logger.stacktrace('Failed to get EmbedVideo!', e);
return DiscordConstants.NOOP_NULL;
}
})();
const VideoComponent = (() => {
try {
const ret = new (WebpackModules.getByDisplayName('MediaPlayer'))({}).render();
const vc = Utilities.findInReactTree(ret, e => e && e.props && typeof e.props.className === 'string' && e.props.className.indexOf('video-8eMOth') !== -1);
return vc.type;
} catch (e) {
Logger.stacktrace('Failed to get the video component', e);
return DiscordConstants.NOOP_NULL;
}
})();
const ComponentRenderers = WebpackModules.getByProps('renderVideoComponent') || {};
/* MY CHANGELOG >:C */
XenoLib.showChangelog = (title, version, changelog, footer) => {
const ChangelogClasses = DiscordClasses.Changelog;
const items = [];
let isFistType = true;
for (let i = 0; i < changelog.length; i++) {
const item = changelog[i];
switch (item.type) {
case 'image':
items.push(React.createElement('img', { alt: '', src: item.src, width: item.width || 451, height: item.height || 254 }));
continue;
case 'video':
items.push(React.createElement(VideoComponent, { src: item.src, poster: item.thumbnail, width: item.width || 451, height: item.height || 254, loop: !0, muted: !0, autoPlay: !0, className: ChangelogClasses.video }));
continue;
case 'youtube':
items.push(React.createElement(EmbedVideo, { className: ChangelogClasses.video, allowFullScreen: !1, href: `https://youtu.be/${item.youtube_id}`, thumbnail: { url: `https://i.ytimg.com/vi/${item.youtube_id}/maxresdefault.jpg`, width: item.width || 451, height: item.height || 254 }, video: { url: `https://www.youtube.com/embed/${item.youtube_id}?vq=large&rel=0&controls=0&showinfo=0`, width: item.width || 451, height: item.height || 254 }, width: item.width || 451, height: item.height || 254, renderVideoComponent: ComponentRenderers.renderVideoComponent || DiscordConstants.NOOP_NULL, renderImageComponent: ComponentRenderers.renderImageComponent || DiscordConstants.NOOP_NULL, renderLinkComponent: ComponentRenderers.renderMaskedLinkComponent || DiscordConstants.NOOP_NULL }));
continue;
case 'description':
items.push(React.createElement('p', {}, FancyParser(item.content)));
continue;
default:
const logType = ChangelogClasses[item.type] || ChangelogClasses.added;
items.push(React.createElement('h1', { className: XenoLib.joinClassNames(logType.value, { [ChangelogClasses.marginTop.value]: item.marginTop || isFistType }) }, item.title));
items.push(
React.createElement(
'ul',
{ className: 'XL-chl-p' },
item.items.map(e =>
React.createElement(
'li',
{},
React.createElement(
'p',
{},
Array.isArray(e)
? e.map(e =>
Array.isArray(e)
? React.createElement(
'ul',
{},
e.map(e => React.createElement('li', {}, FancyParser(e)))
)
: FancyParser(e)
)
: FancyParser(e)
)
)
)
)
);
isFistType = false;
}
}
const renderFooter = () => ['Need support? ', React.createElement('a', { className: XenoLib.joinClassNames(AnchorClasses.anchor, AnchorClasses.anchorUnderlineOnHover), onClick: () => (LayerManager.popLayer(), ModalStack.pop(), InviteActions.acceptInviteAndTransitionToInviteChannel('NYvWdN5')) }, 'Join my support server'), '! Or consider donating via ', React.createElement('a', { className: XenoLib.joinClassNames(AnchorClasses.anchor, AnchorClasses.anchorUnderlineOnHover), onClick: () => window.open('https://paypal.me/lighty13') }, 'Paypal'), ', ', React.createElement('a', { className: XenoLib.joinClassNames(AnchorClasses.anchor, AnchorClasses.anchorUnderlineOnHover), onClick: () => window.open('https://ko-fi.com/lighty_') }, 'Ko-fi'), ', ', React.createElement('a', { className: XenoLib.joinClassNames(AnchorClasses.anchor, AnchorClasses.anchorUnderlineOnHover), onClick: () => window.open('https://www.patreon.com/lightyp') }, 'Patreon'), '!'];
ModalStack.push(props => React.createElement(XenoLib.ReactComponents.ErrorBoundary, { label: 'Changelog', onError: () => props.onClose() }, React.createElement(ChangelogModal, { className: ChangelogClasses.container, selectable: true, onScroll: _ => _, onClose: _ => _, renderHeader: () => React.createElement(FlexChild.Child, { grow: 1, shrink: 1 }, React.createElement(Titles.default, { tag: Titles.Tags.H4 }, title), React.createElement(TextElement, { size: TextElement.Sizes.SIZE_12, className: ChangelogClasses.date }, `Version ${version}`)), renderFooter: () => React.createElement(FlexChild.Child, { gro: 1, shrink: 1 }, React.createElement(TextElement, { size: TextElement.Sizes.SIZE_12 }, footer ? (typeof footer === 'string' ? FancyParser(footer) : footer) : renderFooter())), children: items, ...props })));
};
/* https://github.com/react-spring/zustand
* MIT License
*
* Copyright (c) 2019 Paul Henschel
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
XenoLib.zustand = createState => {
var state;
var listeners = new Set();
const setState = partial => {
var partialState = typeof partial === 'function' ? partial(state) : partial;
if (partialState !== state) {
state = Object.assign({}, state, partialState);
listeners.forEach(function (listener) {
return listener();
});
}
};
const getState = () => state;
const getSubscriber = (listener, selector, equalityFn) => {
if (selector === void 0) selector = getState;
if (equalityFn === void 0) equalityFn = Object.is;
return { currentSlice: selector(state), equalityFn: equalityFn, errored: false, listener: listener, selector: selector, unsubscribe: function unsubscribe() { } };
};
var subscribe = function subscribe(subscriber) {
function listener() {
// Selector or equality function could throw but we don't want to stop
// the listener from being called.
// https://github.com/react-spring/zustand/pull/37
try {
var newStateSlice = subscriber.selector(state);
if (!subscriber.equalityFn(subscriber.currentSlice, newStateSlice)) subscriber.listener((subscriber.currentSlice = newStateSlice));
} catch (error) {
subscriber.errored = true;
subscriber.listener(null, error);
}
}
listeners.add(listener);
return () => listeners.delete(listener);
};
const apiSubscribe = (listener, selector, equalityFn) => subscribe(getSubscriber(listener, selector, equalityFn));
const destroy = () => listeners.clear();
const useStore = (selector, equalityFn) => {
if (selector === void 0) selector = getState;
if (equalityFn === void 0) equalityFn = Object.is;
var forceUpdate = React.useReducer(c => c + 1, 0)[1];
var subscriberRef = React.useRef();
if (!subscriberRef.current) {
subscriberRef.current = getSubscriber(forceUpdate, selector, equalityFn);
subscriberRef.current.unsubscribe = subscribe(subscriberRef.current);
}
var subscriber = subscriberRef.current;
var newStateSlice;
var hasNewStateSlice = false; // The selector or equalityFn need to be called during the render phase if
// they change. We also want legitimate errors to be visible so we re-run
// them if they errored in the subscriber.
if (subscriber.selector !== selector || subscriber.equalityFn !== equalityFn || subscriber.errored) {
// Using local variables to avoid mutations in the render phase.
newStateSlice = selector(state);
hasNewStateSlice = !equalityFn(subscriber.currentSlice, newStateSlice);
} // Syncing changes in useEffect.
React.useLayoutEffect(function () {
if (hasNewStateSlice) subscriber.currentSlice = newStateSlice;
subscriber.selector = selector;
subscriber.equalityFn = equalityFn;
subscriber.errored = false;
});
React.useLayoutEffect(() => subscriber.unsubscribe, []);
return hasNewStateSlice ? newStateSlice : subscriber.currentSlice;
};
const api = { setState: setState, getState: getState, subscribe: apiSubscribe, destroy: destroy };
state = createState(setState, getState, api);
return [useStore, api];
};
/* NOTIFICATIONS START */
let UPDATEKEY = {};
try {
const DeepEqualityCheck = (content1, content2) => {
if (typeof content1 !== typeof content2) return false;
const isCNT1HTML = content1 instanceof HTMLElement;
const isCNT2HTML = content2 instanceof HTMLElement;
if (isCNT1HTML !== isCNT2HTML) return false;
else if (isCNT1HTML) return content1.isEqualNode(content2);
if (content1 !== content2) {
if (Array.isArray(content1)) {
if (content1.length !== content2.length) return false;
for (const [index, item] of content1.entries()) {
if (!DeepEqualityCheck(item, content2[index])) return false;
}
} else if (typeof content1 === 'object') {
if (content1.type) {
if (typeof content1.type !== typeof content2.type) return false;
if (content1.type !== content2.type) return false;
}
if (typeof content1.props !== typeof content2.props) return false;
if (content1.props) {
if (Object.keys(content1.props).length !== Object.keys(content2.props).length) return false;
for (const prop in content1.props) {
if (!DeepEqualityCheck(content1.props[prop], content2.props[prop])) return false;
}
}
} else return false;
}
return true;
};
const [useStore, api] = global.FCAPI && global.FCAPI.NotificationStore ? global.FCAPI.NotificationStore : XenoLib.zustand(e => ({ data: [] }));
const defaultOptions = {
loading: false,
progress: -1,
channelId: undefined,
timeout: 3500,
color: '#2196f3',
onLeave: DiscordConstants.NOOP
};
const utils = {
success(content, options = {}) {
return this.show(content, Object.assign({ color: '#43b581' }, options));
},
info(content, options = {}) {
return this.show(content, Object.assign({ color: '#4a90e2' }, options));
},
warning(content, options = {}) {
return this.show(content, Object.assign({ color: '#ffa600' }, options));
},
danger(content, options = {}) {
return this.show(content, Object.assign({ color: '#f04747' }, options));
},
error(content, options = {}) {
return this.danger(content, options);
},
/**
* @param {string|HTMLElement|React} content - Content to display. If it's a string, it'll be formatted with markdown, including URL support [like this](https://google.com/)
* @param {object} options
* @param {string} [options.channelId] Channel ID if content is a string which gets formatted, and you want to mention a role for example.
* @param {Number} [options.timeout] Set to 0 to keep it permanently until user closes it, or if you want a progress bar
* @param {Boolean} [options.loading] Makes the bar animate differently instead of fading in and out slowly
* @param {Number} [options.progress] 0-100, -1 sets it to 100%, setting it to 100% closes the notification automatically
* @param {string} [options.color] Bar color
* @param {string} [options.allowDuplicates] By default, notifications that are similar get grouped together, use true to disable that
* @param {function} [options.onLeave] Callback when notification is leaving
* @return {Number} - Notification ID. Store this if you plan on force closing it, changing its content or want to set the progress
*/
show(content, options = {}) {
let id = null;
options = Object.assign(Utilities.deepclone(defaultOptions), options);
api.setState(state => {
if (!options.allowDuplicates) {
const notif = state.data.find(n => DeepEqualityCheck(n.content, content) && n.timeout === options.timeout && !n.leaving);
if (notif) {
id = notif.id;
Dispatcher.dirtyDispatch({ type: 'XL_NOTIFS_DUPLICATE', id: notif.id });
return state;
}
}
if (state.data.length >= 100) return state;
do {
id = Math.floor(4294967296 * Math.random());
} while (state.data.findIndex(n => n.id === id) !== -1);
return { data: [].concat(state.data, [{ content, ...options, id }]) };
});
return id;
},
remove(id) {
Dispatcher.dirtyDispatch({ type: 'XL_NOTIFS_REMOVE', id });
},
/**
* @param {Number} id Notification ID
* @param {object} options
* @param {string} [options.channelId] Channel ID if content is a string which gets formatted, and you want to mention a role for example.
* @param {Boolean} [options.loading] Makes the bar animate differently instead of fading in and out slowly
* @param {Number} [options.progress] 0-100, -1 sets it to 100%, setting it to 100% closes the notification automatically
* @param {string} [options.color] Bar color
* @param {function} [options.onLeave] Callback when notification is leaving
*/
update(id, options) {
delete options.id;
api.setState(state => {
const idx = state.data.findIndex(n => n.id === id);
if (idx === -1) return state;
state.data[idx] = Object.assign(state.data[idx], options);
return state;
});
Dispatcher.dirtyDispatch({ type: 'XL_NOTIFS_UPDATE', id, ...options });
},
exists(id) {
return api.getState().data.findIndex(e => e.id === id && !e.leaving) !== -1;
}
};
XenoLib.Notifications = utils;
const ReactSpring = WebpackModules.getByProps('useTransition');
const BadgesModule = WebpackModules.getByProps('NumberBadge');
const CloseButton = React.createElement('svg', { width: 16, height: 16, viewBox: '0 0 24 24' }, React.createElement('path', { d: 'M18.4 4L12 10.4L5.6 4L4 5.6L10.4 12L4 18.4L5.6 20L12 13.6L18.4 20L20 18.4L13.6 12L20 5.6L18.4 4Z', fill: 'currentColor' }));
class Notification extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
closeFast: false /* close button pressed, XL_NOTIFS_REMOVE dispatch */,
offscreen: false /* don't do anything special if offscreen, not timeout */,
counter: 1 /* how many times this notification was shown */,
resetBar: false /* reset bar to 0 in the event counter goes up */,
hovered: false,
leaving: true /* prevent hover events from messing up things */,
loading: props.loading /* loading animation, enable progress */,
progress: props.progress /* -1 means undetermined */,
content: props.content,
contentParsed: this.parseContent(props.content, props.channelId),
color: props.color
};
this._contentRef = null;
this._ref = null;
this._animationCancel = DiscordConstants.NOOP;
this._oldOffsetHeight = 0;
this._initialProgress = !this.props.timeout ? (this.state.loading && this.state.progress !== -1 ? this.state.progress : 100) : 0;
XenoLib._.bindAll(this, ['closeNow', 'handleDispatch', '_setContentRef']);
this.handleResizeEvent = XenoLib._.throttle(this.handleResizeEvent.bind(this), 100);
this.resizeObserver = new ResizeObserver(this.handleResizeEvent);
this._timeout = props.timeout;
}
componentDidMount() {
this._unsubscribe = api.subscribe(_ => this.checkOffScreen());
window.addEventListener('resize', this.handleResizeEvent);
Dispatcher.subscribe('XL_NOTIFS_DUPLICATE', this.handleDispatch);
Dispatcher.subscribe('XL_NOTIFS_REMOVE', this.handleDispatch);
Dispatcher.subscribe('XL_NOTIFS_UPDATE', this.handleDispatch);
Dispatcher.subscribe('XL_NOTIFS_ANIMATED', this.handleDispatch);
Dispatcher.subscribe('XL_NOTIFS_SETTINGS_UPDATE', this.handleDispatch);
}
componentWillUnmount() {
this._unsubscribe();
window.window.removeEventListener('resize', this.handleResizeEvent);
Dispatcher.unsubscribe('XL_NOTIFS_DUPLICATE', this.handleDispatch);
Dispatcher.unsubscribe('XL_NOTIFS_REMOVE', this.handleDispatch);
Dispatcher.unsubscribe('XL_NOTIFS_UPDATE', this.handleDispatch);
Dispatcher.unsubscribe('XL_NOTIFS_ANIMATED', this.handleDispatch);
Dispatcher.unsubscribe('XL_NOTIFS_SETTINGS_UPDATE', this.handleDispatch);
this.resizeObserver.disconnect();
this.resizeObserver = null; /* no mem leaks plz */
this._ref = null;
this._contentRef = null;
}
handleDispatch(e) {
if (this.state.leaving || this.state.closeFast) return;
if (e.type === 'XL_NOTIFS_SETTINGS_UPDATE') {
if (e.key !== UPDATEKEY) return;
this._animationCancel();
this.forceUpdate();
return;
}
if (e.type === 'XL_NOTIFS_ANIMATED') this.checkOffScreen();
if (e.id !== this.props.id) return;
const { content, channelId, loading, progress, color } = e;
const { content: curContent, channelId: curChannelId, loading: curLoading, progress: curProgress, color: curColor } = this.state;
switch (e.type) {
case 'XL_NOTIFS_REMOVE':
this.closeNow();
break;
case 'XL_NOTIFS_DUPLICATE':
this._animationCancel();
this.setState({ counter: this.state.counter + 1, resetBar: !!this.props.timeout, closeFast: false });
break;
case 'XL_NOTIFS_UPDATE':
if (!this.state.initialAnimDone) {
this.state.content = content || curContent;
this.state.channelId = channelId || curChannelId;
this.state.contentParsed = this.parseContent(content || curContent, channelId || curChannelId);
if (typeof loading !== 'undefined') this.state.loading = loading;
if (typeof progress !== 'undefined') this.state.progress = progress;
this.state.color = color || curColor;
return;
}
this._animationCancel();
this.setState({
content: content || curContent,
channelId: channelId || curChannelId,
contentParsed: this.parseContent(content || curContent, channelId || curChannelId),
loading: typeof loading !== 'undefined' ? loading : curLoading,
progress: typeof progress !== 'undefined' ? progress : curProgress,
color: color || curColor
});
break;
}
}
parseContent(content, channelId) {
if (typeof content === 'string') return FancyParser(content, true, { channelId });
else if (content instanceof Element) return ReactTools.createWrappedElement(content);
else return content;
}
checkOffScreen() {
if (this.state.leaving || !this._contentRef) return;
const bcr = this._contentRef.getBoundingClientRect();
if (Math.floor(bcr.bottom) - 1 > Structs.Screen.height || Math.ceil(bcr.top) + 1 < 0) {
if (!this.state.offscreen) {
this._animationCancel();
this.setState({ offscreen: true });
}
} else if (this.state.offscreen) {
this._animationCancel();
this.setState({ offscreen: false });
}
}
closeNow() {
if (this.state.closeFast) return;
this.resizeObserver.disconnect();
this._animationCancel();
api.setState(state => {
const dt = state.data.find(m => m.id === this.props.id);
if (dt) dt.leaving = true;
return { data: state.data };
});
this.setState({ closeFast: true });
}
handleResizeEvent() {
if (this._oldOffsetHeight !== this._contentRef.offsetHeight) {
this._animationCancel();
this.forceUpdate();
}
}
_setContentRef(ref) {
if (!ref) return;
this._contentRef = ref;
this.resizeObserver.observe(ref);
}
render() {
const config = { duration: 200 };
if (this._contentRef) this._oldOffsetHeight = this._contentRef.offsetHeight;
return React.createElement(
ReactSpring.Spring,
{
native: true,
from: { opacity: 0, height: 0, progress: this._initialProgress, loadbrightness: 1 },
to: async (next, cancel) => {
this.state.leaving = false;
this._animationCancel = cancel;
if (this.state.offscreen) {
if (this.state.closeFast) {
this.state.leaving = true;
await next({ opacity: 0, height: 0 });
api.setState(state => ({ data: state.data.filter(n => n.id !== this.props.id) }));
return;
}
await next({ opacity: 1, height: this._contentRef.offsetHeight, loadbrightness: 1 });
if (this.props.timeout) {
await next({ progress: 0 });
} else {
if (this.state.loading && this.state.progress !== -1) {
await next({ progress: 0 });
} else {
await next({ progress: 100 });
}
}
return;
}
const isSettingHeight = this._ref.offsetHeight !== this._contentRef.offsetHeight;
await next({ opacity: 1, height: this._contentRef.offsetHeight });
if (isSettingHeight) Dispatcher.dirtyDispatch({ type: 'XL_NOTIFS_ANIMATED' });
this.state.initialAnimDone = true;
if (this.state.resetBar || (this.state.hovered && LibrarySettings.notifications.timeoutReset)) {
await next({ progress: 0 }); /* shit gets reset */
this.state.resetBar = false;
}
if (!this.props.timeout && !this.state.closeFast) {
if (!this.state.loading) {
await next({ progress: 100 });
} else {
await next({ loadbrightness: 1 });
if (this.state.progress === -1) await next({ progress: 100 });
else await next({ progress: this.state.progress });
}
if (this.state.progress < 100 || !this.state.loading) return;
}
if (this.state.hovered && !this.state.closeFast) return;
if (!this.state.closeFast && !LibrarySettings.notifications.timeoutReset) this._startProgressing = Date.now();
await next({ progress: 100 });
if (this.state.hovered && !this.state.closeFast) return; /* race condition: notif is hovered, but it continues and closes! */
this.state.leaving = true;
if (!this.state.closeFast) {
api.setState(state => {
const dt = state.data.find(m => m.id === this.props.id);
if (dt) dt.leaving = true;
return { data: state.data };
});
}
this.props.onLeave();
await next({ opacity: 0, height: 0 });
api.setState(state => ({ data: state.data.filter(n => n.id !== this.props.id) }));
},
config: key => {
if (key === 'progress') {
let duration = this._timeout;
if (this.state.closeFast || !this.props.timeout || this.state.resetBar || this.state.hovered) duration = 150;
if (this.state.offscreen) duration = 0; /* don't animate at all */
return { duration };
}
if (key === 'loadbrightness') return { duration: 750 };
return config;
}
},
e => {
return React.createElement(
ReactSpring.animated.div,
{
style: {
height: e.height,
opacity: e.opacity
},
className: 'xenoLib-notification',
ref: e => e && (this._ref = e)
},
React.createElement(
'div',
{
className: 'xenoLib-notification-content-wrapper',
ref: this._setContentRef,
onMouseEnter: e => {
if (this.state.leaving || !this.props.timeout || this.state.closeFast) return;
this._animationCancel();
if (this._startProgressing) {
this._timeout -= Date.now() - this._startProgressing;
}
this.state.hovered = true;
this.forceUpdate();
},
onMouseLeave: e => {
if (this.state.leaving || !this.props.timeout || this.state.closeFast) return;
this._animationCancel();
this.setState({ hovered: false });
},
style: {
'--grad-one': this.state.color,
'--grad-two': ColorConverter.lightenColor(this.state.color, 20),
'--bar-color': ColorConverter.darkenColor(this.state.color, 30)
},
onClick: e => {
if (!this.props.onClick) return;
if (e.target && e.target.getAttribute('role') === 'button') return;
this.props.onClick();
this.closeNow();
},
onContextMenu: e => {
if (!this.props.onContext) return;
this.props.onContext();
this.closeNow();
}
},
React.createElement(
'div',
{
className: 'xenoLib-notification-content',
style: {
backdropFilter: LibrarySettings.notifications.backdrop ? 'blur(5px)' : undefined,
background: ColorConverter.int2rgba(ColorConverter.hex2int(LibrarySettings.notifications.backdropColor), LibrarySettings.notifications.backdrop ? 0.3 : 1.0),
border: LibrarySettings.notifications.backdrop ? 'none' : undefined
},
ref: e => {
if (!LibrarySettings.notifications.backdrop || !e) return;
e.style.setProperty('backdrop-filter', e.style.backdropFilter, 'important');
e.style.setProperty('background', e.style.background, 'important');
e.style.setProperty('border', e.style.border, 'important');
}
},
React.createElement(ReactSpring.animated.div, {
className: XenoLib.joinClassNames('xenoLib-notification-loadbar', { 'xenoLib-notification-loadbar-striped': !this.props.timeout && this.state.loading, 'xenoLib-notification-loadbar-user': !this.props.timeout && !this.state.loading }),
style: { right: e.progress.to(e => 100 - e + '%'), filter: e.loadbrightness.to(e => `brightness(${e * 100}%)`) }
}),
React.createElement(
XenoLib.ReactComponents.Button,
{
look: XenoLib.ReactComponents.Button.Looks.BLANK,
size: XenoLib.ReactComponents.Button.Sizes.NONE,
onClick: e => {
e.preventDefault();
e.stopPropagation();
this.closeNow();
},
onContextMenu: e => {
e.preventDefault();
e.stopPropagation();
const state = api.getState();
state.data.forEach(notif => utils.remove(notif.id));
},
className: 'xenoLib-notification-close'
},
CloseButton
),
this.state.counter > 1 && BadgesModule.NumberBadge({ count: this.state.counter, className: 'xenLib-notification-counter', color: '#2196f3' }),
this.state.contentParsed
)
)
);
}
);
}
}
function NotificationsWrapper(e) {
const notifications = useStore(e => {
return e.data;
});
return notifications.map(item => React.createElement(XenoLib.ReactComponents.ErrorBoundary, { label: `Notification ${item.id}`, onError: () => api.setState(state => ({ data: state.data.filter(n => n.id !== item.id) })), key: item.id.toString() }, React.createElement(Notification, { ...item, leaving: false }))).reverse();
}
NotificationsWrapper.displayName = 'XenoLibNotifications';
const DOMElement = document.createElement('div');
DOMElement.className = XenoLib.joinClassNames('xenoLib-notifications', `xenoLib-centering-${LibrarySettings.notifications.position}`);
ReactDOM.render(React.createElement(NotificationsWrapper, {}), DOMElement);
document.querySelector('#app-mount').appendChild(DOMElement);
} catch (e) {
Logger.stacktrace('There has been an error loading the Notifications system, fallback object has been put in place to prevent errors', e);
XenoLib.Notifications = {
success(content, options = {}) { },
info(content, options = {}) { },
warning(content, options = {}) { },
danger(content, options = {}) { },
error(content, options = {}) { },
show(content, options = {}) { },
remove(id) { },
update(id, options) { }
};
}
/* NOTIFICATIONS END */
global.XenoLib = XenoLib;
const notifLocations = ['topLeft', 'topMiddle', 'topRight', 'bottomLeft', 'bottomMiddle', 'bottomRight'];
const notifLocationClasses = [`${XenoLib.getClass('selected topLeft')} ${XenoLib.getClass('topLeft option')}`, `topMiddle-xenoLib ${XenoLib.getClass('topLeft option')}`, `${XenoLib.getClass('selected topRight')} ${XenoLib.getClass('topLeft option')}`, `${XenoLib.getClass('selected bottomLeft')} ${XenoLib.getClass('topLeft option')}`, `bottomMiddle-xenoLib ${XenoLib.getClass('topLeft option')}`, `${XenoLib.getClass('selected bottomRight')} ${XenoLib.getClass('topLeft option')}`];
const PositionSelectorWrapperClassname = XenoLib.getClass('topLeft wrapper');
const PositionSelectorSelectedClassname = XenoLib.getClass('topLeft selected');
const PositionSelectorHiddenInputClassname = XenoLib.getClass('topLeft hiddenInput');
const FormText = WebpackModules.getByDisplayName('FormText');
class NotificationPosition extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
position: props.position
};
}
componentDidMount() {
this._notificationId = XenoLib.Notifications.show('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur lacinia justo eget libero ultrices mollis.', { timeout: 0 });
}
componentWillUnmount() {
XenoLib.Notifications.remove(this._notificationId);
}
getSelected() {
switch (this.state.position) {
case 'topLeft':
return 'Top Left';
case 'topMiddle':
return 'Top Middle';
case 'topRight':
return 'Top Right';
case 'bottomLeft':
return 'Bottom Left';
case 'bottomMiddle':
return 'Bottom Middle';
case 'bottomRight':
return 'Bottom Right';
default:
return 'Unknown';
}
}
render() {
return React.createElement(
'div',
{},
React.createElement(
'div',
{
className: PositionSelectorWrapperClassname
},
notifLocations.map((e, i) => {
return React.createElement(
'label',
{
className: XenoLib.joinClassNames(notifLocationClasses[i], { [PositionSelectorSelectedClassname]: this.state.position === e })
},
React.createElement('input', {
type: 'radio',
name: 'xenolib-notif-position-selector',
value: e,
onChange: () => {
this.props.onChange(e);
this.setState({ position: e });
},
className: PositionSelectorHiddenInputClassname
})
);
})
),
React.createElement(
FormText,
{
type: FormText.Types.DESCRIPTION,
className: DiscordClasses.Margins.marginTop8
},
this.getSelected()
)
);
}
}
class NotificationPositionField extends Settings.SettingField {
constructor(name, note, onChange, value) {
super(name, note, onChange, NotificationPosition, {
position: value,
onChange: reactElement => position => {
this.onChange(position);
}
});
}
}
return class CXenoLib extends Plugin {
constructor() {
super();
this.settings = LibrarySettings;
XenoLib.changeName(__filename, '1XenoLib'); /* prevent user from changing libs filename */
try {
WebpackModules.getByProps('openModal', 'hasModalOpen').closeModal(`${this.name}_DEP_MODAL`);
} catch (e) { }
}
load() {
super.load();
if (!BdApi.Plugins) return; /* well shit what now */
if (!BdApi.isSettingEnabled || !BdApi.disableSetting) return;
const prev = BdApi.isSettingEnabled('fork-ps-2');
if (prev) BdApi.disableSetting('fork-ps-2');
const list = BdApi.Plugins.getAll().filter(k => k._XL_PLUGIN);
for (let p = 0; p < list.length; p++) {
try {
BdApi.Plugins.reload(list[p].getName());
} catch (e) {
try {
Logger.stacktrace(`Failed to reload plugin ${list[p].getName()}`, e);
} catch (e) {
Logger.error(`Failed telling you about failing to reload a plugin`, list[p], e);
}
}
}
if (prev) BdApi.enableSetting('fork-ps-2');
}
buildSetting(data) {
if (data.type === 'position') {
const setting = new NotificationPositionField(data.name, data.note, data.onChange, data.value);
if (data.id) setting.id = data.id;
return setting;
} else if (data.type === 'color') {
const setting = new XenoLib.Settings.ColorPicker(data.name, data.note, data.value, data.onChange, data.options);
if (data.id) setting.id = data.id;
return setting;
}
return super.buildSetting(data);
}
getSettingsPanel() {
return this.buildSettingsPanel()
.append(new XenoLib.Settings.PluginFooter(() => this.showChangelog()))
.getElement();
}
saveSettings(category, setting, value) {
this.settings[category][setting] = value;
LibrarySettings[category][setting] = value;
PluginUtilities.saveSettings(this.name, LibrarySettings);
if (category === 'notifications') {
if (setting === 'position') {
const DOMElement = document.querySelector('.xenoLib-notifications');
if (DOMElement) {
DOMElement.className = XenoLib.joinClassNames('xenoLib-notifications', `xenoLib-centering-${LibrarySettings.notifications.position}`);
Dispatcher.dirtyDispatch({ type: 'XL_NOTIFS_ANIMATED' });
}
} else if (setting === 'backdrop' || setting === 'backdropColor') {
Dispatcher.wait(() => Dispatcher.dispatch({ type: 'XL_NOTIFS_SETTINGS_UPDATE', key: UPDATEKEY }), (UPDATEKEY = {}));
}
} else if (category === 'addons') {
if (setting === 'extra') {
if (value && !patchAddonCardAnyway.patched) patchAddonCardAnyway(true);
XenoLib.Notifications.warning('Reopen plugins section for immediate effect');
}
}
}
showChangelog(footer) {
XenoLib.showChangelog(`${this.name} has been updated!`, this.version, this._config.changelog);
}
get name() {
return config.info.name;
}
get short() {
let string = '';
for (let i = 0, len = config.info.name.length; i < len; i++) {
const char = config.info.name[i];
if (char === char.toUpperCase()) string += char;
}
return string;
}
get author() {
return config.info.authors.map(author => author.name).join(', ');
}
get version() {
return config.info.version;
}
get description() {
return config.info.description;
}
};
};
/* Finalize */
let ZeresPluginLibraryOutdated = false;
try {
if (global.BdApi && 'function' == typeof BdApi.getPlugin) {
const a = (c, a) => ((c = c.split('.').map(b => parseInt(b))), (a = a.split('.').map(b => parseInt(b))), !!(a[0] > c[0])) || !!(a[0] == c[0] && a[1] > c[1]) || !!(a[0] == c[0] && a[1] == c[1] && a[2] > c[2]),
b = BdApi.getPlugin('ZeresPluginLibrary');
((b, c) => b && b._config && b._config.info && b._config.info.version && a(b._config.info.version, c))(b, '1.2.24') && (ZeresPluginLibraryOutdated = !0);
}
} catch (e) {
console.error('Error checking if ZeresPluginLibrary is out of date', e);
}
return !global.ZeresPluginLibrary || ZeresPluginLibraryOutdated
? class {
constructor() {
this._config = config;
this.start = this.load = this.handleMissingLib;
}
getName() {
return this.name.replace(/\s+/g, '');
}
getAuthor() {
return this.author;
}
getVersion() {
return this.version;
}
getDescription() {
return this.description + ' You are missing ZeresPluginLibrary for this plugin, please enable the plugin and click Download Now.';
}
start() { }
stop() { }
handleMissingLib() {
const a = BdApi.findModuleByProps('openModal', 'hasModalOpen');
if (a && a.hasModalOpen(`${this.name}_DEP_MODAL`)) return;
const b = !global.ZeresPluginLibrary,
c = ZeresPluginLibraryOutdated ? 'Outdated Library' : 'Missing Library',
d = `The Library ZeresPluginLibrary required for ${this.name} is ${ZeresPluginLibraryOutdated ? 'outdated' : 'missing'}.`,
e = BdApi.findModuleByDisplayName('Text'),
f = BdApi.findModuleByDisplayName('ConfirmModal'),
g = () => BdApi.alert(c, BdApi.React.createElement('span', {}, BdApi.React.createElement('div', {}, d), `Due to a slight mishap however, you'll have to download the libraries yourself. This is not intentional, something went wrong, errors are in console.`, b || ZeresPluginLibraryOutdated ? BdApi.React.createElement('div', {}, BdApi.React.createElement('a', { href: 'https://betterdiscord.net/ghdl?id=2252', target: '_blank' }, 'Click here to download ZeresPluginLibrary')) : null));
if (!a || !f || !e) return console.error(`Missing components:${(a ? '' : ' ModalStack') + (f ? '' : ' ConfirmationModalComponent') + (e ? '' : 'TextElement')}`), g();
class h extends BdApi.React.PureComponent {
constructor(a) {
super(a), (this.state = { hasError: !1 });
}
componentDidCatch(a) {
console.error(`Error in ${this.props.label}, screenshot or copy paste the error above to Lighty for help.`), this.setState({ hasError: !0 }), 'function' == typeof this.props.onError && this.props.onError(a);
}
render() {
return this.state.hasError ? null : this.props.children;
}
}
let i = !1,
j = !1;
const k = a.openModal(
b => {
if (j) return null;
try {
return BdApi.React.createElement(
h,
{
label: 'missing dependency modal',
onError: () => {
a.closeModal(k), g();
}
},
BdApi.React.createElement(
f,
Object.assign(
{
header: c,
children: BdApi.React.createElement(e, { size: e.Sizes.SIZE_16, children: [`${d} Please click Download Now to download it.`] }),
red: !1,
confirmText: 'Download Now',
cancelText: 'Cancel',
onCancel: b.onClose,
onConfirm: () => {
if (i) return;
i = !0;
const b = require('request'),
c = require('fs'),
d = require('path');
b('https://raw.githubusercontent.com/rauenzi/BDPluginLibrary/master/release/0PluginLibrary.plugin.js', (b, e, f) => {
try {
if (b || 200 !== e.statusCode) return a.closeModal(k), g();
c.writeFile(d.join(BdApi.Plugins && BdApi.Plugins.folder ? BdApi.Plugins.folder : window.ContentManager.pluginsFolder, '0PluginLibrary.plugin.js'), f, () => { });
} catch (b) {
console.error('Fatal error downloading ZeresPluginLibrary', b), a.closeModal(k), g();
}
});
}
},
b,
{ onClose: () => { } }
)
)
);
} catch (b) {
return console.error('There has been an error constructing the modal', b), (j = !0), a.closeModal(k), g(), null;
}
},
{ modalKey: `${this.name}_DEP_MODAL` }
);
}
get name() {
return config.info.name;
}
get short() {
let string = '';
for (let i = 0, len = config.info.name.length; i < len; i++) {
const char = config.info.name[i];
if (char === char.toUpperCase()) string += char;
}
return string;
}
get author() {
return config.info.authors.map(author => author.name).join(', ');
}
get version() {
return config.info.version;
}
get description() {
return config.info.description;
}
}
: buildPlugin(global.ZeresPluginLibrary.buildPlugin(config));
})();
/*@end@*/