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