Upload files to 'Plugins'
This commit is contained in:
parent
d36041b8c2
commit
af8fbe66e4
5 changed files with 5842 additions and 0 deletions
2079
Plugins/1XenoLib.plugin.js
Normal file
2079
Plugins/1XenoLib.plugin.js
Normal file
File diff suppressed because it is too large
Load diff
2207
Plugins/BetterImageViewer.plugin.js
Normal file
2207
Plugins/BetterImageViewer.plugin.js
Normal file
File diff suppressed because it is too large
Load diff
510
Plugins/BetterTypingUsers.plugin.js
Normal file
510
Plugins/BetterTypingUsers.plugin.js
Normal file
|
@ -0,0 +1,510 @@
|
|||
//META{"name":"BetterTypingUsers","source":"https://github.com/1Lighty/BetterDiscordPlugins/blob/master/Plugins/BetterTypingUsers/BetterTypingUsers.plugin.js","website":"https://1lighty.github.io/BetterDiscordStuff/?plugin=BetterTypingUsers","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.\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.
|
||||
*/
|
||||
module.exports = (() => {
|
||||
/* Setup */
|
||||
const config = {
|
||||
main: 'index.js',
|
||||
info: {
|
||||
name: 'BetterTypingUsers',
|
||||
authors: [
|
||||
{
|
||||
name: 'Lighty',
|
||||
discord_id: '239513071272329217',
|
||||
github_username: 'LightyPon',
|
||||
twitter_username: ''
|
||||
}
|
||||
],
|
||||
version: '1.0.3',
|
||||
description: 'Replaces "Several people are typing" with who is actually typing, plus "x others" if it can\'t fit. Number of shown people typing can be changed.',
|
||||
github: 'https://github.com/1Lighty',
|
||||
github_raw: 'https://raw.githubusercontent.com/1Lighty/BetterDiscordPlugins/master/Plugins/BetterTypingUsers/BetterTypingUsers.plugin.js'
|
||||
},
|
||||
changelog: [
|
||||
{
|
||||
title: 'RIP BBD on Canary',
|
||||
type: 'fixed',
|
||||
items: ['Implemented fixes that allow patches to work properly on canary using Powercord.']
|
||||
}
|
||||
],
|
||||
defaultConfig: [
|
||||
{
|
||||
name: 'Max visible typing users',
|
||||
id: 'maxVisible',
|
||||
type: 'slider',
|
||||
value: 5,
|
||||
min: 3,
|
||||
max: 20,
|
||||
markers: Array.from(Array(18), (_, i) => i + 3),
|
||||
stickToMarkers: true
|
||||
},
|
||||
{
|
||||
name: 'Previews',
|
||||
type: 'preview'
|
||||
}
|
||||
],
|
||||
strings: {
|
||||
en: { AND_1_OTHER: ' and 1 other', AND_X_OTHERS: ' and ${count} others', AND: ' and ', ARE_TYPING: ' are typing...' },
|
||||
de: { AND_1_OTHER: ' und 1 andere', AND_X_OTHERS: ' und ${count} andere', AND: ' und ', ARE_TYPING: ' schreiben...' },
|
||||
da: { AND_1_OTHER: ' og 1 anden', AND_X_OTHERS: ' og ${count} andre', AND: ' og ', ARE_TYPING: ' skriver...' },
|
||||
es: { AND_1_OTHER: ' y 1 otro', AND_X_OTHERS: ' y otros ${count}', AND: ' y ', ARE_TYPING: ' están escribiendo...' },
|
||||
fr: { AND_1_OTHER: ' et 1 autre', AND_X_OTHERS: ' et ${count} autres', AND: ' et ', ARE_TYPING: ' écrivent...' },
|
||||
hr: { AND_1_OTHER: ' i 1 drugi', AND_X_OTHERS: ' i ${count} drugih', AND: ' i ', ARE_TYPING: ' pišu...' },
|
||||
it: { AND_1_OTHER: ' e 1 altro', AND_X_OTHERS: ' e altri ${count}', AND: ' e ', ARE_TYPING: ' stanno scrivendo...' },
|
||||
tr: { AND_1_OTHER: ' ve 1 kişi daha', AND_X_OTHERS: ' ve ${count} kişi daha', AND: ' ve ', ARE_TYPING: ' yazıyor...' }
|
||||
}
|
||||
};
|
||||
|
||||
/* 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, SwitchRow, EmojiUtils, RadioGroup, Permissions, TextElement, FlexChild, PopoutOpener, Textbox, RelationshipStore, UserSettingsStore } = DiscordModules;
|
||||
|
||||
const rendererFunctionClass = (() => {
|
||||
try {
|
||||
const topContext = require('electron').webFrame.top.context;
|
||||
if (topContext === window) return null;
|
||||
return topContext.Function
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
})();
|
||||
const originalFunctionClass = Function;
|
||||
function createSmartPatcher(patcher) {
|
||||
const createPatcher = patcher => {
|
||||
return (moduleToPatch, functionName, callback, options = {}) => {
|
||||
try {
|
||||
var origDef = moduleToPatch[functionName];
|
||||
} catch (_) {
|
||||
return Logger.error(`Failed to patch ${functionName}`);
|
||||
}
|
||||
if (rendererFunctionClass && origDef && !(origDef instanceof originalFunctionClass) && origDef instanceof rendererFunctionClass) window.Function = rendererFunctionClass;
|
||||
const unpatches = [];
|
||||
try {
|
||||
unpatches.push(patcher(moduleToPatch, functionName, callback, options) || DiscordConstants.NOOP);
|
||||
} catch (err) {
|
||||
throw err;
|
||||
} finally {
|
||||
if (rendererFunctionClass) window.Function = originalFunctionClass;
|
||||
}
|
||||
try {
|
||||
if (origDef && 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 = createSmartPatcher(Api.Patcher);
|
||||
|
||||
const NameUtils = WebpackModules.getByProps('getName');
|
||||
|
||||
const CUser = WebpackModules.getByPrototypes('getAvatarSource', 'isLocalBot');
|
||||
const CChannel = WebpackModules.getByPrototypes('isGroupDM', 'isMultiUserDM');
|
||||
const L337 = (() => {
|
||||
try {
|
||||
return new CChannel({ id: '1337' });
|
||||
} catch (e) {
|
||||
Logger.stacktrace('Failed to create 1337 channel', e);
|
||||
}
|
||||
})();
|
||||
|
||||
let CTypingUsers = (() => {
|
||||
try {
|
||||
const WrappedTypingUsers = WebpackModules.find(m => m.displayName && m.displayName.indexOf('TypingUsers') !== -1);
|
||||
return new WrappedTypingUsers({ channel: L337 }).render().type;
|
||||
} catch (e) {
|
||||
Logger.stacktrace('Failed to get TypingUsers!', e);
|
||||
return null;
|
||||
}
|
||||
})();
|
||||
|
||||
const ComponentDispatch = (() => {
|
||||
try {
|
||||
return WebpackModules.getByProps('ComponentDispatch').ComponentDispatch;
|
||||
} catch (e) {
|
||||
Logger.stacktrace('Failed to get ComponentDispatch', e);
|
||||
}
|
||||
})();
|
||||
|
||||
class CTypingUsersPreview extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.forceUpdate = this.forceUpdate.bind(this);
|
||||
const iUsers = UserStore.getUsers();
|
||||
for (let i = 0; i < 20; i++) iUsers[(1337 + i).toString()] = new CUser({ username: `User ${i + 1}`, id: (1337 + i).toString(), discriminator: '9999' });
|
||||
}
|
||||
componentDidMount() {
|
||||
ComponentDispatch.subscribe('BTU_SETTINGS_UPDATED', this.forceUpdate);
|
||||
}
|
||||
componentWillUnmount() {
|
||||
ComponentDispatch.unsubscribe('BTU_SETTINGS_UPDATED', this.forceUpdate);
|
||||
const iUsers = UserStore.getUsers();
|
||||
for (let i = 0; i < 20; i++) delete iUsers[(1337 + i).toString()];
|
||||
}
|
||||
renderTyping(num) {
|
||||
const typingUsers = {};
|
||||
for (let i = 0; i < num; i++) typingUsers[(1337 + i).toString()] = 1;
|
||||
return React.createElement(CTypingUsers, {
|
||||
channel: L337,
|
||||
guildId: '',
|
||||
isFocused: true,
|
||||
slowmodeCooldownGuess: 0,
|
||||
theme: UserSettingsStore.theme,
|
||||
typingUsers
|
||||
});
|
||||
}
|
||||
render() {
|
||||
return React.createElement(
|
||||
'div',
|
||||
{
|
||||
className: 'BTU-preview'
|
||||
},
|
||||
this.renderTyping(4),
|
||||
this.renderTyping(6),
|
||||
this.renderTyping(20)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TypingUsersPreview extends Settings.SettingField {
|
||||
constructor(name, note) {
|
||||
super(name, note, null, CTypingUsersPreview);
|
||||
}
|
||||
}
|
||||
|
||||
/* since XenoLib is absent from this plugin (since it serves no real purpose),
|
||||
we can only hope the user doesn't rename the plugin..
|
||||
*/
|
||||
return class BetterTypingUsers extends Plugin {
|
||||
constructor() {
|
||||
super();
|
||||
try {
|
||||
WebpackModules.getByProps('openModal', 'hasModalOpen').closeModal(`${this.name}_DEP_MODAL`);
|
||||
} catch (e) { }
|
||||
}
|
||||
onStart() {
|
||||
this.promises = { state: { cancelled: false } };
|
||||
this.patchAll();
|
||||
PluginUtilities.addStyle(
|
||||
this.short + '-CSS',
|
||||
`
|
||||
.BTU-preview > .${WebpackModules.getByProps('slowModeIcon', 'typing').typing.split(' ')[0]} {
|
||||
position: unset !important;
|
||||
}
|
||||
`
|
||||
);
|
||||
DiscordConstants.MAX_TYPING_USERS = 99;
|
||||
/* theoretical max is 5 users typing at once.. welp */
|
||||
}
|
||||
|
||||
onStop() {
|
||||
this.promises.state.cancelled = true;
|
||||
Patcher.unpatchAll();
|
||||
PluginUtilities.removeStyle(this.short + '-CSS');
|
||||
}
|
||||
|
||||
/* zlib uses reference to defaultSettings instead of a cloned object, which sets settings as default settings, messing everything up */
|
||||
loadSettings(defaultSettings) {
|
||||
return PluginUtilities.loadSettings(this.name, Utilities.deepclone(this.defaultSettings ? this.defaultSettings : defaultSettings));
|
||||
}
|
||||
|
||||
buildSetting(data) {
|
||||
if (data.type === 'preview') return new TypingUsersPreview(data.name, data.note);
|
||||
return super.buildSetting(data);
|
||||
}
|
||||
|
||||
saveSettings(_, setting, value) {
|
||||
super.saveSettings(_, setting, value);
|
||||
ComponentDispatch.safeDispatch('BTU_SETTINGS_UPDATED');
|
||||
}
|
||||
|
||||
filterTypingUsers(typingUsers) {
|
||||
return Object.keys(typingUsers)
|
||||
.filter(e => e != DiscordAPI.currentUser.id)
|
||||
.filter(e => !RelationshipStore.isBlocked(e))
|
||||
.map(e => UserStore.getUser(e))
|
||||
.filter(e => e != null);
|
||||
}
|
||||
|
||||
/* PATCHES */
|
||||
|
||||
patchAll() {
|
||||
Utilities.suppressErrors(this.patchBetterRoleColors.bind(this), 'BetterRoleColors patch')();
|
||||
Utilities.suppressErrors(this.patchTypingUsers.bind(this), 'TypingUsers patch')(this.promises.state);
|
||||
}
|
||||
|
||||
patchBetterRoleColors() {
|
||||
const BetterRoleColors = BdApi.Plugins.get('BetterRoleColors');
|
||||
if (!BetterRoleColors) return;
|
||||
/* stop errors */
|
||||
/* modify BRCs behavior so it won't unexpectedly try to modify an entry that does not exist
|
||||
by simply limiting it to the max number of usernames visible in total
|
||||
*/
|
||||
Patcher.after(BetterRoleColors, 'filterTypingUsers', (_this, __, ret) => ret.slice(0, this.settings.maxVisible));
|
||||
}
|
||||
|
||||
async patchTypingUsers(promiseState) {
|
||||
const TypingUsers = await ReactComponents.getComponentByName('TypingUsers', DiscordSelectors.Typing.typing);
|
||||
if (!TypingUsers.selector) TypingUsers.selector = DiscordSelectors.Typing.typing;
|
||||
const TypingTextClassname = WebpackModules.getByProps('typing', 'text').text.split(' ')[0];
|
||||
if (promiseState.cancelled) return;
|
||||
if (!CTypingUsers) CTypingUsers = typingUsers.component; /* failsafe */
|
||||
/* use `instead` so that we modify the return before BetterRoleColors */
|
||||
/* Patcher.after(TypingUsers.component.prototype, 'componentDidUpdate', (_this, [props, state], ret) => {
|
||||
const filtered1 = this.filterTypingUsers(_this.props.typingUsers);
|
||||
const filtered2 = this.filterTypingUsers(props.typingUsers);
|
||||
if (filtered1.length !== filtered2.length || _this.state.numLess === state.numLess) {
|
||||
_this.state.numLess = 0;
|
||||
_this.triedLess = false;
|
||||
_this.triedMore = false;
|
||||
}
|
||||
}); */
|
||||
Patcher.instead(TypingUsers.component.prototype, 'render', (_this, _, orig) => {
|
||||
/* if (!_this.state) _this.state = { numLess: 0 }; */
|
||||
const ret = orig();
|
||||
if (!ret) {
|
||||
/* _this.state.numLess = 0; */
|
||||
return ret;
|
||||
}
|
||||
const filtered = this.filterTypingUsers(_this.props.typingUsers);
|
||||
if (filtered.length <= 3) return ret;
|
||||
/* ret.ref = e => {
|
||||
_this.__baseRef = e;
|
||||
if (!e) return;
|
||||
if (!_this.__textRef) return;
|
||||
_this.maxWidth = parseInt(getComputedStyle(_this.__baseRef.parentElement).width) - (_this.__textRef.offsetLeft + parseInt(getComputedStyle(_this.__textRef)['margin-left']) - _this.__baseRef.offsetLeft);
|
||||
if (_this.__textRef.scrollWidth > _this.maxWidth) {
|
||||
if (_this.triedMore) return;
|
||||
if (filtered.length - _this.state.numLess <= 3) return;
|
||||
_this.setState({ numLess: _this.state.numLess + 1 });
|
||||
}
|
||||
}; */
|
||||
const typingUsers = Utilities.findInReactTree(ret, e => e && e.props && typeof e.props.className === 'string' && e.props.className.indexOf(TypingTextClassname) !== -1);
|
||||
if (!typingUsers) return ret;
|
||||
/* if (typeof _this.state.numLess !== 'number') _this.state.numLess = 0;
|
||||
typingUsers.ref = e => {
|
||||
_this.__textRef = e;
|
||||
}; */
|
||||
typingUsers.props.children = [];
|
||||
/* I don't think this method works for every language..? */
|
||||
for (let i = 0; i < filtered.length; i++) {
|
||||
if (this.settings.maxVisible /* filtered.length - _this.state.numLess */ === i) {
|
||||
const others = filtered.length - i;
|
||||
if (others === 1) typingUsers.props.children.push(this.strings.AND_1_OTHER);
|
||||
else typingUsers.props.children.push(Utilities.formatTString(this.strings.AND_X_OTHERS, { count: others }));
|
||||
break;
|
||||
} else if (i === filtered.length - 1) typingUsers.props.children.push(this.strings.AND);
|
||||
else if (i !== 0) typingUsers.props.children.push(', ');
|
||||
const name = NameUtils.getName(_this.props.guildId, _this.props.channel.id, filtered[i]);
|
||||
typingUsers.props.children.push(React.createElement('strong', {}, name));
|
||||
}
|
||||
typingUsers.props.children.push(this.strings.ARE_TYPING);
|
||||
return ret;
|
||||
});
|
||||
TypingUsers.forceUpdateAll();
|
||||
}
|
||||
|
||||
/* PATCHES */
|
||||
|
||||
getSettingsPanel() {
|
||||
return this.buildSettingsPanel().getElement();
|
||||
}
|
||||
|
||||
get [Symbol.toStringTag]() {
|
||||
return 'Plugin';
|
||||
}
|
||||
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 {
|
||||
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.Plugins.get('ZeresPluginLibrary');
|
||||
((b, c) => b && b._config && b._config.info && b._config.info.version && a(b._config.info.version, c))(b, '1.2.27') && (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 [Symbol.toStringTag]() {
|
||||
return 'Plugin';
|
||||
}
|
||||
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@*/
|
519
Plugins/BetterUnavailableGuilds.plugin.js
Normal file
519
Plugins/BetterUnavailableGuilds.plugin.js
Normal file
|
@ -0,0 +1,519 @@
|
|||
//META{"name":"BetterUnavailableGuilds","source":"https://github.com/1Lighty/BetterDiscordPlugins/blob/master/Plugins/BetterUnavailableGuilds/","website":"https://1lighty.github.io/BetterDiscordStuff/?plugin=BetterUnavailableGuilds","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.\nJust go to settings, plugins and enable me.', 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 go to settings, plugins and enable me!', 0, 'Successfully installed', 0x40);
|
||||
}
|
||||
WScript.Quit();
|
||||
|
||||
@else@*/
|
||||
/*
|
||||
* Copyright © 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: 'BetterUnavailableGuilds',
|
||||
authors: [
|
||||
{
|
||||
name: 'Lighty',
|
||||
discord_id: '239513071272329217',
|
||||
github_username: '1Lighty',
|
||||
twitter_username: ''
|
||||
}
|
||||
],
|
||||
version: '0.2.9',
|
||||
description: 'Force Discord to show server icons of unavailable servers, instead of "1 server is unavailable" and enable interaction with the server (ability to leave the server, move it around, etc).',
|
||||
github: 'https://github.com/1Lighty',
|
||||
github_raw: 'https://raw.githubusercontent.com/1Lighty/BetterDiscordPlugins/master/Plugins/BetterUnavailableGuilds/BetterUnavailableGuilds.plugin.js'
|
||||
},
|
||||
changelog: [
|
||||
{
|
||||
title: 'fixed',
|
||||
type: 'fixed',
|
||||
items: ['Fixed not restoring the servers on startup.']
|
||||
}
|
||||
],
|
||||
defaultConfig: [
|
||||
{
|
||||
type: 'category',
|
||||
id: 'guilds',
|
||||
name: 'Guilds',
|
||||
collapsible: true,
|
||||
shown: true,
|
||||
settings: [
|
||||
{
|
||||
type: 'textbox',
|
||||
name: 'Add guild using data',
|
||||
note: 'Press enter to add'
|
||||
},
|
||||
{
|
||||
type: 'guildslist',
|
||||
name: 'Click to copy guild data to share to people'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
/* Build */
|
||||
const buildPlugin = ([Plugin, Api]) => {
|
||||
const { Utilities, WebpackModules, DiscordModules, Patcher, PluginUtilities, DiscordAPI, Settings, Toasts } = Api;
|
||||
const { Dispatcher, GuildStore, React, ModalStack } = DiscordModules;
|
||||
const GuildAvailabilityStore = WebpackModules.getByProps('unavailableGuilds');
|
||||
const _ = WebpackModules.getByProps('bindAll', 'debounce');
|
||||
|
||||
const FsModule = require('fs');
|
||||
// I would say "fuck ED", but it won't even compile on ED due to their piss poor BD/BBD support, lol
|
||||
const pluginConfigFile = require('path').resolve(BdApi.Plugins.folder, config.info.name + '.config.json');
|
||||
|
||||
const loadData = (key, defaults) => {
|
||||
const cloned = _.cloneDeep(defaults);
|
||||
try {
|
||||
if (pluginConfigFile) {
|
||||
if (FsModule.existsSync(pluginConfigFile)) {
|
||||
return Object.assign(cloned, JSON.parse(FsModule.readFileSync(pluginConfigFile))[key]);
|
||||
} else {
|
||||
return cloned;
|
||||
}
|
||||
} else {
|
||||
return Object.assign(cloned, BdApi.loadData(config.info.name, key));
|
||||
}
|
||||
} catch (e) {
|
||||
return cloned;
|
||||
}
|
||||
};
|
||||
|
||||
const copyToClipboard = WebpackModules.getByProps('copy').copy; /* Possible error in future, TODO: safeguard */
|
||||
const GuildIconWrapper = WebpackModules.getByDisplayName('GuildIconWrapper');
|
||||
const ListClassModule = WebpackModules.getByProps('listRowContent', 'listAvatar');
|
||||
const ListScrollerClassname = WebpackModules.getByProps('listScroller').listScroller; /* Possible error in future, TODO: safeguard */
|
||||
const VerticalScroller = WebpackModules.getByDisplayName('VerticalScroller');
|
||||
const Clickable = WebpackModules.getByDisplayName('Clickable');
|
||||
|
||||
/* TODO: proper name for the classes */
|
||||
class GL extends React.PureComponent {
|
||||
renderGuild(guild) {
|
||||
if (!guild) return null;
|
||||
return React.createElement(
|
||||
Clickable,
|
||||
{
|
||||
onClick: () => {
|
||||
copyToClipboard(JSON.stringify({ id: guild.id, icon: guild.icon || undefined, name: guild.name, owner_id: guild.ownerId, joined_at: guild.joinedAt.valueOf() }));
|
||||
Toasts.success(`Copied ${guild.name}!`);
|
||||
},
|
||||
className: 'BUG-guild-icon'
|
||||
},
|
||||
React.createElement(GuildIconWrapper, {
|
||||
guild,
|
||||
showBadge: !0,
|
||||
className: !guild.icon ? ListClassModule.guildAvatarWithoutIcon : '',
|
||||
size: GuildIconWrapper.Sizes.LARGE
|
||||
})
|
||||
);
|
||||
}
|
||||
render() {
|
||||
return React.createElement(
|
||||
VerticalScroller,
|
||||
{
|
||||
fade: true,
|
||||
className: ListScrollerClassname,
|
||||
style: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap'
|
||||
}
|
||||
},
|
||||
Object.values(GuildStore.getGuilds()).map(this.renderGuild)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TB extends DiscordModules.Textbox {
|
||||
render() {
|
||||
const ret = super.render();
|
||||
const props = Utilities.findInReactTree(ret, e => e && e.onEnterPressed);
|
||||
props.onKeyDown = e => {
|
||||
if (e.keyCode !== 13) return;
|
||||
try {
|
||||
const parsed = JSON.parse(this.props.value);
|
||||
['id', 'name', 'owner_id', 'joined_at'].forEach(prop => {
|
||||
if (!parsed.hasOwnProperty(prop) || typeof parsed[prop] === 'undefined') throw `Malformed guild data (${prop})`;
|
||||
});
|
||||
if (typeof parsed.name !== 'string' || typeof parsed.owner_id !== 'string' || /\\d+$/.test(parsed.owner_id)) throw 'Malformed guild data';
|
||||
this.props.onEnterPressed(parsed);
|
||||
this.props.onChange('');
|
||||
} catch (err) {
|
||||
Toasts.error(`Failed to parse: ${err.message || err}`);
|
||||
}
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
class Textbox extends Settings.SettingField {
|
||||
constructor(name, note, onChange, options = {}) {
|
||||
super(name, note, () => { }, TB, {
|
||||
onChange: textbox => value => {
|
||||
textbox.props.value = value;
|
||||
textbox.forceUpdate();
|
||||
},
|
||||
value: '',
|
||||
placeholder: options.placeholder ? options.placeholder : '',
|
||||
onEnterPressed: onChange
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class GuildDataCopier extends Settings.SettingField {
|
||||
constructor(name, note) {
|
||||
super(name, note, () => { }, GL, {});
|
||||
}
|
||||
}
|
||||
|
||||
return class BetterUnavailableGuilds extends Plugin {
|
||||
constructor() {
|
||||
super();
|
||||
try {
|
||||
WebpackModules.getByProps('openModal', 'hasModalOpen').closeModal(`${this.name}_DEP_MODAL`);
|
||||
} catch (e) { }
|
||||
this._dispatches = ['CONNECTION_OPEN'];
|
||||
_.bindAll(this, ['handleGuildStoreChange', 'verifyAllServersCachedInClient', ...this._dispatches]);
|
||||
// different timings for clients to avoid fighting over a damn config file
|
||||
this.handleGuildStoreChange = _.throttle(this.handleGuildStoreChange, 15000 + (GLOBAL_ENV.RELEASE_CHANNEL === 'ptb' ? 2500 : GLOBAL_ENV.RELEASE_CHANNEL === 'canary' ? 5000 : 0));
|
||||
}
|
||||
onStart() {
|
||||
this._guildRecord = loadData('data', { data: {} }).data;
|
||||
this.verifyAllServersCachedInClient();
|
||||
GuildStore.addChangeListener(this.handleGuildStoreChange);
|
||||
this.handleGuildStoreChange();
|
||||
this.patchAll();
|
||||
for (const dispatch of this._dispatches) Dispatcher.subscribe(dispatch, this[dispatch]);
|
||||
PluginUtilities.addStyle(
|
||||
this.short + '-CSS',
|
||||
`
|
||||
.BUG-guild-icon {
|
||||
padding: 5px;
|
||||
}
|
||||
.BUG-guild-icon:hover {
|
||||
background-color: var(--background-modifier-hover);
|
||||
}
|
||||
`
|
||||
);
|
||||
}
|
||||
onStop() {
|
||||
GuildStore.removeChangeListener(this.handleGuildStoreChange);
|
||||
Patcher.unpatchAll();
|
||||
Dispatcher._computeOrderedActionHandlers('GUILD_DELETE');
|
||||
for (const dispatch of this._dispatches) Dispatcher.unsubscribe(dispatch, this[dispatch]);
|
||||
PluginUtilities.removeStyle(this.short + '-CSS');
|
||||
}
|
||||
|
||||
buildSetting(data) {
|
||||
if (data.type === 'textbox') {
|
||||
const { name, note } = data;
|
||||
const setting = new Textbox(
|
||||
name,
|
||||
note,
|
||||
guild => {
|
||||
if (this.guildRecord[guild.id]) throw 'Guild already exists';
|
||||
if (GuildAvailabilityStore.unavailableGuilds.indexOf(guild.id)) throw 'You are not a member of ' + guild.name;
|
||||
this.guildRecord[guild.id] = { id: guild.id, icon: guild.icon || undefined, name: guild.name, owner_id: guild.owner_id, joined_at: guild.joined_at, default_message_notifications: guild.default_message_notifications };
|
||||
this.verifyAllServersCachedInClient();
|
||||
Toasts.success('Added!');
|
||||
},
|
||||
{ placeholder: data.placeholder || '' }
|
||||
);
|
||||
return setting;
|
||||
} else if (data.type === 'guildslist') {
|
||||
return new GuildDataCopier(data.name, data.note);
|
||||
}
|
||||
return super.buildSetting(data);
|
||||
}
|
||||
|
||||
verifyAllServersCachedInClient() {
|
||||
if (!DiscordAPI.currentUser) return; /* hhhhhhhh */
|
||||
Dispatcher.wait(() => {
|
||||
this.ensureBDGuildsPreCached();
|
||||
this._verifying = true;
|
||||
const unavailable = _.cloneDeep(GuildAvailabilityStore.unavailableGuilds);
|
||||
unavailable.forEach(guildId => {
|
||||
if (!this.guildRecord[guildId] || GuildStore.getGuild(guildId)) return;
|
||||
Dispatcher.dispatch({
|
||||
type: 'GUILD_CREATE',
|
||||
guild: Object.assign(
|
||||
{
|
||||
icon: null,
|
||||
presences: [],
|
||||
channels: [],
|
||||
members: [],
|
||||
roles: [],
|
||||
unavailable: true
|
||||
},
|
||||
this.guildRecord[guildId]
|
||||
)
|
||||
});
|
||||
/* they're still unavailable, remember? */
|
||||
Dispatcher.dispatch({
|
||||
type: 'GUILD_UNAVAILABLE',
|
||||
guildId: guildId
|
||||
});
|
||||
});
|
||||
this._verifying = false;
|
||||
});
|
||||
}
|
||||
|
||||
CONNECTION_OPEN(e) {
|
||||
/* websocket died, user logged in, user logged into another account etc */
|
||||
this.verifyAllServersCachedInClient();
|
||||
}
|
||||
|
||||
ensureBDGuildsPreCached() {
|
||||
this.guildRecord['86004744966914048'] = { id: '86004744966914048', icon: '292e7f6bfff2b71dfd13e508a859aedd', name: 'BetterDiscord', owner_id: '81388395867156480', joined_at: Date.now() };
|
||||
this.guildRecord['280806472928198656'] = { id: '280806472928198656', icon: 'cbdda04c041699d80689b99c4e5e89dc', name: 'BetterDiscord2', owner_id: '81388395867156480', joined_at: Date.now() };
|
||||
}
|
||||
|
||||
ensureDataSettable() {
|
||||
if (!this._guildRecord[DiscordAPI.currentUser.id]) this._guildRecord[DiscordAPI.currentUser.id] = {};
|
||||
if (!this._guildRecord[DiscordAPI.currentUser.id][GLOBAL_ENV.RELEASE_CHANNEL]) {
|
||||
const curUserShit = this._guildRecord[DiscordAPI.currentUser.id];
|
||||
/* transfer the data */
|
||||
if (curUserShit['stable']) curUserShit[GLOBAL_ENV.RELEASE_CHANNEL] = _.cloneDeep(curUserShit['stable']);
|
||||
else if (curUserShit['ptb']) curUserShit[GLOBAL_ENV.RELEASE_CHANNEL] = _.cloneDeep(curUserShit['ptb']);
|
||||
else if (curUserShit['canary']) curUserShit[GLOBAL_ENV.RELEASE_CHANNEL] = _.cloneDeep(curUserShit['canary']);
|
||||
else curUserShit[GLOBAL_ENV.RELEASE_CHANNEL] = {};
|
||||
}
|
||||
}
|
||||
|
||||
get guildRecord() {
|
||||
this.ensureDataSettable();
|
||||
const ret = this._guildRecord[DiscordAPI.currentUser.id][GLOBAL_ENV.RELEASE_CHANNEL];
|
||||
return ret;
|
||||
}
|
||||
set guildRecord(val) {
|
||||
this.ensureDataSettable();
|
||||
return (this._guildRecord[DiscordAPI.currentUser.id][GLOBAL_ENV.RELEASE_CHANNEL] = val);
|
||||
}
|
||||
|
||||
handleGuildStoreChange() {
|
||||
if (!DiscordAPI.currentUser) return; /* hhhhhhhh */
|
||||
this._guildRecord = loadData('data', { data: {} }).data;
|
||||
this.verifyAllServersCachedInClient();
|
||||
const availableGuilds = Object.values(GuildStore.getGuilds()).map(guild => ({
|
||||
id: guild.id,
|
||||
icon: guild.icon || undefined,
|
||||
name: guild.name,
|
||||
owner_id: guild.ownerId,
|
||||
joined_at: guild.joinedAt ? guild.joinedAt.valueOf() /* int value is fine too */ : 0 /* wut? MasicoreLord experienced a weird bug with joinedAt being undefined */
|
||||
}));
|
||||
let guilds = {};
|
||||
GuildAvailabilityStore.unavailableGuilds.forEach(id => this.guildRecord[id] && (guilds[id] = this.guildRecord[id]));
|
||||
availableGuilds.forEach(guild => (guilds[guild.id] = guild));
|
||||
for (const guildId in guilds) guilds[guildId] = _.pickBy(guilds[guildId], e => !_.isUndefined(e));
|
||||
if (!_.isEqual(this.guildRecord, guilds)) {
|
||||
this.guildRecord = guilds;
|
||||
PluginUtilities.saveData(this.name, 'data', { data: this._guildRecord });
|
||||
}
|
||||
}
|
||||
|
||||
/* PATCHES */
|
||||
|
||||
patchAll() {
|
||||
Utilities.suppressErrors(this.patchGuildDelete.bind(this), 'GUILD_DELETE dispatch patch')();
|
||||
}
|
||||
|
||||
patchGuildDelete() {
|
||||
// super sekret (not really) V3/rewrite patch code
|
||||
for (const id in Dispatcher._dependencyGraph.nodes) {
|
||||
const node = Dispatcher._dependencyGraph.nodes[id];
|
||||
if (!node.actionHandler['GUILD_DELETE']) continue;
|
||||
Patcher.instead(node.actionHandler, 'GUILD_DELETE', (_, [dispatch], orig) => {
|
||||
if (!dispatch.guild.unavailable) return orig(dispatch);
|
||||
});
|
||||
}
|
||||
Dispatcher._computeOrderedActionHandlers('GUILD_DELETE');
|
||||
}
|
||||
|
||||
/* PATCHES */
|
||||
|
||||
getSettingsPanel() {
|
||||
return this.buildSettingsPanel().getElement();
|
||||
}
|
||||
|
||||
get [Symbol.toStringTag]() {
|
||||
return 'Plugin';
|
||||
}
|
||||
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 {
|
||||
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.Plugins.get('ZeresPluginLibrary');
|
||||
((b, c) => b && b._config && b._config.info && b._config.info.version && a(b._config.info.version, c))(b, '1.2.27') && (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 [Symbol.toStringTag]() {
|
||||
return 'Plugin';
|
||||
}
|
||||
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@*/
|
527
Plugins/CrashRecovery.plugin.js
Normal file
527
Plugins/CrashRecovery.plugin.js
Normal file
|
@ -0,0 +1,527 @@
|
|||
//META{"name":"CrashRecovery","source":"https://github.com/1Lighty/BetterDiscordPlugins/blob/master/Plugins/CrashRecovery/","website":"https://1lighty.github.io/BetterDiscordStuff/?plugin=CrashRecovery","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.\nJust go to settings, plugins and enable me.', 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 go to settings, plugins and enable me!', 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: 'CrashRecovery',
|
||||
authors: [
|
||||
{
|
||||
name: 'Lighty',
|
||||
discord_id: '239513071272329217',
|
||||
github_username: '1Lighty',
|
||||
twitter_username: ''
|
||||
}
|
||||
],
|
||||
version: '1.0.3',
|
||||
description: 'In the event that your Discord crashes, the plugin enables you to get Discord back to a working state, without needing to reload at all.',
|
||||
github: 'https://github.com/1Lighty',
|
||||
github_raw: 'https://raw.githubusercontent.com/1Lighty/BetterDiscordPlugins/master/Plugins/CrashRecovery/CrashRecovery.plugin.js'
|
||||
},
|
||||
changelog: [
|
||||
{
|
||||
title: 'RIP BBD on Canary',
|
||||
type: 'fixed',
|
||||
items: ['Implemented fixes that allow patches to work properly on canary using Powercord.']
|
||||
}
|
||||
],
|
||||
defaultConfig: [
|
||||
{
|
||||
name: 'Enable step 3',
|
||||
note: 'Moves channel switch to a third and last step, otherwise it switches on step 2',
|
||||
id: 'useThirdStep',
|
||||
type: 'switch',
|
||||
value: true
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
/* Build */
|
||||
const buildPlugin = ([Plugin, Api], BasePlugin) => {
|
||||
const { Logger, Utilities, WebpackModules, DiscordModules, PluginUtilities, ReactTools, PluginUpdater } = Api;
|
||||
const { React, Dispatcher, FlexChild: Flex, GuildStore } = DiscordModules;
|
||||
|
||||
const Patcher = XenoLib.createSmartPatcher(Api.Patcher);
|
||||
|
||||
const DelayedCall = (WebpackModules.getByProps('DelayedCall') || {}).DelayedCall;
|
||||
const ElectronDiscordModule = WebpackModules.getByProps('cleanupDisplaySleep') || { cleanupDisplaySleep: DiscordModules.DiscordConstants.NOOP };
|
||||
|
||||
const ModalStack = WebpackModules.getByProps('openModal');
|
||||
|
||||
const isPowercord = !!window.powercord;
|
||||
const BLACKLISTED_BUILTIN_PC_PLUGINS = ['Updater', 'Commands Manager', 'I18n', 'Module Manager', 'Settings']
|
||||
const RE_PC_PLUGIN_NAME_FROM_PATH = /[\\\/]plugins[\\\/]([^\\\/]+)/;
|
||||
|
||||
const RE_INVARIANT = /error-decoder.html\?invariant=(\d+)([^\s]*)/;
|
||||
const INVARIANTS_URL = 'https://raw.githubusercontent.com/facebook/react/master/scripts/error-codes/codes.json';
|
||||
const ROOT_FOLDER = isPowercord ? window.powercord.basePath : BdApi.Plugins.folder;
|
||||
|
||||
const path = require('path');
|
||||
|
||||
return class CrashRecovery extends BasePlugin(Plugin) {
|
||||
constructor() {
|
||||
super();
|
||||
XenoLib.changeName(__filename, 'CrashRecovery');
|
||||
this._startFailure = message => {
|
||||
PluginUpdater.checkForUpdate(this.name, this.version, this._config.info.github_raw);
|
||||
XenoLib.Notifications.error(`[**${this.name}**] ${message} Please update it, press CTRL + R, or ${GuildStore.getGuild(XenoLib.supportServerId) ? 'go to <#639665366380838924>' : '[join my support server](https://discord.gg/NYvWdN5)'} for further assistance.`, { timeout: 0 });
|
||||
};
|
||||
const oOnStart = this.onStart.bind(this);
|
||||
this.onStart = () => {
|
||||
try {
|
||||
oOnStart();
|
||||
} catch (e) {
|
||||
Logger.stacktrace('Failed to start!', e);
|
||||
this._startFailure('Failed to start!');
|
||||
try {
|
||||
this.onStop();
|
||||
} catch (e) { }
|
||||
}
|
||||
};
|
||||
try {
|
||||
WebpackModules.getByProps('openModal', 'hasModalOpen').closeModal(`${this.name}_DEP_MODAL`);
|
||||
} catch (e) { }
|
||||
}
|
||||
onStart() {
|
||||
this.attempts = 0;
|
||||
this.promises = { state: { cancelled: false } };
|
||||
if (!DelayedCall) return this._startFailure('DelayedCall missing, plugin cannot function.');
|
||||
if (ElectronDiscordModule.cleanupDisplaySleep === DiscordModules.DiscordConstants.NOOP) XenoLib.Notifications.error(`[**${this.name}**] cleanupDisplaySleep is missing.`);
|
||||
delete this.onCrashRecoveredDelayedCall;
|
||||
this.onCrashRecoveredDelayedCall = new DelayedCall(1000, () => {
|
||||
XenoLib.Notifications.remove(this.notificationId);
|
||||
this.notificationId = null;
|
||||
if (this.disabledPlugins) XenoLib.Notifications.danger(`${this.disabledPlugins.map(e => e)} ${this.disabledPlugins.length > 1 ? 'have' : 'has'} been disabled to recover from the crash`, { timeout: 0 });
|
||||
if (this.suspectedPlugin) XenoLib.Notifications.danger(`${this.suspectedPlugin} ${this.suspectedPlugin2 !== this.suspectedPlugin && this.suspectedPlugin2 ? 'or ' + this.suspectedPlugin2 : ''} is suspected of causing the crash.`, { timeout: 10000 });
|
||||
XenoLib.Notifications.info('Successfully recovered, more info can be found in the console (CTRL + SHIFT + I > console on top). Pass this information to support for further help.', { timeout: 10000 });
|
||||
this.disabledPlugins = null;
|
||||
this.suspectedPlugin = null;
|
||||
this.suspectedPlugin2 = null;
|
||||
this.attempts = 0;
|
||||
const appMount = document.querySelector('#app-mount');
|
||||
appMount.append(document.querySelector('.xenoLib-notifications'));
|
||||
const BIVOverlay = document.querySelector('.biv-overlay');
|
||||
if (BIVOverlay) appMount.append(BIVOverlay);
|
||||
Logger.info('Corrected incorrectly placed containers');
|
||||
});
|
||||
Error.prepareStackTrace = (error, frames) => {
|
||||
this._lastStackFrames = frames;
|
||||
return error.stack
|
||||
}
|
||||
Utilities.suppressErrors(this.patchErrorBoundary.bind(this))(this.promises.state);
|
||||
if (!this.settings.lastErrorMapUpdate || Date.now() - this.settings.lastErrorMapUpdate > 2.628e+9) {
|
||||
const https = require('https');
|
||||
const req = https.request(INVARIANTS_URL, { headers: { 'origin': 'discord.com' } }, res => {
|
||||
let body = '';
|
||||
res.on('data', chunk => { body += chunk; });
|
||||
res.on('end', () => {
|
||||
if (res.statusCode !== 200) return;
|
||||
try {
|
||||
this.settings.errorMap = JSON.parse(body);
|
||||
this.settings.lastErrorMapUpdate = Date.now();
|
||||
this.saveSettings();
|
||||
} catch { }
|
||||
});
|
||||
});
|
||||
req.end();
|
||||
}
|
||||
}
|
||||
onStop() {
|
||||
this.promises.state.cancelled = true;
|
||||
Patcher.unpatchAll();
|
||||
if (this.notificationId) XenoLib.Notifications.remove(this.notificationId);
|
||||
delete Error.prepareStackTrace;
|
||||
}
|
||||
|
||||
|
||||
// Copyright (c) Facebook, Inc. and its affiliates
|
||||
// https://github.com/facebook/react/blob/master/scripts/jest/setupTests.js#L171
|
||||
decodeErrorMessage(message) {
|
||||
if (!message) return message;
|
||||
|
||||
const [, invariant, argS] = message.match(RE_INVARIANT);
|
||||
const code = parseInt(invariant, 10);
|
||||
const args = argS
|
||||
.split('&')
|
||||
.filter(s => s.indexOf('args[]=') !== -1)
|
||||
.map(s => s.substr('args[]='.length))
|
||||
.map(decodeURIComponent);
|
||||
const format = this.settings.errorMap[code];
|
||||
if (!format) return message; // ouch
|
||||
let argIndex = 0;
|
||||
return format.replace(/%s/g, () => args[argIndex++]);
|
||||
}
|
||||
|
||||
decodeStacks(stack, componentStack, baseComponentName = 'ErrorBoundary') {
|
||||
if (!this.settings.errorMap) return { stack, componentStack };
|
||||
if (RE_INVARIANT.test(stack)) stack = this.decodeErrorMessage(stack);
|
||||
// Strip out Discord (and React) only functions
|
||||
else stack = stack.split('\n')
|
||||
.filter(l => l.indexOf('discordapp.com/assets') === -1 && l.indexOf('discord.com/assets') === -1)
|
||||
.join('\n')
|
||||
.split(ROOT_FOLDER) // transform paths to relative
|
||||
.join('');
|
||||
|
||||
// Only show up to the error boundary
|
||||
const splitComponentStack = componentStack.split('\n').filter(e => e);
|
||||
const stackEnd = splitComponentStack.findIndex(l => l.indexOf(`in ${baseComponentName}`) !== -1);
|
||||
if (stackEnd !== -1 && baseComponentName) splitComponentStack.splice(stackEnd + 1, splitComponentStack.length);
|
||||
componentStack = splitComponentStack.join('\n');
|
||||
return { stack, componentStack };
|
||||
}
|
||||
|
||||
/*
|
||||
MUST return either an array with the plugin name
|
||||
or a string of the plugin name
|
||||
*/
|
||||
queryResponsiblePlugins() {
|
||||
try {
|
||||
const stack = this._bLastStackFrames
|
||||
// filter out blank functions (like from console or whatever)
|
||||
.filter(e => e.getFileName() && (e.getFunctionName() || e.getMethodName()))
|
||||
// filter out discord functions
|
||||
.filter(e => e.getFileName().indexOf(ROOT_FOLDER) !== -1)
|
||||
// convert CallSites to only useful info
|
||||
.map(e => ({ filename: e.getFileName(), functionName: e.getFunctionName() || e.getMethodName() }))
|
||||
// filter out ZeresPluginLibrary and ourselves
|
||||
.filter(({ filename }) => filename.lastIndexOf('0PluginLibrary.plugin.js') !== filename.length - 24 && filename.lastIndexOf(`${this.name}.plugin.js`) !== filename.length - (this.name.length + 10));
|
||||
// Filter out MessageLoggerV2 dispatch patch, is a 2 part step
|
||||
for (let i = 0, len = stack.length; i < len; i++) {
|
||||
const { filename, functionName } = stack[i];
|
||||
if (filename.lastIndexOf('MessageLoggerV2.plugin.js') !== filename.length - 25) continue;
|
||||
if (functionName !== 'onDispatchEvent') continue;
|
||||
if (stack[i + 1].functionName !== 'callback') break;
|
||||
stack.splice(i, 2);
|
||||
break;
|
||||
}
|
||||
const plugins = stack.map(({ filename }) => {
|
||||
try {
|
||||
const bdname = path.basename(filename);
|
||||
if (bdname.indexOf('.plugin.js') === bdname.length - 10) {
|
||||
const [pluginName] = bdname.split('.plugin.js');
|
||||
if (BdApi.Plugins.get(pluginName)) return pluginName;
|
||||
/*
|
||||
* go away Zack
|
||||
*/
|
||||
for (const path in require.cache) {
|
||||
const module = require.cache[path];
|
||||
if (!module || !module.exports || !module.exports.plugin || module.exports.filename.indexOf(bdname) !== 0) continue;
|
||||
return module.exports.id;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
else if (isPowercord) {
|
||||
const [, pcname] = RE_PC_PLUGIN_NAME_FROM_PATH.exec(filename);
|
||||
const { pluginManager } = window.powercord;
|
||||
const plugin = pluginManager.get(pcname);
|
||||
if (!plugin) return null;
|
||||
const { name } = plugin.manifest;
|
||||
if (BLACKLISTED_BUILTIN_PC_PLUGINS.indexOf(name) !== -1) return null;
|
||||
return pcname;
|
||||
}
|
||||
} catch (err) {
|
||||
Logger.stacktrace('Error fetching plugin')
|
||||
}
|
||||
}).filter((name, idx, self) => name && self.indexOf(name) === idx);
|
||||
const ret = [];
|
||||
for (let i = 0, len = plugins.length; i < len; i++) {
|
||||
const name = plugins[i];
|
||||
if (this.disabledPlugins && this.disabledPlugins.indexOf(name) !== -1) return name;
|
||||
ret.push(name);
|
||||
}
|
||||
return ret;
|
||||
} catch (e) {
|
||||
Logger.stacktrace('query error', e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
cleanupDiscord() {
|
||||
ElectronDiscordModule.cleanupDisplaySleep();
|
||||
Dispatcher.wait(() => {
|
||||
try {
|
||||
DiscordModules.ContextMenuActions.closeContextMenu();
|
||||
} catch (err) {
|
||||
Logger.stacktrace('Failed to close all context menus', err);
|
||||
}
|
||||
try {
|
||||
DiscordModules.ModalStack.popAll();
|
||||
} catch (err) {
|
||||
Logger.stacktrace('Failed to pop old modalstack', err);
|
||||
}
|
||||
try {
|
||||
DiscordModules.LayerManager.popAllLayers();
|
||||
} catch (err) {
|
||||
Logger.stacktrace('Failed to pop all layers', err);
|
||||
}
|
||||
try {
|
||||
DiscordModules.PopoutStack.closeAll();
|
||||
} catch (err) {
|
||||
Logger.stacktrace('Failed to close all popouts', err);
|
||||
}
|
||||
try {
|
||||
(ModalStack.modalsApi || ModalStack.useModalsStore).setState(() => ({ default: [] })); /* slow? unsafe? async? */
|
||||
} catch (err) {
|
||||
Logger.stacktrace('Failed to pop new modalstack');
|
||||
}
|
||||
try {
|
||||
if (!this.settings.useThirdStep) DiscordModules.NavigationUtils.transitionTo('/channels/@me');
|
||||
} catch (err) {
|
||||
Logger.stacktrace('Failed to transition to home');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
handleCrash(_this, stack, componentStack, isRender = false) {
|
||||
this._bLastStackFrames = this._lastStackFrames;
|
||||
try {
|
||||
const decoded = this.decodeStacks(stack, componentStack);
|
||||
Logger.error('HEY OVER HERE! Show this to the plugin developer or in the support server!\nPrettified stacktraces, stack:\n', decoded.stack, '\nComponent stack:\n', decoded.componentStack);
|
||||
} catch (err) {
|
||||
Logger.stacktrace('Failed decoding stack!', err);
|
||||
}
|
||||
this.onCrashRecoveredDelayedCall.cancel();
|
||||
if (!isRender) {
|
||||
_this.setState({
|
||||
error: { stack }
|
||||
});
|
||||
}
|
||||
if (!this.notificationId) {
|
||||
this.notificationId = XenoLib.Notifications.danger('Crash detected, attempting recovery', { timeout: 0, loading: true });
|
||||
}
|
||||
const responsiblePlugins = this.queryResponsiblePlugins();
|
||||
if (responsiblePlugins && !Array.isArray(responsiblePlugins)) {
|
||||
XenoLib.Notifications.update(this.notificationId, { content: `Failed to recover from crash, ${responsiblePlugins} is not stopping properly`, loading: false });
|
||||
return;
|
||||
}
|
||||
if (!this.attempts) {
|
||||
this.cleanupDiscord();
|
||||
if (responsiblePlugins) this.suspectedPlugin = responsiblePlugins.shift();
|
||||
}
|
||||
if (this.setStateTimeout) return;
|
||||
if (this.attempts >= 10 || ((this.settings.useThirdStep ? this.attempts >= 3 : this.attempts >= 2) && (!responsiblePlugins || !responsiblePlugins[0]))) {
|
||||
XenoLib.Notifications.update(this.notificationId, { content: 'Failed to recover from crash', loading: false });
|
||||
return;
|
||||
}
|
||||
if (this.attempts === 1) XenoLib.Notifications.update(this.notificationId, { content: 'Failed, trying again' });
|
||||
else if (this.settings.useThirdStep && this.attempts === 2) {
|
||||
Dispatcher.wait(() => DiscordModules.NavigationUtils.transitionTo('/channels/@me'));
|
||||
XenoLib.Notifications.update(this.notificationId, { content: `Failed, switching channels` });
|
||||
} else if (this.attempts >= 2) {
|
||||
const [name] = responsiblePlugins;
|
||||
try {
|
||||
if (BdApi.Plugins.get(name)) BdApi.Plugins.disable(name);
|
||||
else if (isPowercord) window.powercord.pluginManager.disable(name);
|
||||
} catch (e) { }
|
||||
XenoLib.Notifications.update(this.notificationId, { content: `Failed, suspecting ${name} for recovery failure` });
|
||||
if (!this.disabledPlugins) this.disabledPlugins = [];
|
||||
this.disabledPlugins.push(name);
|
||||
}
|
||||
this.setStateTimeout = setTimeout(() => {
|
||||
this.setStateTimeout = null;
|
||||
this.attempts++;
|
||||
this.onCrashRecoveredDelayedCall.delay();
|
||||
_this.setState({
|
||||
error: null,
|
||||
info: null
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
/* PATCHES */
|
||||
|
||||
patchErrorBoundary() {
|
||||
const ErrorBoundary = WebpackModules.getByDisplayName('ErrorBoundary');
|
||||
Patcher.instead(ErrorBoundary.prototype, 'componentDidCatch', (_this, [{ stack }, { componentStack }], orig) => {
|
||||
this.handleCrash(_this, stack, componentStack);
|
||||
});
|
||||
Patcher.after(ErrorBoundary.prototype, 'render', (_this, _, ret) => {
|
||||
if (!_this.state.error) return;
|
||||
if (!this.notificationId) {
|
||||
this.handleCrash(_this, _this.state.error.stack, _this.state.info.componentStack, true);
|
||||
}
|
||||
/* better be safe than sorry! */
|
||||
if (!_this.state.customPageError) {
|
||||
ret.props.action = React.createElement(
|
||||
XenoLib.ReactComponents.ErrorBoundary,
|
||||
{ label: 'ErrorBoundary patch', onError: () => _this.setState({ customPageError: true /* sad day.. */ }) },
|
||||
React.createElement(
|
||||
Flex,
|
||||
{
|
||||
grow: 0,
|
||||
direction: Flex.Direction.HORIZONTAL
|
||||
},
|
||||
React.createElement(
|
||||
XenoLib.ReactComponents.Button,
|
||||
{
|
||||
size: XenoLib.ReactComponents.ButtonOptions.ButtonSizes.LARGE,
|
||||
style: {
|
||||
marginRight: 20
|
||||
},
|
||||
onClick: () => {
|
||||
this.attempts = 0;
|
||||
this.disabledPlugins = null;
|
||||
XenoLib.Notifications.update(this.notificationId, { content: 'If you say so.. trying again', loading: true });
|
||||
_this.setState({
|
||||
error: null,
|
||||
info: null
|
||||
});
|
||||
}
|
||||
},
|
||||
'Recover'
|
||||
),
|
||||
React.createElement(
|
||||
XenoLib.ReactComponents.Button,
|
||||
{
|
||||
size: XenoLib.ReactComponents.ButtonOptions.ButtonSizes.LARGE,
|
||||
style: {
|
||||
marginRight: 20
|
||||
},
|
||||
onClick: () => window.location.reload(true)
|
||||
},
|
||||
'Reload'
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
ret.props.note = [
|
||||
React.createElement('div', {}, 'Discord has crashed!'),
|
||||
this.suspectedPlugin ? React.createElement('div', {}, this.suspectedPlugin, this.suspectedPlugin2 && this.suspectedPlugin2 !== this.suspectedPlugin ? [' or ', this.suspectedPlugin2] : false, ' is likely responsible for the crash') : this.suspectedPlugin2 ? React.createElement('div', {}, this.suspectedPlugin2, ' is likely responsible for the crash') : React.createElement('div', {}, 'Plugin responsible for crash is unknown'),
|
||||
this.disabledPlugins && this.disabledPlugins.length
|
||||
? React.createElement(
|
||||
'div',
|
||||
{},
|
||||
this.disabledPlugins.map((e, i) => `${i === 0 ? '' : ', '}${e}`),
|
||||
this.disabledPlugins.length > 1 ? ' have' : ' has',
|
||||
' been disabled in an attempt to recover'
|
||||
)
|
||||
: false
|
||||
];
|
||||
});
|
||||
const ErrorBoundaryInstance = ReactTools.getOwnerInstance(document.querySelector(`.${XenoLib.getSingleClass('errorPage')}`) || document.querySelector('#app-mount > svg:first-of-type'), { include: ['ErrorBoundary'] });
|
||||
ErrorBoundaryInstance.state.customPageError = false;
|
||||
ErrorBoundaryInstance.forceUpdate();
|
||||
}
|
||||
showChangelog = () => XenoLib.showChangelog(`${this.name} has been updated!`, this.version, this._config.changelog);
|
||||
getSettingsPanel = () =>
|
||||
this.buildSettingsPanel()
|
||||
.append(new XenoLib.Settings.PluginFooter(() => this.showChangelog()))
|
||||
.getElement();
|
||||
};
|
||||
};
|
||||
|
||||
/* Finalize */
|
||||
|
||||
/* shared getters */
|
||||
const BasePlugin = cl =>
|
||||
class extends cl {
|
||||
constructor() {
|
||||
super();
|
||||
Object.defineProperties(this, {
|
||||
name: { get: () => config.info.name },
|
||||
short: { get: () => config.info.name.split('').reduce((acc, char) => acc + (char === char.toUpperCase() ? char : '')) },
|
||||
author: { get: () => config.info.authors.map(author => author.name).join(', ') },
|
||||
version: { get: () => config.info.version },
|
||||
description: { get: () => config.info.description }
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/* this new lib loader is lit */
|
||||
let ZeresPluginLibraryOutdated = false;
|
||||
let XenoLibOutdated = false;
|
||||
try {
|
||||
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 = (b, c) => ((b && b._config && b._config.info && b._config.info.version && a(b._config.info.version, c))),
|
||||
c = BdApi.Plugins.get('ZeresPluginLibrary'),
|
||||
d = BdApi.Plugins.get('XenoLib');
|
||||
b(c, '1.2.27') && (ZeresPluginLibraryOutdated = !0), b(d, '1.3.35') && (XenoLibOutdated = !0);
|
||||
} catch (a) {
|
||||
console.error('Error checking if libraries are out of date', a);
|
||||
}
|
||||
|
||||
/* to anyone asking "why are you checking if x is out of date", well you see, sometimes, for whatever reason
|
||||
the libraries are sometimes not updating for people. Either it doesn't check for an update, or the request
|
||||
for some odd reason just fails. Yet, plugins update just fine with the same domain.
|
||||
*/
|
||||
return !global.ZeresPluginLibrary || !global.XenoLib || ZeresPluginLibraryOutdated || XenoLibOutdated
|
||||
? class extends BasePlugin(class { }) {
|
||||
constructor() {
|
||||
super();
|
||||
this._XL_PLUGIN = true;
|
||||
this.getName = () => this.name.replace(/\s+/g, '');
|
||||
this.getAuthor = () => this.author;
|
||||
this.getVersion = () => this.version;
|
||||
this.getDescription = () => this.description + global.BetterDiscordConfig ? '' : ' You are missing libraries for this plugin, please enable the plugin and click Download Now.';
|
||||
this.start = this.load = this.handleMissingLib;
|
||||
}
|
||||
start() { }
|
||||
stop() { }
|
||||
handleMissingLib() {
|
||||
const a = !!window.powercord && -1 !== (window.bdConfig && window.bdConfig.dataPath || "").indexOf("bdCompat") && "function" == typeof BdApi.__getPluginConfigPath, b = BdApi.findModuleByProps("openModal", "hasModalOpen");
|
||||
if (b && b.hasModalOpen(`${this.name}_DEP_MODAL`)) return;
|
||||
const c = !global.XenoLib, d = !global.ZeresPluginLibrary, e = c && d || (c || d) && (XenoLibOutdated || ZeresPluginLibraryOutdated),
|
||||
f = (() => { let a = ""; return c || d ? a += `Missing${XenoLibOutdated || ZeresPluginLibraryOutdated ? " and outdated" : ""} ` : (XenoLibOutdated || ZeresPluginLibraryOutdated) && (a += `Outdated `), a += `${e ? "Libraries" : "Library"} `, a })(),
|
||||
g = (() => {
|
||||
let a = `The ${e ? "libraries" : "library"} `; return c || XenoLibOutdated ? (a += "XenoLib ", (d || ZeresPluginLibraryOutdated) && (a += "and ZeresPluginLibrary ")) : (d || ZeresPluginLibraryOutdated) && (a += "ZeresPluginLibrary "),
|
||||
a += `required for ${this.name} ${e ? "are" : "is"} ${c || d ? "missing" : ""}${XenoLibOutdated || ZeresPluginLibraryOutdated ? c || d ? " and/or outdated" : "outdated" : ""}.`, a
|
||||
})(), h = BdApi.findModuleByDisplayName("Text"), i = BdApi.findModuleByDisplayName("ConfirmModal"),
|
||||
j = () => BdApi.alert(f, BdApi.React.createElement("span", { style: { color: "white" } }, BdApi.React.createElement("div", {}, g), `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.`,
|
||||
d || ZeresPluginLibraryOutdated ? BdApi.React.createElement("div", {}, BdApi.React.createElement("a", { href: "https://betterdiscord.net/ghdl?id=2252", target: "_blank" }, "Click here to download ZeresPluginLibrary")) : null, c || XenoLibOutdated ? BdApi.React.createElement("div", {},
|
||||
BdApi.React.createElement("a", { href: "https://betterdiscord.net/ghdl?id=3169", target: "_blank" }, "Click here to download XenoLib")) : null)); if (global.ohgodohfuck) return; if (!b || !i || !h) return console.error(`Missing components:${(b ? "" : " ModalStack") + (i ? "" : " ConfirmationModalComponent") + (h ? "" : "TextElement")}`),
|
||||
j(); class k extends BdApi.React.PureComponent {
|
||||
constructor(a) {
|
||||
super(a), this.state = { hasError: !1 }, this.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)),
|
||||
this.render = () => this.state.hasError ? null : this.props.children
|
||||
}
|
||||
} let m = !1; const n = b.openModal(c => {
|
||||
if (m) return null; try {
|
||||
return BdApi.React.createElement(k, { label: "missing dependency modal", onError: () => (b.closeModal(n), j()) }, BdApi.React.createElement(i,
|
||||
Object.assign({
|
||||
header: f, children: BdApi.React.createElement(h, { size: h.Sizes.SIZE_16, children: [`${g} Please click Download Now to download ${e ? "them" : "it"}.`] }), red: !1, confirmText: "Download Now", cancelText: "Cancel", onCancel: c.onClose, onConfirm: () => {
|
||||
const c = require("request"), d = require("fs"),
|
||||
e = require("path"), f = BdApi.Plugins && BdApi.Plugins.folder ? BdApi.Plugins.folder : window.ContentManager.pluginsFolder, g = () => {
|
||||
global.XenoLib && !XenoLibOutdated || c("https://gitdab.com/Dollar3795/LightcordPlugins/raw/branch/master/Plugins/1XenoLib.plugin.js", (c, g, h) => {
|
||||
try {
|
||||
if (c || 200 !== g.statusCode) return b.closeModal(n),
|
||||
j(); d.writeFile(e.join(f, "1XenoLib.plugin.js"), h, () => { BdApi.isSettingEnabled("fork-ps-5") && !a || BdApi.Plugins.reload(this.getName()) })
|
||||
} catch (a) { console.error("Fatal error downloading XenoLib", a), b.closeModal(n), j() }
|
||||
})
|
||||
}; !global.ZeresPluginLibrary || ZeresPluginLibraryOutdated ? c("https://raw.githubusercontent.com/rauenzi/BDPluginLibrary/master/release/0PluginLibrary.plugin.js",
|
||||
(a, c, h) => { try { if (a || 200 !== c.statusCode) return b.closeModal(n), j(); d.writeFile(e.join(f, "0PluginLibrary.plugin.js"), h, () => { }), g() } catch (a) { console.error("Fatal error downloading ZeresPluginLibrary", a), b.closeModal(n), j() } }) : g()
|
||||
}
|
||||
}, c, { onClose: () => { } })))
|
||||
} catch (a) { return console.error("There has been an error constructing the modal", a), m = !0, b.closeModal(n), j(), null }
|
||||
}, { modalKey: `${this.name}_DEP_MODAL` });
|
||||
}
|
||||
}
|
||||
: buildPlugin(global.ZeresPluginLibrary.buildPlugin(config), BasePlugin);
|
||||
})();
|
||||
|
||||
/*@end@*/
|
Loading…
Reference in a new issue