739 lines
32 KiB
JavaScript
739 lines
32 KiB
JavaScript
//META{"name":"XenoLib","source":"https://github.com/1Lighty/BetterDiscordPlugins/blob/master/Plugins/1XenoLib.plugin.js/"}*//
|
|
/*@cc_on
|
|
@if (@_jscript)
|
|
|
|
// Offer to self-install for clueless users that try to run this directly.
|
|
var shell = WScript.CreateObject('WScript.Shell');
|
|
var fs = new ActiveXObject('Scripting.FileSystemObject');
|
|
var pathPlugins = shell.ExpandEnvironmentStrings('%APPDATA%\\BetterDiscord\\plugins');
|
|
var pathSelf = WScript.ScriptFullName;
|
|
// Put the user at ease by addressing them in the first person
|
|
shell.Popup('It looks like you\'ve mistakenly tried to run me directly. \n(Don\'t do that!)', 0, 'I\'m a plugin for BetterDiscord', 0x30);
|
|
if (fs.GetParentFolderName(pathSelf) === fs.GetAbsolutePathName(pathPlugins)) {
|
|
shell.Popup('I\'m in the correct folder already.\nJust reload Discord with Ctrl+R.', 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!\nJust reload Discord with Ctrl+R.', 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.
|
|
*/
|
|
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.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);
|
|
};
|
|
|
|
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 .text-2sI5Sd {
|
|
opacity: 0;
|
|
transform: translate3d(200%,0,0);
|
|
}
|
|
.xenoLib-color-picker .xenoLib-button:hover .text-2sI5Sd {
|
|
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.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.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));
|
|
});
|
|
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
|
|
*/
|
|
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));
|
|
}
|
|
});
|
|
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 })));
|
|
}
|
|
};
|
|
|
|
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');
|
|
|
|
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.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<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 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
|
|
})
|
|
)
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
};
|
|
};
|
|
|
|
/* 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}<br/>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.<br/><br/><a href="https://rauenzi.github.io/BDPluginLibrary/release/0PluginLibrary.plugin.js"target="_blank">Click here to download ZeresPluginLibrary</a>`);
|
|
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@*/
|