From 0a79f144c0f45adad4f39695c14664b0eef4660e Mon Sep 17 00:00:00 2001 From: _Lighty_ Date: Tue, 24 Dec 2019 12:58:53 +0100 Subject: [PATCH] XL v1.2.3 context menu fixes --- Plugins/1XenoLib.plugin.js | 1284 ++++++++++++++++++------------------ 1 file changed, 646 insertions(+), 638 deletions(-) diff --git a/Plugins/1XenoLib.plugin.js b/Plugins/1XenoLib.plugin.js index 0e78f3d..2003805 100644 --- a/Plugins/1XenoLib.plugin.js +++ b/Plugins/1XenoLib.plugin.js @@ -23,56 +23,56 @@ @else@*/ /* - * Copyright© 2019-2020, _Lighty_ + * Copyright© 2019-2020, _Lighty_ * All rights reserved. * Code may not be redistributed, modified or otherwise taken without explicit permission. */ var XenoLib = (() => { - /* Setup */ - const config = { - main: 'index.js', - info: { - name: 'XenoLib', - authors: [ - { - name: 'Lighty', - discord_id: '239513071272329217', - github_username: 'LightyPon', - twitter_username: '' - } - ], - version: '1.2.2', - description: 'Simple library to complement plugins with shared code without lowering performance.', - github: 'https://github.com/1Lighty', - github_raw: 'https://raw.githubusercontent.com/1Lighty/BetterDiscordPlugins/master/Plugins/1XenoLib.plugin.js' - }, - changelog: [ - { - title: 'Hopefully fixed', - type: 'fixed', - items: ["Reworked the context menu patching, so plugins like BDContextMenu by Zerebos don't break", 'Added utility function for renaming plugins to prepare for future plugin renames'] - } - ] + /* Setup */ + const config = { + main: 'index.js', + info: { + name: 'XenoLib', + authors: [ + { + name: 'Lighty', + discord_id: '239513071272329217', + github_username: 'LightyPon', + twitter_username: '' + } + ], + version: '1.2.3', + description: 'Simple library to complement plugins with shared code without lowering performance.', + github: 'https://github.com/1Lighty', + github_raw: 'https://raw.githubusercontent.com/1Lighty/BetterDiscordPlugins/master/Plugins/1XenoLib.plugin.js' + }, + changelog: [ + { + title: 'Hopefully fixed', + type: 'fixed', + items: ['Fixed BDFDB causing issues with patching context menus'] + } + ] + }; + + /* Build */ + const buildPlugin = ([Plugin, Api]) => { + const { ContextMenu, EmulatedTooltip, Toasts, Settings, Popouts, Modals, Utilities, WebpackModules, Filters, DiscordModules, ColorConverter, DOMTools, DiscordClasses, DiscordSelectors, ReactTools, ReactComponents, DiscordAPI, Logger, Patcher, PluginUpdater, PluginUtilities, DiscordClassModules, Structs } = Api; + const { React, ModalStack, ContextMenuActions, ContextMenuItem, ContextMenuItemsGroup, ReactDOM, ChannelStore, GuildStore, UserStore, DiscordConstants, Dispatcher, GuildMemberStore, GuildActions, PrivateChannelActions, LayerManager, InviteActions } = DiscordModules; + + const ContextMenuSubMenuItem = WebpackModules.getByDisplayName('FluxContainer(SubMenuItem)'); + + if (global.XenoLib) global.XenoLib.shutdown(); + const XenoLib = {}; + XenoLib.shutdown = () => { + Patcher.unpatchAll(); + PluginUtilities.removeStyle('XenoLib-CSS'); + if (global.BDEvents) BDEvents.off('plugin-unloaded', listener); }; - /* Build */ - const buildPlugin = ([Plugin, Api]) => { - const { ContextMenu, EmulatedTooltip, Toasts, Settings, Popouts, Modals, Utilities, WebpackModules, Filters, DiscordModules, ColorConverter, DOMTools, DiscordClasses, DiscordSelectors, ReactTools, ReactComponents, DiscordAPI, Logger, Patcher, PluginUpdater, PluginUtilities, DiscordClassModules, Structs } = Api; - const { React, ModalStack, ContextMenuActions, ContextMenuItem, ContextMenuItemsGroup, ReactDOM, ChannelStore, GuildStore, UserStore, DiscordConstants, Dispatcher, GuildMemberStore, GuildActions, PrivateChannelActions, LayerManager, InviteActions } = DiscordModules; - - const ContextMenuSubMenuItem = WebpackModules.getByDisplayName('FluxContainer(SubMenuItem)'); - - if (global.XenoLib) global.XenoLib.shutdown(); - const XenoLib = {}; - XenoLib.shutdown = () => { - Patcher.unpatchAll(); - PluginUtilities.removeStyle('XenoLib-CSS'); - if (global.BDEvents) BDEvents.off('plugin-unloaded', listener); - }; - - PluginUtilities.addStyle( - 'XenoLib-CSS', - ` + PluginUtilities.addStyle( + 'XenoLib-CSS', + ` .xenoLib-color-picker .xenoLib-button { width: 34px; min-height: 38px; @@ -112,619 +112,627 @@ var XenoLib = (() => { transform: translate3d(-200%,0,0); } ` - ); + ); - XenoLib.getUser = WebpackModules.getByProps('getUser', 'acceptAgreements').getUser; + XenoLib.getUser = WebpackModules.getByProps('getUser', 'acceptAgreements').getUser; - XenoLib.getClass = arg => { - const args = arg.split(' '); - return WebpackModules.getByProps(...args)[args[args.length - 1]]; - }; - XenoLib.getSingleClass = arg => XenoLib.getClass(arg).split(' ')[0]; - XenoLib.joinClassNames = WebpackModules.getModule(e => e.default && e.default.default); + XenoLib.getClass = arg => { + const args = arg.split(' '); + return WebpackModules.getByProps(...args)[args[args.length - 1]]; + }; + XenoLib.getSingleClass = arg => XenoLib.getClass(arg).split(' ')[0]; + XenoLib.joinClassNames = WebpackModules.getModule(e => e.default && e.default.default); - XenoLib.authorId = '239513071272329217'; - const requestUser = () => - XenoLib.getUser(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(); - XenoLib.supportServerId = '389049952732446731'; + XenoLib.authorId = '239513071272329217'; + const requestUser = () => + XenoLib.getUser(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(); + XenoLib.supportServerId = '389049952732446731'; - try { - if (V2C_PluginCard && V2C_ThemeCard) { - const LinkClassname = XenoLib.joinClassNames(XenoLib.getClass('anchorUnderlineOnHover anchor'), XenoLib.getClass('anchor anchorUnderlineOnHover'), 'bda-author'); - const handlePatch = (_this, _, ret) => { - const author = Utilities.getNestedProp(ret, 'props.children.0.props.children.0.props.children.4'); - const footer = Utilities.getNestedProp(ret, 'props.children.2.props.children.0.props.children'); - 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 - ]; - } - if (footer) { - footer.push( - ' | ', - React.createElement('a', { className: 'bda-link', href: 'https://paypal.me/lighty13', target: '_blank' }, 'Paypal'), - ' | ', - React.createElement('a', { className: 'bda-link', href: 'https://ko-fi.com/lighty_', target: '_blank' }, 'Ko-fi'), - ' | ', - React.createElement( - 'a', - { - className: 'bda-link', - onClick: () => { - LayerManager.popLayer(); - if (GuildStore.getGuild(XenoLib.supportServerId)) GuildActions.transitionToGuildSync(XenoLib.supportServerId); - else InviteActions.openNativeAppModal('NYvWdN5'); - } - }, - 'Support Server' - ) - ); - if (_this.props.plugin.showChangelog || _this.props.plugin.getChanges) { - footer.push( - ' | ', - React.createElement( - 'a', - { - className: 'bda-link', - onClick: () => { - if (_this.props.plugin.showChangelog) _this.props.plugin.showChangelog(); - else Modals.showChangelogModal(_this.props.plugin.getName() + ' Changelog', _this.props.plugin.getVersion(), _this.props.plugin.getChanges()); - } - }, - 'Changelog' - ) - ); - } - } - }; - Patcher.after(V2C_PluginCard.prototype, 'render', handlePatch); - Patcher.after(V2C_ThemeCard.prototype, 'render', handlePatch); - } - } catch (e) { } - - XenoLib.__contextPatches = []; - XenoLib.__contextPatches.__contextPatched = false; - const existingContextMenus = ['NativeContextMenu', 'GuildRoleContextMenu', 'MessageContextMenu', 'DeveloperContextMenu', 'ScreenshareContextMenu']; - const ContextMenuClassname = XenoLib.getSingleClass('subMenuContext contextMenu'); - const getContextMenuChild = val => { - if (!val) return; - const isValid = obj => obj.type === 'div' && obj.props && typeof obj.props.className === 'string' && obj.props.className.indexOf(ContextMenuClassname) !== -1 && Array.isArray(Utilities.getNestedProp(obj, 'props.children.props.children')); - if (isValid(val)) return val.props.children; - const children = Utilities.getNestedProp(val, 'props.children'); - if (!children) return; - if (Array.isArray(children)) { - for (let i = 0; i < children.length; i++) { - const ret = getContextMenuChild(children[i]); - if (ret) return ret.props.children; - } - } else if (isValid(children)) return children.props.children; - }; - function patchAllContextMenus() { - const handleContextMenu = (_this, ret) => { - const menuGroups = getContextMenuChild(ret) || ret; - if (!menuGroups) return Logger.warn('Failed to get context menu groups!', _this, ret); - XenoLib.__contextPatches.forEach(e => { - try { - e(_this, menuGroups); - } catch (e) { - Logger.stacktrace('Error with patched context menu', e); - } - }); - }; - existingContextMenus.forEach(type => { - const module = WebpackModules.getByDisplayName(type); - if (!module) return Logger.warn(`Failed to find ContextMenu type`, type); - Patcher.after(module.prototype, 'render', (_this, _, ret) => handleContextMenu(_this, ret)); + try { + if (V2C_PluginCard && V2C_ThemeCard) { + const LinkClassname = XenoLib.joinClassNames(XenoLib.getClass('anchorUnderlineOnHover anchor'), XenoLib.getClass('anchor anchorUnderlineOnHover'), 'bda-author'); + const handlePatch = (_this, _, ret) => { + const author = Utilities.getNestedProp(ret, 'props.children.0.props.children.0.props.children.4'); + const footer = Utilities.getNestedProp(ret, 'props.children.2.props.children.0.props.children'); + 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(); }); - function getModule(regex) { - const modules = WebpackModules.getAllModules(); - for (const index in modules) { - if (!modules.hasOwnProperty(index)) continue; - const module = modules[index]; - if (!module.exports || !module.exports.__esModule || !module.exports.default) continue; - if (module.exports.default.toString().search(regex) !== -1) return module; - } - } - const somemoremenus = [getModule(/case \w.ContextMenuTypes.CHANNEL_LIST_TEXT/), getModule(/case \w.ContextMenuTypes.GUILD_CHANNEL_LIST/), getModule(/case \w.ContextMenuTypes.USER_CHANNEL_MEMBERS/)]; - somemoremenus.forEach(menu => { - if (!menu) return Logger.warn('Special context menu is undefined!'); - const originalFunc = menu.exports.default; - Patcher.after(menu.exports, 'default', (_, [props], ret) => handleContextMenu({ props }, ret)); - Patcher.instead(menu.exports.default, 'toString', (_this, args, orig) => { - return originalFunc.toString(...args); - }); - }); - XenoLib.__contextPatches.__contextPatched = true; - } - XenoLib.patchContext = callback => { - XenoLib.__contextPatches.push(callback); - if (!XenoLib.__contextPatches.__contextPatched) patchAllContextMenus(); - }; - class ContextMenuWrapper extends React.PureComponent { - render() { - return React.createElement('div', { className: DiscordClasses.ContextMenu.contextMenu }, this.props.menu); - } - } - XenoLib.createSharedContext = (menuCreation, props, type) => { - if (props.__XenoLib_ContextMenus) { - props.__XenoLib_ContextMenus.push(menuCreation); - } else { - props.__XenoLib_ContextMenus = [menuCreation]; - const oOnContextMenu = props.onContextMenu; - props.onContextMenu = e => (typeof oOnContextMenu === 'function' && oOnContextMenu(e), ContextMenuActions.openContextMenu(e, e => React.createElement(ContextMenuWrapper, { menu: props.__XenoLib_ContextMenus.map(m => m()), type }))); - } - }; - - if (global.XenoLib) { - if (global.XenoLib.__contextPatches && global.XenoLib.__contextPatches.length) { - XenoLib.__contextPatches.push(...global.XenoLib.__contextPatches); - patchAllContextMenus(); - } - } - XenoLib.unpatchContext = callback => XenoLib.__contextPatches.splice(XenoLib.__contextPatches.indexOf(callback), 1); - - XenoLib.createContextMenuItem = (label, action, options = {}) => - React.createElement(ContextMenuItem, { - label, - action: () => { - ContextMenuActions.closeContextMenu(); - action(); + }; + 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 }, - ...options - }); - XenoLib.createContextMenuSubMenu = (label, items, options = {}) => - React.createElement(ContextMenuSubMenuItem, { - label, - render: items, - ...options - }); - XenoLib.createContextMenuGroup = (children, options) => React.createElement(ContextMenuItemsGroup, { children, ...options }); - XenoLib.DiscordUtils = WebpackModules.getByProps('bindAll', 'debounce'); + 'Lighty' + ), + post + ]; + } + if (footer) { + footer.push( + ' | ', + React.createElement('a', { className: 'bda-link', href: 'https://paypal.me/lighty13', target: '_blank' }, 'Paypal'), + ' | ', + React.createElement('a', { className: 'bda-link', href: 'https://ko-fi.com/lighty_', target: '_blank' }, 'Ko-fi'), + ' | ', + React.createElement( + 'a', + { + className: 'bda-link', + onClick: () => { + LayerManager.popLayer(); + if (GuildStore.getGuild(XenoLib.supportServerId)) GuildActions.transitionToGuildSync(XenoLib.supportServerId); + else InviteActions.openNativeAppModal('NYvWdN5'); + } + }, + 'Support Server' + ) + ); + if (_this.props.plugin.showChangelog || _this.props.plugin.getChanges) { + footer.push( + ' | ', + React.createElement( + 'a', + { + className: 'bda-link', + onClick: () => { + if (_this.props.plugin.showChangelog) _this.props.plugin.showChangelog(); + else Modals.showChangelogModal(_this.props.plugin.getName() + ' Changelog', _this.props.plugin.getVersion(), _this.props.plugin.getChanges()); + } + }, + 'Changelog' + ) + ); + } + } + }; + Patcher.after(V2C_PluginCard.prototype, 'render', handlePatch); + Patcher.after(V2C_ThemeCard.prototype, 'render', handlePatch); + } + } catch (e) {} - const dialog = require('electron').remote.dialog; - const showSaveDialog = dialog.showSaveDialogSync || dialog.showSaveDialog; - const showOpenDialog = dialog.showOpenDialogSync || dialog.showOpenDialog; - XenoLib.ReactComponents = {}; - - XenoLib.ReactComponents.ButtonOptions = WebpackModules.getByProps('ButtonLink'); - XenoLib.ReactComponents.Button = XenoLib.ReactComponents.ButtonOptions.default; - - const MultiInputClassname = XenoLib.joinClassNames(DiscordClasses.BasicInputs.input.value, XenoLib.getClass('multiInput')); - const MultiInputFirstClassname = XenoLib.getClass('multiInputFirst'); - const MultiInputFieldClassname = XenoLib.getClass('multiInputField'); - const ErrorMessageClassname = XenoLib.getClass('input errorMessage'); - const ErrorClassname = XenoLib.getClass('input error'); - 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 + XenoLib.__contextPatches = []; + XenoLib.__contextPatches.__contextPatched = false; + const existingContextMenus = ['NativeContextMenu', 'GuildRoleContextMenu', 'MessageContextMenu', 'DeveloperContextMenu', 'ScreenshareContextMenu']; + const ContextMenuClassname = XenoLib.getSingleClass('subMenuContext contextMenu'); + const getContextMenuChild = val => { + if (!val) return; + const isValid = obj => obj.type === 'div' && obj.props && typeof obj.props.className === 'string' && obj.props.className.indexOf(ContextMenuClassname) !== -1 && Array.isArray(Utilities.getNestedProp(obj, 'props.children.props.children')); + if (isValid(val)) return val.props.children; + const children = Utilities.getNestedProp(val, 'props.children'); + if (!children) return; + if (Array.isArray(children)) { + for (let i = 0; i < children.length; i++) { + const ret = getContextMenuChild(children[i]); + if (ret) return ret.props.children; + } + } else if (isValid(children)) return children.props.children; + }; + function patchAllContextMenus() { + const handleContextMenu = (_this, ret) => { + const menuGroups = getContextMenuChild(ret) || ret; + if (!menuGroups) return Logger.warn('Failed to get context menu groups!', _this, ret); + XenoLib.__contextPatches.forEach(e => { + try { + e(_this, menuGroups); + } catch (e) { + Logger.stacktrace('Error with patched context menu', e); + } + }); + }; + existingContextMenus.forEach(type => { + const module = WebpackModules.getByDisplayName(type); + if (!module) return Logger.warn(`Failed to find ContextMenu type`, type); + Patcher.after(module.prototype, 'render', (_this, _, ret) => handleContextMenu(_this, ret)); + }); + function getModule(regex) { + const modules = WebpackModules.getAllModules(); + for (const index in modules) { + if (!modules.hasOwnProperty(index)) continue; + const module = modules[index]; + if (!module.exports || !module.exports.__esModule || !module.exports.default) continue; + /* if BDFDB was inited before us, patch the already patched function */ + if (module.exports.default.toString().search(regex) !== -1 || (module.exports.default.isBDFDBpatched && module.exports.default.originalsource.toString().search(regex) !== -1)) return module; + } + } + const somemoremenus = [getModule(/case \w.ContextMenuTypes.CHANNEL_LIST_TEXT/), getModule(/case \w.ContextMenuTypes.GUILD_CHANNEL_LIST/), getModule(/case \w.ContextMenuTypes.USER_CHANNEL_MEMBERS/)]; + somemoremenus.forEach(menu => { + if (!menu) return Logger.warn('Special context menu is undefined!'); + const origDef = menu.exports.default; + const originalFunc = Utilities.getNestedProp(menu, 'exports.BDFDBpatch.default.originalMethod') || menu.exports.default; + Patcher.after(menu.exports, 'default', (_, [props], ret) => handleContextMenu({ props }, ret)); + /* make it friendly to other plugins and libraries that search by string + note: removing this makes BDFDB shit itself */ - XenoLib.ReactComponents.FilePicker = class FilePicker extends React.PureComponent { - constructor(props) { - super(props); - this.state = { - multiInputFocused: false, - path: props.path, - error: null - }; - XenoLib.DiscordUtils.bindAll(this, ['handleOnBrowse', 'handleChange']); - this.delayedCallVerifyPath = new DelayedCall(500, () => - FsModule.access(this.state.path, FsModule.constants.W_OK, error => { - const invalid = (error && error.message.match(/.*: (.*), access '/)[1]) || null; - this.setState({ error: invalid }); - if (invalid && this.props.nullOnInvalid) this.props.onChange(null); - }) - ); - } - handleOnBrowse() { - const path = showOpenDialog({ title: this.props.title, properties: this.props.properties }); - if (Array.isArray(path) && path.length) this.handleChange(path[0]); - } - handleChange(path) { - this.props.onChange(path); - this.setState({ path }); - this.delayedCallVerifyPath.delay(); - } - 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 }), - 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) - ); - } - }; - - /** - * @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} [options.colors=presetColors] - preset list of colors - * @author Zerebos, from his library ZLibrary - */ - const FormItem = WebpackModules.getByDisplayName('FormItem'); - const DeprecatedModal = WebpackModules.getByDisplayName('DeprecatedModal'); - const ModalContainerClassname = XenoLib.getClass('mobile container'); - const ModalContentClassname = XenoLib.getClass('mobile container content'); - - const Icon = WebpackModules.getByDisplayName('Icon'); - - class ColorPickerModal extends React.PureComponent { - constructor(props) { - super(props); - this.state = { value: props.value }; - XenoLib.DiscordUtils.bindAll(this, ['handleChange']); - } - handleChange(value) { - this.setState({ value }); - this.props.onChange(ColorConverter.int2hex(value)); - } - render() { - return React.createElement( - DeprecatedModal, - { className: ModalContainerClassname, tag: 'form', onSubmit: this.handleSubmit, size: '' }, - React.createElement( - DeprecatedModal.Content, - { className: ModalContentClassname }, - React.createElement( - FormItem, - { className: DiscordClasses.Margins.marginTop20 }, - React.createElement(WebpackModules.getByDisplayName('ColorPicker'), { - 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 - }) - ) - ) - ); - } + Patcher.instead(menu.exports.default, 'toString', (_, args, __) => originalFunc.toString(...args)); + /* if BDFDB already patched it, patch the function BDFDB is storing in case it decides to unaptch + this is to prevent BDFDB from removing our patch + this function is never called in BDFDB, it's only stored for restore + */ + if (origDef.isBDFDBpatched && menu.exports.BDFDBpatch && typeof menu.exports.BDFDBpatch.default.originalMethod === 'function') { + Patcher.after(menu.exports.BDFDBpatch.default, 'originalMethod', (_, [props], ret) => handleContextMenu({ props }, ret)); + /* make it friendly to other plugins and libraries that search by string + note: removing this makes BDFDB shit itself + */ + Patcher.instead(menu.exports.BDFDBpatch.default.originalMethod, 'toString', (_, args, __) => originalFunc.toString(...args)); } - - class ColorPicker extends React.PureComponent { - constructor(props) { - super(props); - this.state = { - error: null, - value: props.value, - multiInputFocused: false - }; - XenoLib.DiscordUtils.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() { - ModalStack.push(e => 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: 'xenoLib-button button-34kXw5 button-3tQuzi' - }, - React.createElement('span', { className: 'text-2sI5Sd' }, 'Color picker'), - React.createElement( - 'span', - { - className: 'xenoLib-button-icon' - }, - React.createElement(Icon, { - name: 'Dropper' - }) - ) - ), - 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: 'xenoLib-button button-34kXw5 button-3tQuzi' - }, - React.createElement('span', { className: 'text-2sI5Sd' }, 'Reset'), - React.createElement( - 'span', - { - className: 'xenoLib-button-icon xenoLib-revert' - }, - React.createElement(Icon, { - name: 'ClockReverse' - }) - ) - ) - ), - !!this.state.error && React.createElement('div', { className: ErrorMessageClassname }, 'Error: ', this.state.error) - ); - } - } - XenoLib.Settings = {}; - 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.loadData = (name, key, defaultData) => { - try { - return Object.assign(defaultData ? Utilities.deepclone(defaultData) : {}, BdApi.getData(name, key)); - } catch (err) { - Logger.err(name, 'Unable to load data: ', err); - return Utilities.deepclone(defaultData); - } - }; - - XenoLib.changeName = (currentName, newName) => { - 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 = global.pluginCookie && pluginCookie[pluginName]; - try { - 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`)); - Toasts.success(`[XenoLib] ${pluginName} file has been renamed to ${newName}`); - if (!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; - const onLoaded = e => { - if (e !== newName) return; - BDEvents.off('plugin-loaded', onLoaded); - pluginModule.startPlugin(newName); - }; - BDEvents.on('plugin-loaded', onLoaded); - } - } catch (e) { - Logger.stacktrace('There has been an issue renaming a plugin', e); - } - }; - - global.XenoLib = XenoLib; - const listener = e => { - if (e !== 'XenoLib') return; - XenoLib.shutdown(); - BDEvents.off('plugin-unloaded', listener); - }; - if (global.BDEvents) { - BDEvents.dispatch('xenolib-loaded'); - BDEvents.on('plugin-unloaded', listener); - } - - XenoLib.changeName(__filename, '1XenoLib'); /* prevent user from changing libs filename */ - - return class XenoLib extends Plugin { - onStart() { - Toasts.info('Starting me does nothing :)'); - } - 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; - } - }; + }); + XenoLib.__contextPatches.__contextPatched = true; + } + XenoLib.patchContext = callback => { + XenoLib.__contextPatches.push(callback); + }; + class ContextMenuWrapper extends React.PureComponent { + render() { + return React.createElement('div', { className: DiscordClasses.ContextMenu.contextMenu }, this.props.menu); + } + } + XenoLib.createSharedContext = (menuCreation, props, type) => { + if (props.__XenoLib_ContextMenus) { + props.__XenoLib_ContextMenus.push(menuCreation); + } else { + props.__XenoLib_ContextMenus = [menuCreation]; + const oOnContextMenu = props.onContextMenu; + props.onContextMenu = e => (typeof oOnContextMenu === 'function' && oOnContextMenu(e), ContextMenuActions.openContextMenu(e, e => React.createElement(ContextMenuWrapper, { menu: props.__XenoLib_ContextMenus.map(m => m()), type }))); + } }; - /* Finalize */ + if (global.XenoLib) if (global.XenoLib.__contextPatches && global.XenoLib.__contextPatches.length) XenoLib.__contextPatches.push(...global.XenoLib.__contextPatches); + XenoLib.unpatchContext = callback => XenoLib.__contextPatches.splice(XenoLib.__contextPatches.indexOf(callback), 1); + patchAllContextMenus(); /* prevent BDFDB from being a gay piece of crap by patching it first */ + XenoLib.createContextMenuItem = (label, action, options = {}) => + React.createElement(ContextMenuItem, { + label, + action: () => { + ContextMenuActions.closeContextMenu(); + action(); + }, + ...options + }); + XenoLib.createContextMenuSubMenu = (label, items, options = {}) => + React.createElement(ContextMenuSubMenuItem, { + label, + render: items, + ...options + }); + XenoLib.createContextMenuGroup = (children, options) => React.createElement(ContextMenuItemsGroup, { children, ...options }); + XenoLib.DiscordUtils = WebpackModules.getByProps('bindAll', 'debounce'); - return !global.ZeresPluginLibrary - ? class { - getName() { - return this.name.replace(/\s+/g, ''); - } + const dialog = require('electron').remote.dialog; + const showSaveDialog = dialog.showSaveDialogSync || dialog.showSaveDialog; + const showOpenDialog = dialog.showOpenDialogSync || dialog.showOpenDialog; + XenoLib.ReactComponents = {}; - getAuthor() { - return this.author; - } + XenoLib.ReactComponents.ButtonOptions = WebpackModules.getByProps('ButtonLink'); + XenoLib.ReactComponents.Button = XenoLib.ReactComponents.ButtonOptions.default; - getVersion() { - return this.version; - } + const MultiInputClassname = XenoLib.joinClassNames(DiscordClasses.BasicInputs.input.value, XenoLib.getClass('multiInput')); + const MultiInputFirstClassname = XenoLib.getClass('multiInputFirst'); + const MultiInputFieldClassname = XenoLib.getClass('multiInputField'); + const ErrorMessageClassname = XenoLib.getClass('input errorMessage'); + const ErrorClassname = XenoLib.getClass('input error'); + 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 + */ + XenoLib.ReactComponents.FilePicker = class FilePicker extends React.PureComponent { + constructor(props) { + super(props); + this.state = { + multiInputFocused: false, + path: props.path, + error: null + }; + XenoLib.DiscordUtils.bindAll(this, ['handleOnBrowse', 'handleChange']); + this.delayedCallVerifyPath = new DelayedCall(500, () => + FsModule.access(this.state.path, FsModule.constants.W_OK, error => { + const invalid = (error && error.message.match(/.*: (.*), access '/)[1]) || null; + this.setState({ error: invalid }); + if (invalid && this.props.nullOnInvalid) this.props.onChange(null); + }) + ); + } + handleOnBrowse() { + const path = showOpenDialog({ title: this.props.title, properties: this.props.properties }); + if (Array.isArray(path) && path.length) this.handleChange(path[0]); + } + handleChange(path) { + this.props.onChange(path); + this.setState({ path }); + this.delayedCallVerifyPath.delay(); + } + 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 }), + 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) + ); + } + }; - getDescription() { - return this.description; - } + /** + * @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} [options.colors=presetColors] - preset list of colors + * @author Zerebos, from his library ZLibrary + */ + const FormItem = WebpackModules.getByDisplayName('FormItem'); + const DeprecatedModal = WebpackModules.getByDisplayName('DeprecatedModal'); + const ModalContainerClassname = XenoLib.getClass('mobile container'); + const ModalContentClassname = XenoLib.getClass('mobile container content'); - stop() { } + const Icon = WebpackModules.getByDisplayName('Icon'); - load() { - const header = `Missing Library`; - const content = `The Library ZeresPluginLibrary required for ${this.name} is missing.`; - const ModalStack = BdApi.findModuleByProps('push', 'update', 'pop', 'popWithKey'); - const TextElement = BdApi.findModuleByProps('Sizes', 'Weights'); - const ConfirmationModal = BdApi.findModule(m => m.defaultProps && m.key && m.key() === 'confirm-modal'); - const onFail = () => BdApi.getCore().alert(header, `${content}
Due to a slight mishap however, you'll have to download the library yourself. After opening the link, do CTRL + S to download the library.

Click here to download ZeresPluginLibrary`); - if (!ModalStack || !ConfirmationModal || !TextElement) return onFail(); - ModalStack.push(props => { - return BdApi.React.createElement( - ConfirmationModal, - Object.assign( - { - header, - children: [ - TextElement({ - color: TextElement.Colors.PRIMARY, - children: [`${content} Please click Download Now to install it.`] - }) - ], - red: false, - confirmText: 'Download Now', - cancelText: 'Cancel', - onConfirm: () => { - const request = require('request'); - const fs = require('fs'); - const path = require('path'); - const onDone = () => { - if (!global.pluginModule || !global.BDEvents) return; - const onLoaded = e => { - if (e !== 'ZeresPluginLibrary') return; - BDEvents.off('plugin-loaded', onLoaded); - pluginModule.reloadPlugin(this.name); - }; - BDEvents.on('plugin-loaded', onLoaded); - }; - request('https://rauenzi.github.io/BDPluginLibrary/release/0PluginLibrary.plugin.js', (error, response, body) => { - if (error) return onFail(); - onDone(); - fs.writeFile(path.join(window.ContentManager.pluginsFolder, '0PluginLibrary.plugin.js'), body, () => { }); - }); - } - }, - props - ) - ); - }); - } + class ColorPickerModal extends React.PureComponent { + constructor(props) { + super(props); + this.state = { value: props.value }; + XenoLib.DiscordUtils.bindAll(this, ['handleChange']); + } + handleChange(value) { + this.setState({ value }); + this.props.onChange(ColorConverter.int2hex(value)); + } + render() { + return React.createElement( + DeprecatedModal, + { className: ModalContainerClassname, tag: 'form', onSubmit: this.handleSubmit, size: '' }, + React.createElement( + DeprecatedModal.Content, + { className: ModalContentClassname }, + React.createElement( + FormItem, + { className: DiscordClasses.Margins.marginTop20 }, + React.createElement(WebpackModules.getByDisplayName('ColorPicker'), { + 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 + }) + ) + ) + ); + } + } - start() { } - - 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; - } + class ColorPicker extends React.PureComponent { + constructor(props) { + super(props); + this.state = { + error: null, + value: props.value, + multiInputFocused: false + }; + XenoLib.DiscordUtils.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; } - : buildPlugin(global.ZeresPluginLibrary.buildPlugin(config)); + this.setState({ value }); + this.props.onChange(!value.length || !ColorConverter.isValidHex(value) ? this.props.defaultColor : value); + } + handleColorPicker() { + ModalStack.push(e => 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: 'xenoLib-button button-34kXw5 button-3tQuzi' + }, + React.createElement('span', { className: 'text-2sI5Sd' }, 'Color picker'), + React.createElement( + 'span', + { + className: 'xenoLib-button-icon' + }, + React.createElement(Icon, { + name: 'Dropper' + }) + ) + ), + 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: 'xenoLib-button button-34kXw5 button-3tQuzi' + }, + React.createElement('span', { className: 'text-2sI5Sd' }, 'Reset'), + React.createElement( + 'span', + { + className: 'xenoLib-button-icon xenoLib-revert' + }, + React.createElement(Icon, { + name: 'ClockReverse' + }) + ) + ) + ), + !!this.state.error && React.createElement('div', { className: ErrorMessageClassname }, 'Error: ', this.state.error) + ); + } + } + XenoLib.Settings = {}; + 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.loadData = (name, key, defaultData) => { + try { + return Object.assign(defaultData ? Utilities.deepclone(defaultData) : {}, BdApi.getData(name, key)); + } catch (err) { + Logger.err(name, 'Unable to load data: ', err); + return Utilities.deepclone(defaultData); + } + }; + + XenoLib.changeName = (currentName, newName) => { + 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 = global.pluginCookie && pluginCookie[pluginName]; + try { + 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`)); + Toasts.success(`[XenoLib] ${pluginName} file has been renamed to ${newName}`); + if (!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; + const onLoaded = e => { + if (e !== newName) return; + BDEvents.off('plugin-loaded', onLoaded); + pluginModule.startPlugin(newName); + }; + BDEvents.on('plugin-loaded', onLoaded); + } + } catch (e) { + Logger.stacktrace('There has been an issue renaming a plugin', e); + } + }; + + global.XenoLib = XenoLib; + const listener = e => { + if (e !== 'XenoLib') return; + XenoLib.shutdown(); + BDEvents.off('plugin-unloaded', listener); + }; + if (global.BDEvents) { + BDEvents.dispatch('xenolib-loaded'); + BDEvents.on('plugin-unloaded', listener); + } + + XenoLib.changeName(__filename, '1XenoLib'); /* prevent user from changing libs filename */ + + return class XenoLib extends Plugin { + onStart() { + Toasts.info('Starting me does nothing :)'); + } + 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 */ + + return !global.ZeresPluginLibrary + ? class { + getName() { + return this.name.replace(/\s+/g, ''); + } + + getAuthor() { + return this.author; + } + + getVersion() { + return this.version; + } + + getDescription() { + return this.description; + } + + stop() {} + + load() { + const header = `Missing Library`; + const content = `The Library ZeresPluginLibrary required for ${this.name} is missing.`; + const ModalStack = BdApi.findModuleByProps('push', 'update', 'pop', 'popWithKey'); + const TextElement = BdApi.findModuleByProps('Sizes', 'Weights'); + const ConfirmationModal = BdApi.findModule(m => m.defaultProps && m.key && m.key() === 'confirm-modal'); + const onFail = () => BdApi.getCore().alert(header, `${content}
Due to a slight mishap however, you'll have to download the library yourself. After opening the link, do CTRL + S to download the library.

Click here to download ZeresPluginLibrary`); + if (!ModalStack || !ConfirmationModal || !TextElement) return onFail(); + ModalStack.push(props => { + return BdApi.React.createElement( + ConfirmationModal, + Object.assign( + { + header, + children: [ + TextElement({ + color: TextElement.Colors.PRIMARY, + children: [`${content} Please click Download Now to install it.`] + }) + ], + red: false, + confirmText: 'Download Now', + cancelText: 'Cancel', + onConfirm: () => { + const request = require('request'); + const fs = require('fs'); + const path = require('path'); + const onDone = () => { + if (!global.pluginModule || !global.BDEvents) return; + const onLoaded = e => { + if (e !== 'ZeresPluginLibrary') return; + BDEvents.off('plugin-loaded', onLoaded); + pluginModule.reloadPlugin(this.name); + }; + BDEvents.on('plugin-loaded', onLoaded); + }; + request('https://rauenzi.github.io/BDPluginLibrary/release/0PluginLibrary.plugin.js', (error, response, body) => { + if (error) return onFail(); + onDone(); + fs.writeFile(path.join(window.ContentManager.pluginsFolder, '0PluginLibrary.plugin.js'), body, () => {}); + }); + } + }, + props + ) + ); + }); + } + + start() {} + + 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@*/