Upload files to 'Plugins'

This commit is contained in:
Dollar3795 2021-03-05 08:14:37 +00:00
parent d36041b8c2
commit af8fbe66e4
5 changed files with 5842 additions and 0 deletions

2079
Plugins/1XenoLib.plugin.js Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View 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@*/

View 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@*/

View 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@*/