Upload files to 'Plugins'
This commit is contained in:
parent
af8fbe66e4
commit
b0ec95966a
|
@ -0,0 +1,603 @@
|
|||
//META{"name":"InAppNotifications","source":"https://github.com/1Lighty/BetterDiscordPlugins/blob/master/Plugins/InAppNotifications/InAppNotifications.plugin.js","website":"https://1lighty.github.io/BetterDiscordStuff/?plugin=InAppNotifications","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: 'InAppNotifications',
|
||||
authors: [
|
||||
{
|
||||
name: 'Lighty',
|
||||
discord_id: '239513071272329217',
|
||||
github_username: 'LightyPon',
|
||||
twitter_username: ''
|
||||
}
|
||||
],
|
||||
version: '1.0.10',
|
||||
description: 'Show a notification in Discord when someone sends a message, just like on mobile.',
|
||||
github: 'https://github.com/1Lighty',
|
||||
github_raw: 'https://raw.githubusercontent.com/1Lighty/BetterDiscordPlugins/master/Plugins/InAppNotifications/InAppNotifications.plugin.js'
|
||||
},
|
||||
defaultConfig: [
|
||||
{
|
||||
name: 'Ignore DND mode',
|
||||
id: 'dndIgnore',
|
||||
type: 'switch',
|
||||
value: true
|
||||
},
|
||||
{
|
||||
name: 'Bar color',
|
||||
id: 'color',
|
||||
type: 'color',
|
||||
value: '#4a90e2',
|
||||
options: {
|
||||
defaultColor: '#4a90e2'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Set bar color to top role color',
|
||||
id: 'roleColor',
|
||||
type: 'switch',
|
||||
value: true
|
||||
},
|
||||
{
|
||||
name: 'Calculate timeout by number of words',
|
||||
note: 'Long text will stay for longer',
|
||||
id: 'wpmTimeout',
|
||||
type: 'switch',
|
||||
value: true
|
||||
},
|
||||
{
|
||||
name: 'Words per minute',
|
||||
id: 'wordsPerMinute',
|
||||
type: 'slider',
|
||||
value: 300,
|
||||
min: 50,
|
||||
max: 900,
|
||||
markers: Array.from(Array(18), (_, i) => (i + 1) * 50),
|
||||
stickToMarkers: true
|
||||
},
|
||||
{
|
||||
type: 'note'
|
||||
}
|
||||
],
|
||||
changelog: [
|
||||
{
|
||||
title: 'RIP BBD on Canary',
|
||||
type: 'fixed',
|
||||
items: ['Implemented fixes that allow patches to work properly on canary using Powercord.']
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
/* 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, GuildStore, UserStore, DiscordConstants, Dispatcher, GuildMemberStore, GuildActions, SwitchRow, EmojiUtils, RadioGroup, Permissions, FlexChild, PopoutOpener, Textbox, RelationshipStore, WindowInfo, UserSettingsStore, NavigationUtils, UserNameResolver, SelectedChannelStore } = DiscordModules;
|
||||
|
||||
const Patcher = XenoLib.createSmartPatcher(Api.Patcher);
|
||||
|
||||
const ChannelStore = WebpackModules.getByProps('getChannel', 'getDMFromUserId');
|
||||
|
||||
const LurkerStore = WebpackModules.getByProps('isLurking');
|
||||
const MuteStore = WebpackModules.getByProps('allowNoMessages');
|
||||
const isMentionedUtils = WebpackModules.getByProps('isRawMessageMentioned');
|
||||
const ParserModule = WebpackModules.getByProps('astParserFor', 'parse');
|
||||
const MessageClasses = WebpackModules.getByProps('username', 'messageContent');
|
||||
const MarkupClassname = XenoLib.getClass('markup');
|
||||
const Messages = (WebpackModules.getByProps('Messages') || {}).Messages;
|
||||
const SysMessageUtils = WebpackModules.getByProps('getSystemMessageUserJoin', 'stringify');
|
||||
const MessageParseUtils = (WebpackModules.getByProps('parseAndRebuild', 'default') || {}).default;
|
||||
const CUser = WebpackModules.getByPrototypes('getAvatarSource', 'isLocalBot');
|
||||
const TextElement = WebpackModules.getByDisplayName('Text');
|
||||
|
||||
class ExtraText extends Settings.SettingField {
|
||||
constructor(name, note) {
|
||||
super(name, note, null, TextElement, {
|
||||
children: 'To change the position or backdrop background color of the notifications, check XenoLib settings.'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const currentChannel = _ => {
|
||||
const channel = ChannelStore.getChannel(SelectedChannelStore.getChannelId());
|
||||
return channel ? Structs.Channel.from(channel) : null;
|
||||
}
|
||||
|
||||
return class InAppNotifications extends Plugin {
|
||||
constructor() {
|
||||
super();
|
||||
XenoLib.changeName(__filename, 'InAppNotifications');
|
||||
const oOnStart = this.onStart.bind(this);
|
||||
this.onStart = () => {
|
||||
try {
|
||||
oOnStart();
|
||||
} catch (e) {
|
||||
Logger.stacktrace('Failed to start!', e);
|
||||
PluginUpdater.checkForUpdate(this.name, this.version, this._config.info.github_raw);
|
||||
XenoLib.Notifications.error(`[**${this.name}**] Failed to start! 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 });
|
||||
try {
|
||||
this.onStop();
|
||||
} catch (e) { }
|
||||
}
|
||||
};
|
||||
const oMESSAGE_CREATE = this.MESSAGE_CREATE.bind(this);
|
||||
this.MESSAGE_CREATE = e => {
|
||||
try {
|
||||
oMESSAGE_CREATE(e);
|
||||
} catch (e) {
|
||||
this.errorCount++;
|
||||
if (this.errorCount >= 10) {
|
||||
Logger.stacktrace('Error in MESSAGE_CREATE dispatch handler', e);
|
||||
PluginUpdater.checkForUpdate(this.name, this.version, this._config.info.github_raw);
|
||||
XenoLib.Notifications.error(`[**${this.name}**] Plugin is throwing errors and is in a broken state, please update it or ${GuildStore.getGuild(XenoLib.supportServerId) ? 'go to <#639665366380838924>' : '[join my support server](https://discord.gg/NYvWdN5)'} for further assistance.`, { timeout: 0 });
|
||||
try {
|
||||
this.onStop();
|
||||
} catch (e) { }
|
||||
}
|
||||
}
|
||||
};
|
||||
try {
|
||||
WebpackModules.getByProps('openModal', 'hasModalOpen').closeModal(`${this.name}_DEP_MODAL`);
|
||||
} catch (e) { }
|
||||
}
|
||||
onStart() {
|
||||
try {
|
||||
/* do not, under any circumstances, let this kill the plugin */
|
||||
const CUSTOM_RULES = XenoLib._.cloneDeep(WebpackModules.getByProps('RULES').RULES);
|
||||
for (let rule of Object.keys(CUSTOM_RULES)) CUSTOM_RULES[rule].raw = null;
|
||||
for (let rule of ['paragraph', 'text', 'codeBlock', 'emoji', 'inlineCode']) CUSTOM_RULES[rule].raw = e => e.content;
|
||||
for (let rule of ['autolink', 'br', 'link', 'newline', 'url']) delete CUSTOM_RULES[rule];
|
||||
for (let rule of ['blockQuote', 'channel', 'em', 'mention', 'roleMention', 's', 'spoiler', 'strong', 'u']) CUSTOM_RULES[rule].raw = (e, t, n) => t(e.content, n);
|
||||
CUSTOM_RULES.customEmoji.raw = e => e.name;
|
||||
const astTools = WebpackModules.getByProps('flattenAst');
|
||||
const SimpleMarkdown = WebpackModules.getByProps('parserFor', 'outputFor');
|
||||
const parser = SimpleMarkdown.parserFor(CUSTOM_RULES);
|
||||
const render = SimpleMarkdown.htmlFor(SimpleMarkdown.ruleOutput(CUSTOM_RULES, 'raw'));
|
||||
this._timeUnparser = (e = '', r = true, a = {}) => render(astTools.constrainAst(astTools.flattenAst(parser(e, Object.assign({ inline: r }, a)))));
|
||||
} catch (err) {
|
||||
Logger.stacktrace('Failed to create custom unparser', err);
|
||||
this._timeUnparser = null;
|
||||
}
|
||||
|
||||
this.errorCount = 0;
|
||||
Dispatcher.subscribe('MESSAGE_CREATE', this.MESSAGE_CREATE);
|
||||
this.patchAll();
|
||||
PluginUtilities.addStyle(
|
||||
this.short + '-CSS',
|
||||
`
|
||||
.IAN-message {
|
||||
padding-left: 40px;
|
||||
position: relative;
|
||||
min-height: 36px;
|
||||
pointer-events: none;
|
||||
}
|
||||
.IAN-message .IAN-avatar {
|
||||
left: -2px;
|
||||
pointer-events: none;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
top: 0;
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.IAN-message .${MessageClasses.username.split(' ')[0]} {
|
||||
font-size: 0.9rem;
|
||||
line-height: unset;
|
||||
}
|
||||
.IAN-message .${MarkupClassname.split(' ')[0]} {
|
||||
line-height: unset;
|
||||
}
|
||||
.IAN-message .${MarkupClassname.split(' ')[0]} {
|
||||
max-height: calc(100vh - 150px);
|
||||
}
|
||||
.IAN-message .${MarkupClassname.split(' ')[0]}, .IAN-message .${MessageClasses.username.split(' ')[0]} {
|
||||
overflow: hidden
|
||||
}
|
||||
`
|
||||
);
|
||||
}
|
||||
|
||||
onStop() {
|
||||
Dispatcher.unsubscribe('MESSAGE_CREATE', this.MESSAGE_CREATE);
|
||||
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 === 'color') {
|
||||
const setting = new XenoLib.Settings.ColorPicker(data.name, data.note, data.value, data.onChange, data.options);
|
||||
if (data.id) setting.id = data.id;
|
||||
return setting;
|
||||
} else if (data.type === 'note') {
|
||||
return new ExtraText('', '');
|
||||
}
|
||||
return super.buildSetting(data);
|
||||
}
|
||||
|
||||
_shouldNotify(iAuthor, iChannel) {
|
||||
if (iChannel.isManaged()) return false;
|
||||
const guildId = iChannel.getGuildId();
|
||||
if (guildId && LurkerStore.isLurking(guildId)) return false;
|
||||
if (iAuthor.id === DiscordAPI.currentUser.id || RelationshipStore.isBlocked(iAuthor.id)) return false;
|
||||
if (!this.settings.dndIgnore && UserSettingsStore.status === DiscordConstants.StatusTypes.DND) return false;
|
||||
if (MuteStore.allowNoMessages(iChannel)) return false;
|
||||
return true;
|
||||
}
|
||||
shouldNotify(message, iChannel, iAuthor) {
|
||||
if (!DiscordAPI.currentUser || !iChannel || !iAuthor) return false;
|
||||
/* dunno what the func name is as this is copied from discord, so I named it _shouldNotify */
|
||||
if (!this._shouldNotify(iAuthor, iChannel)) return false;
|
||||
if (currentChannel() && currentChannel().id === iChannel.id) return false;
|
||||
/* channel has notif settings set to all messages */
|
||||
if (MuteStore.allowAllMessages(iChannel)) return true;
|
||||
const everyoneSuppressed = MuteStore.isSuppressEveryoneEnabled(iChannel.guild_id);
|
||||
const rolesSuppressed = MuteStore.isSuppressRolesEnabled(iChannel.guild_id);
|
||||
/* only if mentioned, but only if settings allow */
|
||||
return isMentionedUtils.isRawMessageMentioned(message, DiscordAPI.currentUser.id, everyoneSuppressed, rolesSuppressed);
|
||||
}
|
||||
|
||||
getChannelName(iChannel, iAuthor) {
|
||||
switch (iChannel.type) {
|
||||
case DiscordConstants.ChannelTypes.GROUP_DM:
|
||||
if ('' !== iChannel.name) return iChannel.name;
|
||||
const recipients = iChannel.recipients.map(e => (e === iAuthor.id ? iAuthor : UserStore.getUser(e))).filter(e => e);
|
||||
return recipients.length > 0 ? recipients.map(e => e.username).join(', ') : Messages.UNNAMED;
|
||||
case DiscordConstants.ChannelTypes.GUILD_ANNOUNCEMENT:
|
||||
case DiscordConstants.ChannelTypes.GUILD_TEXT:
|
||||
return '#' + iChannel.name;
|
||||
default:
|
||||
return iChannel.name;
|
||||
}
|
||||
}
|
||||
|
||||
getActivity(e, t, n, r) {
|
||||
switch (e.type) {
|
||||
case DiscordConstants.ChannelTypes.GUILD_ANNOUNCEMENT:
|
||||
case DiscordConstants.ChannelTypes.GUILD_TEXT:
|
||||
return t;
|
||||
case DiscordConstants.ChannelTypes.GROUP_DM:
|
||||
return n;
|
||||
case DiscordConstants.ChannelTypes.DM:
|
||||
default:
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
makeTextChatNotification(iChannel, message, iAuthor) {
|
||||
let author = UserNameResolver.getName(iChannel.guild_id, iChannel.id, iAuthor);
|
||||
let channel = author;
|
||||
switch (iChannel.type) {
|
||||
case DiscordConstants.ChannelTypes.GUILD_ANNOUNCEMENT:
|
||||
case DiscordConstants.ChannelTypes.GUILD_TEXT:
|
||||
const iGuild = GuildStore.getGuild(iChannel.guild_id);
|
||||
if (message.type === DiscordConstants.MessageTypes.DEFAULT || iGuild) channel += ` (${this.getChannelName(iChannel)}, ${iGuild.name})`;
|
||||
break;
|
||||
case DiscordConstants.ChannelTypes.GROUP_DM:
|
||||
const newChannel = this.getChannelName(iChannel, iAuthor);
|
||||
if (!iChannel.isManaged() || !iAuthor.bot || channel !== newChannel) channel += ` (${newChannel})`;
|
||||
}
|
||||
let d = message.content;
|
||||
if (message.activity && message.application) {
|
||||
const targetMessage = message.activity.type === DiscordConstants.ActivityActionTypes.JOIN ? this.getActivity(iChannel, Messages.NOTIFICATION_MESSAGE_CREATE_GUILD_ACTIVITY_JOIN, Messages.NOTIFICATION_MESSAGE_CREATE_GROUP_DM_ACTIVITY_JOIN, Messages.NOTIFICATION_MESSAGE_CREATE_DM_ACTIVITY_JOIN) : this.getActivity(iChannel, Messages.NOTIFICATION_MESSAGE_CREATE_GUILD_ACTIVITY_SPECTATE, Messages.NOTIFICATION_MESSAGE_CREATE_GROUP_DM_ACTIVITY_SPECTATE, Messages.NOTIFICATION_MESSAGE_CREATE_DM_ACTIVITY_SPECTATE);
|
||||
d = targetMessage.format({ user: author, game: message.application.name });
|
||||
} else if (message.activity && message.activity.type === DiscordConstants.ActivityActionTypes.LISTEN) {
|
||||
const targetMessage = this.getActivity(iChannel, Messages.NOTIFICATION_MESSAGE_CREATE_GUILD_ACTIVITY_LISTEN, Messages.NOTIFICATION_MESSAGE_CREATE_GROUP_DM_ACTIVITY_LISTEN, Messages.NOTIFICATION_MESSAGE_CREATE_DM_ACTIVITY_LISTEN);
|
||||
d = targetMessage.format({ user: author });
|
||||
} else if (message.type !== DiscordConstants.MessageTypes.DEFAULT) {
|
||||
const content = SysMessageUtils.stringify(message);
|
||||
if (!content) return null;
|
||||
d = MessageParseUtils.unparse(content, iChannel.id, true);
|
||||
}
|
||||
if (!d.length && message.attachments.length) d = Messages.NOTIFICATION_BODY_ATTACHMENT.format({ filename: message.attachments[0].filename });
|
||||
if (!d.length && message.embeds.length) {
|
||||
const embed = message.embeds[0];
|
||||
if (embed.description) d = embed.title ? embed.title + ': ' + embed.description : embed.description;
|
||||
else if (embed.title) d = embed.title;
|
||||
else if (embed.fields) {
|
||||
const field = embed.fields[0];
|
||||
d = field.name + ': ' + field.value;
|
||||
}
|
||||
}
|
||||
return {
|
||||
icon: iAuthor.getAvatarURL(),
|
||||
title: channel,
|
||||
content: d
|
||||
};
|
||||
}
|
||||
|
||||
MESSAGE_CREATE({ channelId, message }) {
|
||||
const iChannel = ChannelStore.getChannel(channelId);
|
||||
let iAuthor = UserStore.getUser(message.author.id);
|
||||
if (!iAuthor) {
|
||||
iAuthor = new CUser(message.author);
|
||||
UserStore.getUsers()[message.author.id] = iAuthor;
|
||||
}
|
||||
if (!iChannel || !iAuthor) return;
|
||||
if (!this.shouldNotify(message, iChannel, iAuthor)) return;
|
||||
const notif = this.makeTextChatNotification(iChannel, message, iAuthor);
|
||||
if (!notif) return; /* wah */
|
||||
const member = GuildMemberStore.getMember(iChannel.guild_id, iAuthor.id);
|
||||
this.showNotification(notif, iChannel, this.settings.roleColor && member && member.colorString);
|
||||
}
|
||||
|
||||
calculateTime(text) {
|
||||
let words = 0;
|
||||
if (this._timeUnparser) {
|
||||
try {
|
||||
text = this._timeUnparser(text);
|
||||
} catch (err) {
|
||||
Logger.stacktrace(`Failed to unparse text ${text}`, err);
|
||||
this._timeUnparser = null;
|
||||
}
|
||||
}
|
||||
/* https://github.com/ngryman/reading-time */
|
||||
function ansiWordBound(c) {
|
||||
return ' ' === c || '\n' === c || '\r' === c || '\t' === c;
|
||||
}
|
||||
for (var i = 0; i < text.length;) {
|
||||
for (; i < text.length && !ansiWordBound(text[i]); i++);
|
||||
words++;
|
||||
for (; i < text.length && ansiWordBound(text[i]); i++);
|
||||
}
|
||||
return (words / this.settings.wordsPerMinute) * 60 * 1000;
|
||||
}
|
||||
|
||||
showNotification(notif, iChannel, color) {
|
||||
const timeout = this.settings.wpmTimeout ? Math.min(this.calculateTime(notif.title) + this.calculateTime(notif.content), 60000) : 0;
|
||||
const notificationId = XenoLib.Notifications.show(
|
||||
React.createElement(
|
||||
'div',
|
||||
{
|
||||
className: 'IAN-message'
|
||||
},
|
||||
React.createElement('img', {
|
||||
className: 'IAN-avatar',
|
||||
src: notif.icon
|
||||
}),
|
||||
React.createElement(
|
||||
'span',
|
||||
{
|
||||
className: MessageClasses.username
|
||||
},
|
||||
notif.title
|
||||
),
|
||||
React.createElement('div', { className: XenoLib.joinClassNames(MarkupClassname, MessageClasses.messageContent) }, ParserModule.parse(notif.content, true, { channelId: iChannel.id }))
|
||||
),
|
||||
{
|
||||
timeout: Math.max(5000, timeout),
|
||||
onClick: () => {
|
||||
NavigationUtils.transitionTo(`/channels/${iChannel.guild_id || '@me'}/${iChannel.id}`);
|
||||
XenoLib.Notifications.remove(notificationId);
|
||||
},
|
||||
color: color || this.settings.color
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/* PATCHES */
|
||||
|
||||
patchAll() {
|
||||
Utilities.suppressErrors(this.patchShouldNotify.bind(this), 'shouldNotify patch')();
|
||||
}
|
||||
|
||||
patchShouldNotify() {
|
||||
Patcher.after(WebpackModules.getByProps('shouldDisplayNotifications'), 'shouldDisplayNotifications', () => (WindowInfo.isFocused() ? false : undefined));
|
||||
}
|
||||
|
||||
/* PATCHES */
|
||||
|
||||
showChangelog(footer) {
|
||||
XenoLib.showChangelog(`${this.name} has been updated!`, this.version, this._config.changelog);
|
||||
}
|
||||
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;
|
||||
let XenoLibOutdated = false;
|
||||
try {
|
||||
const i = (i, n) => ((i = i.split('.').map(i => parseInt(i))), (n = n.split('.').map(i => parseInt(i))), !!(n[0] > i[0]) || !!(n[0] == i[0] && n[1] > i[1]) || !!(n[0] == i[0] && n[1] == i[1] && n[2] > i[2])),
|
||||
n = (n, e) => n && n._config && n._config.info && n._config.info.version && i(n._config.info.version, e),
|
||||
e = BdApi.Plugins.get('ZeresPluginLibrary'),
|
||||
o = BdApi.Plugins.get('XenoLib');
|
||||
n(e, '1.2.27') && (ZeresPluginLibraryOutdated = !0), n(o, '1.3.32') && (XenoLibOutdated = !0);
|
||||
} catch (i) {
|
||||
console.error('Error checking if libraries are out of date', i);
|
||||
}
|
||||
|
||||
return !global.ZeresPluginLibrary || !global.XenoLib || ZeresPluginLibraryOutdated || XenoLibOutdated
|
||||
? class {
|
||||
constructor() {
|
||||
this._XL_PLUGIN = true;
|
||||
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 libraries 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.XenoLib,
|
||||
c = !global.ZeresPluginLibrary,
|
||||
d = (b && c) || ((b || c) && (XenoLibOutdated || ZeresPluginLibraryOutdated)),
|
||||
e = (() => {
|
||||
let a = '';
|
||||
return b || c ? (a += `Missing${XenoLibOutdated || ZeresPluginLibraryOutdated ? ' and outdated' : ''} `) : (XenoLibOutdated || ZeresPluginLibraryOutdated) && (a += `Outdated `), (a += `${d ? 'Libraries' : 'Library'} `), a;
|
||||
})(),
|
||||
f = (() => {
|
||||
let a = `The ${d ? 'libraries' : 'library'} `;
|
||||
return b || XenoLibOutdated ? ((a += 'XenoLib '), (c || ZeresPluginLibraryOutdated) && (a += 'and ZeresPluginLibrary ')) : (c || ZeresPluginLibraryOutdated) && (a += 'ZeresPluginLibrary '), (a += `required for ${this.name} ${d ? 'are' : 'is'} ${b || c ? 'missing' : ''}${XenoLibOutdated || ZeresPluginLibraryOutdated ? (b || c ? ' and/or outdated' : 'outdated') : ''}.`), a;
|
||||
})(),
|
||||
g = BdApi.findModuleByDisplayName('Text'),
|
||||
h = BdApi.findModuleByDisplayName('ConfirmModal'),
|
||||
i = () => BdApi.alert(e, BdApi.React.createElement('span', {}, BdApi.React.createElement('div', {}, f), `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.`, c || ZeresPluginLibraryOutdated ? BdApi.React.createElement('div', {}, BdApi.React.createElement('a', { href: 'https://betterdiscord.net/ghdl?id=2252', target: '_blank' }, 'Click here to download ZeresPluginLibrary')) : null, b || 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 (!a || !h || !g) return console.error(`Missing components:${(a ? '' : ' ModalStack') + (h ? '' : ' ConfirmationModalComponent') + (g ? '' : 'TextElement')}`), i();
|
||||
class j 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 k = !1,
|
||||
l = !1;
|
||||
const m = a.openModal(
|
||||
b => {
|
||||
if (l) return null;
|
||||
try {
|
||||
return BdApi.React.createElement(
|
||||
j,
|
||||
{ label: 'missing dependency modal', onError: () => (a.closeModal(m), i()) },
|
||||
BdApi.React.createElement(
|
||||
h,
|
||||
Object.assign(
|
||||
{
|
||||
header: e,
|
||||
children: BdApi.React.createElement(g, { size: g.Sizes.SIZE_16, children: [`${f} Please click Download Now to download ${d ? 'them' : 'it'}.`] }),
|
||||
red: !1,
|
||||
confirmText: 'Download Now',
|
||||
cancelText: 'Cancel',
|
||||
onCancel: b.onClose,
|
||||
onConfirm: () => {
|
||||
if (k) return;
|
||||
k = !0;
|
||||
const b = require('request'),
|
||||
c = require('fs'),
|
||||
d = require('path'),
|
||||
e = BdApi.Plugins && BdApi.Plugins.folder ? BdApi.Plugins.folder : window.ContentManager.pluginsFolder,
|
||||
f = () => {
|
||||
(global.XenoLib && !XenoLibOutdated) ||
|
||||
b('https://gitdab.com/Dollar3795/LightcordPlugins/raw/branch/master/Plugins/1XenoLib.plugin.js', (b, f, g) => {
|
||||
try {
|
||||
if (b || 200 !== f.statusCode) return a.closeModal(m), i();
|
||||
c.writeFile(d.join(e, '1XenoLib.plugin.js'), g, () => { });
|
||||
} catch (b) {
|
||||
console.error('Fatal error downloading XenoLib', b), a.closeModal(m), i();
|
||||
}
|
||||
});
|
||||
};
|
||||
!global.ZeresPluginLibrary || ZeresPluginLibraryOutdated
|
||||
? b('https://raw.githubusercontent.com/rauenzi/BDPluginLibrary/master/release/0PluginLibrary.plugin.js', (b, g, h) => {
|
||||
try {
|
||||
if (b || 200 !== g.statusCode) return a.closeModal(m), i();
|
||||
c.writeFile(d.join(e, '0PluginLibrary.plugin.js'), h, () => { }), f();
|
||||
} catch (b) {
|
||||
console.error('Fatal error downloading ZeresPluginLibrary', b), a.closeModal(m), i();
|
||||
}
|
||||
})
|
||||
: f();
|
||||
}
|
||||
},
|
||||
b,
|
||||
{ onClose: () => { } }
|
||||
)
|
||||
)
|
||||
);
|
||||
} catch (b) {
|
||||
return console.error('There has been an error constructing the modal', b), (l = !0), a.closeModal(m), i(), 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@*/
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,534 @@
|
|||
//META{"name":"MultiUploads","source":"https://github.com/1Lighty/BetterDiscordPlugins/blob/master/Plugins/MultiUploads/MultiUploads.plugin.js","website":"https://1lighty.github.io/BetterDiscordStuff/?plugin=MultiUploads","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: 'MultiUploads',
|
||||
authors: [
|
||||
{
|
||||
name: 'Lighty',
|
||||
discord_id: '239513071272329217',
|
||||
github_username: 'LightyPon',
|
||||
twitter_username: ''
|
||||
}
|
||||
],
|
||||
version: '1.1.4',
|
||||
description: 'Multiple uploads send in a single message, like on mobile. Hold shift while pressing the upload button to only upload one. Adds ability to paste multiple times.',
|
||||
github: 'https://github.com/1Lighty',
|
||||
github_raw: 'https://raw.githubusercontent.com/1Lighty/BetterDiscordPlugins/master/Plugins/MultiUploads/MultiUploads.plugin.js'
|
||||
},
|
||||
changelog: [
|
||||
{
|
||||
title: 'RIP BBD on Canary',
|
||||
type: 'fixed',
|
||||
items: ['More canary fixes.']
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
/* Build */
|
||||
const buildPlugin = ([Plugin, Api]) => {
|
||||
const { Utilities, WebpackModules, DiscordModules, ReactComponents, Logger, PluginUtilities } = Api;
|
||||
const { ChannelStore, DiscordConstants, Dispatcher, Permissions } = 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 _ = WebpackModules.getByProps('bindAll', 'debounce');
|
||||
const Upload = (WebpackModules.getByProps('Upload') || {}).Upload;
|
||||
const MessageDraftUtils = WebpackModules.getByProps('saveDraft');
|
||||
const FileUtils = WebpackModules.getByProps('anyFileTooLarge');
|
||||
const GenericUploaderBase = WebpackModules.find(e => e.prototype && e.prototype.upload && e.prototype.cancel && !e.__proto__.prototype.cancel);
|
||||
const MessageFileUploader = WebpackModules.find(e => e.prototype && e.prototype.upload && e.__proto__ === GenericUploaderBase);
|
||||
const UploadUtils = WebpackModules.getByProps('upload', 'instantBatchUpload', 'cancel');
|
||||
|
||||
return class MultiUploads extends Plugin {
|
||||
constructor() {
|
||||
super();
|
||||
try {
|
||||
WebpackModules.getByProps('openModal', 'hasModalOpen').closeModal(`${this.name}_DEP_MODAL`);
|
||||
} catch (e) { }
|
||||
_.bindAll(this, ['UPLOAD_MODAL_POP_FILE', 'UPLOAD_MODAL_PUSH_FILES', 'UPLOAD_MODAL_CLEAR_ALL_FILES']);
|
||||
}
|
||||
onStart() {
|
||||
this.promises = { cancelled: false };
|
||||
this.patchAll();
|
||||
this.uploads = [];
|
||||
if (!Upload) throw 'Upload class could not be found';
|
||||
Dispatcher.subscribe('UPLOAD_MODAL_POP_FILE', this.UPLOAD_MODAL_POP_FILE);
|
||||
Dispatcher.subscribe('UPLOAD_MODAL_PUSH_FILES', this.UPLOAD_MODAL_PUSH_FILES);
|
||||
Dispatcher.subscribe('UPLOAD_MODAL_CLEAR_ALL_FILES', this.UPLOAD_MODAL_CLEAR_ALL_FILES);
|
||||
}
|
||||
|
||||
onStop() {
|
||||
this.promises.cancelled = true;
|
||||
Patcher.unpatchAll();
|
||||
Dispatcher.unsubscribe('UPLOAD_MODAL_POP_FILE', this.UPLOAD_MODAL_POP_FILE);
|
||||
Dispatcher.unsubscribe('UPLOAD_MODAL_PUSH_FILES', this.UPLOAD_MODAL_PUSH_FILES);
|
||||
Dispatcher.unsubscribe('UPLOAD_MODAL_CLEAR_ALL_FILES', this.UPLOAD_MODAL_CLEAR_ALL_FILES);
|
||||
}
|
||||
|
||||
// Replicate UploadModalStore so we keep track of our own instead
|
||||
UPLOAD_MODAL_POP_FILE() {
|
||||
this.uploads.shift();
|
||||
}
|
||||
UPLOAD_MODAL_PUSH_FILES({ files, channelId }) {
|
||||
for (const file of files) this.uploads.push(new Upload(file, channelId));
|
||||
}
|
||||
UPLOAD_MODAL_CLEAR_ALL_FILES() {
|
||||
this.uploads = [];
|
||||
}
|
||||
|
||||
/* PATCHES */
|
||||
|
||||
patchAll() {
|
||||
this.patchMessageFileUploader();
|
||||
this.patchUploadModal(this.promises);
|
||||
this.patchInstantBatchUpload();
|
||||
}
|
||||
|
||||
patchMessageFileUploader() {
|
||||
const superagent = WebpackModules.getByProps('getXHR');
|
||||
// Reverse engineered, override it entirely with our own implementation
|
||||
Patcher.instead(MessageFileUploader.prototype, 'upload', (_this, [file, message = {}]) => {
|
||||
const noSpoilerMessage = Object.assign({}, message);
|
||||
// hasSpoiler has no use here, only used to check if images should be spoilered
|
||||
delete noSpoilerMessage.hasSpoiler;
|
||||
// make a fale foče if it's multi upload, and set the name to represent how many files we're uploading
|
||||
const fakeFile = Array.isArray(file) ? Object.assign({}, file[0], { name: `Uploading ${file.length} files...` }) : file;
|
||||
// call super.upload(fakeFile, noSpoilerImage);
|
||||
// since no access to super, this is the next best thing
|
||||
GenericUploaderBase.prototype.upload.call(_this, fakeFile, noSpoilerMessage);
|
||||
const req = superagent.post(_this._url);
|
||||
// if it's multiple files, attach them, each having a different field name
|
||||
// this was reversed by snooping mobile uploads, apparently each file is its own field
|
||||
// each field being file(file index) so file0 file1 file2 file3 file4 etc, with file being the default single upload
|
||||
if (Array.isArray(file)) {
|
||||
const numMap = {};
|
||||
file.forEach((e, idx) => {
|
||||
let name = e.name;
|
||||
// ensure no other file has the same name, otherwise we'll be in a world of pain
|
||||
if (file.find((e_, idx_) => e_.name === name && idx_ < idx)) {
|
||||
if (!numMap[name]) numMap[name] = 0;
|
||||
numMap[name]++;
|
||||
const split = name.split('.');
|
||||
// no extention, just append the number
|
||||
if (split.length === 1) name = `${numMap[name]}`;
|
||||
else {
|
||||
// extract everything before the extension, add number, then the extension
|
||||
const beforeExt = split.slice(0, -1);
|
||||
const ext = split.slice(-1);
|
||||
name = `${beforeExt.join('.')}${numMap[name]}.${ext}`;
|
||||
}
|
||||
}
|
||||
// attach, with its own unique file field
|
||||
req.attach('file' + idx, e, (message.hasSpoiler ? 'SPOILER_' : '') + name);
|
||||
});
|
||||
}
|
||||
else req.attach('file', file, (message.hasSpoiler ? 'SPOILER_' : '') + file.name);
|
||||
// added on replies update, dunno why? throws error if it's not here
|
||||
req.field('payload_json', JSON.stringify(noSpoilerMessage));
|
||||
// attach all other fields, sometimes value is a non valid type though
|
||||
_.each(noSpoilerMessage, (value, key) => {
|
||||
if (!value) return;
|
||||
req.field(key, value);
|
||||
});
|
||||
req.then(e => {
|
||||
if (e.ok) _this._handleComplete()
|
||||
else _this._handleError(e.body && e.body.code)
|
||||
}, _ => _this._handleError());
|
||||
const { xhr } = req;
|
||||
if (xhr.upload) xhr.upload.onprogress = (...props) => _this._handleXHRProgress(...props);
|
||||
xhr.addEventListener('progress', _this._handleXHRProgress, false);
|
||||
_this._handleStart(_ => req.abort());
|
||||
})
|
||||
}
|
||||
|
||||
async patchUploadModal(promiseState) {
|
||||
const Upload = await ReactComponents.getComponentByName('Upload', `.${WebpackModules.getByProps('uploadModal').uploadModal.split(' ')[0]}`);
|
||||
if (promiseState.cancelled) return;
|
||||
const ParseUtils = WebpackModules.getByProps('parsePreprocessor');
|
||||
const WYSIWYGSerializeDeserialize = WebpackModules.getByProps('serialize', 'deserialize');
|
||||
const UploadModalUtils = WebpackModules.getByProps('popFirstFile');
|
||||
// our own function, just to check if we can send multiple messages at once
|
||||
Patcher.instead(Upload.component.prototype, 'canSendBulk', _this => {
|
||||
const { channel } = _this.props;
|
||||
if (!channel.rateLimitPerUser) return true;
|
||||
return Permissions.can(DiscordConstants.Permissions.MANAGE_CHANNELS, channel) || Permissions.can(DiscordConstants.Permissions.MANAGE_MESSAGES, channel);
|
||||
});
|
||||
// override this function in particular as it's easy to patch and is run after all checks are validated
|
||||
// which are @everyone, slowmode, whatnot
|
||||
Patcher.instead(Upload.component.prototype, 'submitUpload', (_this, [valid, content, upload, channel, hasSpoiler], orig) => {
|
||||
if (!valid || upload === null || !_this.props.hasAdditionalUploads || _this.__MU_onlySingle) return orig(valid, content, upload, channel, hasSpoiler);
|
||||
let parsed = ParseUtils.parse(channel, content);
|
||||
MessageDraftUtils.saveDraft(channel.id, '');
|
||||
_this.setState({
|
||||
textFocused: false,
|
||||
textValue: '',
|
||||
richValue: WYSIWYGSerializeDeserialize.deserialize('')
|
||||
});
|
||||
|
||||
const canSendAll = _this.canSendBulk();
|
||||
|
||||
// fetch group of files we can send in 1 message
|
||||
let files = this.getNextMessageGroup(channel.id);
|
||||
// upload them after a set timeout
|
||||
const startUpload = e => setTimeout(_ => (UploadUtils.upload(channel.id, files, parsed, hasSpoiler), e && e()), 125);
|
||||
if (canSendAll) {
|
||||
// if we can send all of them, store the array locally because it'll be cleared at the end of this function
|
||||
const uploads = [...this.uploads];
|
||||
const start = _ => startUpload(_ => {
|
||||
// clear parsed, so we don't send same text multiple times
|
||||
parsed = { content: '', invalidEmojis: [], tts: false, validNonShortcutEmojis: [] };
|
||||
// remove previously sent files
|
||||
uploads.splice(0, files.length);
|
||||
if (!uploads.length) return;
|
||||
// fetch next group of files we can send in 1 message
|
||||
files = this.getNextMessageGroup(channel.id, uploads);
|
||||
start();
|
||||
});
|
||||
start();
|
||||
} else startUpload();
|
||||
|
||||
// replica of the showNextFile function with only slight differences
|
||||
if (!canSendAll && files.length !== this.uploads.length) {
|
||||
_this.setState({
|
||||
transitioning: true
|
||||
});
|
||||
setTimeout((function () {
|
||||
// clear multiple times
|
||||
for (let i = 0; i < files.length; i++) UploadModalUtils.popFirstFile();
|
||||
}
|
||||
), 100);
|
||||
_this._transitionTimeout = setTimeout((() => {
|
||||
return _this.setState({
|
||||
transitioning: false
|
||||
})
|
||||
}
|
||||
), 200);
|
||||
} else {
|
||||
// clear all instead of one
|
||||
UploadModalUtils.clearAll();
|
||||
_this.props.onClose();
|
||||
}
|
||||
});
|
||||
Patcher.instead(Upload.component.prototype, '_confirm', (_this, [e]) => {
|
||||
_this.__MU_onlySingle = e ? e.shiftKey : false;
|
||||
return _this._olConfirm();
|
||||
});
|
||||
const patchId = _.uniqueId('MultiUploads');
|
||||
Patcher.before(Upload.component.prototype, 'renderFooter', _this => {
|
||||
if (_this.handleSubmit.__MU_patched === patchId) return;
|
||||
if (!_this._olConfirm) _this._olConfirm = _this.confirm;
|
||||
_this.confirm = _this._confirm.bind(_this);
|
||||
_this.confirm.__MU_patched = patchId;
|
||||
});
|
||||
Patcher.after(Upload.component.prototype, 'renderFooter', (_this, _, ret) => {
|
||||
if (!_this.props.hasAdditionalUploads) return;
|
||||
const buttons = Utilities.findInReactTree(ret, e => Array.isArray(e) && e.find(e => e && e.props && e.props.onClick === _this.cancel));
|
||||
const uploadButtonProps = Utilities.findInReactTree(buttons, e => e && typeof e.children === 'function');
|
||||
if (!uploadButtonProps) return;
|
||||
const oChildren = uploadButtonProps.children;
|
||||
uploadButtonProps.children = e => {
|
||||
try {
|
||||
const ret = oChildren(e);
|
||||
if (_this.props.hasAdditionalUploads) {
|
||||
const uploadButton = Utilities.findInReactTree(ret, e => e && e.onClick === _this.confirm);
|
||||
const group = this.getNextMessageGroup(_this.props.channel.id);
|
||||
const { props } = uploadButton.children;
|
||||
if (group.length === this.uploads.length || _this.canSendBulk()) props.children = 'Upload All';
|
||||
else if (group.length > 1) props.children = `Upload ${group.length}`;
|
||||
}
|
||||
return ret;
|
||||
} catch (err) {
|
||||
Logger.stacktrace('Failed patching Upload button', err);
|
||||
try {
|
||||
return oChildren(e);
|
||||
} catch (err) {
|
||||
Logger.stacktrace('Failed calling original Upload button func', err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
const promptToUpload = WebpackModules.getByString('.Messages.UPLOAD_AREA_TOO_LARGE_TITLE');
|
||||
Patcher.after(Upload.component.prototype, 'render', (_this, _, ret) => {
|
||||
const channelTextEditorContainer = Utilities.findInReactTree(ret, e => Utilities.getNestedProp(e, 'type.type.render.displayName') === 'ChannelTextAreaContainer');
|
||||
if (!channelTextEditorContainer) return;
|
||||
channelTextEditorContainer.props.promptToUpload = promptToUpload;
|
||||
})
|
||||
Upload.forceUpdateAll();
|
||||
}
|
||||
|
||||
patchInstantBatchUpload() {
|
||||
Patcher.instead(UploadUtils, 'instantBatchUpload', (_, [channelId, files], orig) => {
|
||||
if (files.length === 1) return orig(channelId, files);
|
||||
let fileGroup = this.getNextMessageGroup(channelId, files);
|
||||
const startUpload = e => setTimeout(_ => (UploadUtils.upload(channelId, fileGroup), e && e()), 125);
|
||||
const uploads = [...files];
|
||||
const start = _ => startUpload(_ => {
|
||||
uploads.splice(0, fileGroup.length);
|
||||
if (!uploads.length) return;
|
||||
fileGroup = this.getNextMessageGroup(channelId, uploads);
|
||||
start();
|
||||
});
|
||||
start();
|
||||
})
|
||||
}
|
||||
|
||||
getNextMessageGroup(channelId, uploads = this.uploads) {
|
||||
const { guild_id } = ChannelStore.getChannel(channelId);
|
||||
const maxSize = FileUtils.maxFileSize(guild_id);
|
||||
if (!uploads.length) return [];
|
||||
const isUpload = !!uploads[0].file;
|
||||
const retAccum = [isUpload ? uploads[0].file : uploads[0]];
|
||||
let sizeAccum = isUpload ? uploads[0].file.size : uploads[0].size;
|
||||
for (let i = 1, len = uploads.length; i < len; i++) {
|
||||
const { file } = isUpload ? uploads[i] : { file: uploads[i] };
|
||||
if (sizeAccum + file.size > maxSize) break;
|
||||
retAccum.push(file);
|
||||
sizeAccum += file.size
|
||||
if (retAccum.length >= 10) break;
|
||||
}
|
||||
return retAccum;
|
||||
}
|
||||
|
||||
/* PATCHES */
|
||||
|
||||
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 || window.BDModules
|
||||
? 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 + window.BDModules ? '' : 'You are missing ZeresPluginLibrary for this plugin, please enable the plugin and click Download Now.';
|
||||
}
|
||||
start() { }
|
||||
stop() { }
|
||||
handleMissingLib() {
|
||||
if (window.BDModules) return;
|
||||
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@*/
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,624 @@
|
|||
//META{"name":"UnreadBadgesRedux","source":"https://github.com/1Lighty/BetterDiscordPlugins/blob/master/Plugins/UnreadBadgesRedux/","website":"https://1lighty.github.io/BetterDiscordStuff/?plugin=UnreadBadgesRedux","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: 'UnreadBadgesRedux',
|
||||
authors: [
|
||||
{
|
||||
name: 'Lighty',
|
||||
discord_id: '239513071272329217',
|
||||
github_username: 'LightyPon',
|
||||
twitter_username: ''
|
||||
}
|
||||
],
|
||||
version: '1.0.12',
|
||||
description: 'Adds a number badge to server icons and channels.',
|
||||
github: 'https://github.com/1Lighty',
|
||||
github_raw: 'https://raw.githubusercontent.com/1Lighty/BetterDiscordPlugins/master/Plugins/UnreadBadgesRedux/UnreadBadgesRedux.plugin.js'
|
||||
},
|
||||
changelog: [
|
||||
{
|
||||
title: 'RIP BBD on Canary',
|
||||
type: 'fixed',
|
||||
items: ['Implemented fixes that allow patches to work properly on canary using Powercord.']
|
||||
}
|
||||
],
|
||||
defaultConfig: [
|
||||
{
|
||||
type: 'category',
|
||||
id: 'misc',
|
||||
name: 'Display settings',
|
||||
collapsible: true,
|
||||
shown: true,
|
||||
settings: [
|
||||
{
|
||||
name: 'Display badge on folders',
|
||||
id: 'folders',
|
||||
type: 'switch',
|
||||
value: true
|
||||
},
|
||||
{
|
||||
name: 'Ignore muted servers in folders unread badge count',
|
||||
id: 'noMutedGuildsInFolderCount',
|
||||
type: 'switch',
|
||||
value: true
|
||||
},
|
||||
{
|
||||
name: 'Ignore muted channels in servers in folders unread badge count',
|
||||
id: 'noMutedChannelsInGuildsInFolderCount',
|
||||
type: 'switch',
|
||||
value: true
|
||||
},
|
||||
{
|
||||
name: "Don't display badge on expanded folders",
|
||||
id: 'expandedFolders',
|
||||
type: 'switch',
|
||||
value: true
|
||||
},
|
||||
{
|
||||
name: 'Display badge on servers',
|
||||
id: 'guilds',
|
||||
type: 'switch',
|
||||
value: true
|
||||
},
|
||||
{
|
||||
name: 'Display badge on muted servers',
|
||||
id: 'mutedGuilds',
|
||||
type: 'switch',
|
||||
value: true
|
||||
},
|
||||
{
|
||||
name: 'Ignore muted channels in server unread badge count',
|
||||
id: 'noMutedInGuildCount',
|
||||
type: 'switch',
|
||||
value: true
|
||||
},
|
||||
{
|
||||
name: 'Display badge on channels',
|
||||
id: 'channels',
|
||||
type: 'switch',
|
||||
value: true
|
||||
},
|
||||
{
|
||||
name: 'Display badge on muted channels',
|
||||
id: 'mutedChannels',
|
||||
type: 'switch',
|
||||
value: true
|
||||
},
|
||||
{
|
||||
name: 'Display badge on left side on channels',
|
||||
note: "In case you want the settings button to stay where it always is. This however doesn't move it before the NSFW tag if you use the BetterNsfwTag plugin",
|
||||
id: 'channelsDisplayOnLeft',
|
||||
type: 'switch',
|
||||
value: false
|
||||
},
|
||||
{
|
||||
name: 'Background color',
|
||||
id: 'backgroundColor',
|
||||
type: 'color',
|
||||
value: '#7289da',
|
||||
options: {
|
||||
defaultColor: '#7289da'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Text color',
|
||||
id: 'textColor',
|
||||
type: 'color',
|
||||
value: '#ffffff',
|
||||
options: {
|
||||
defaultColor: '#ffffff'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Muted channel badge darkness',
|
||||
id: 'mutedChannelBadgeDarkness',
|
||||
type: 'slider',
|
||||
value: 0.25,
|
||||
min: 0,
|
||||
max: 1,
|
||||
equidistant: true,
|
||||
options: {
|
||||
equidistant: true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
/* Build */
|
||||
const buildPlugin = ([Plugin, Api], BasePlugin) => {
|
||||
const { Settings, Utilities, WebpackModules, DiscordModules, ColorConverter, ReactComponents, PluginUtilities, Logger, ReactTools, ModalStack } = Api;
|
||||
const { React, ChannelStore } = DiscordModules;
|
||||
|
||||
const Patcher = XenoLib.createSmartPatcher(Api.Patcher);
|
||||
|
||||
const ReactSpring = WebpackModules.getByProps('useTransition');
|
||||
const BadgesModule = WebpackModules.getByProps('NumberBadge');
|
||||
const StoresModule = WebpackModules.getByProps('useStateFromStores');
|
||||
|
||||
/* discord won't let me access it, so I remade it :( */
|
||||
class BadgeContainer extends React.PureComponent {
|
||||
componentDidMount() {
|
||||
this.forceUpdate();
|
||||
}
|
||||
componentWillAppear(e) {
|
||||
e();
|
||||
}
|
||||
componentWillEnter(e) {
|
||||
e();
|
||||
}
|
||||
componentWillLeave(e) {
|
||||
this.timeoutId = setTimeout(e, 300);
|
||||
}
|
||||
componentWillUnmount() {
|
||||
clearTimeout(this.timeoutId);
|
||||
}
|
||||
render() {
|
||||
return React.createElement(
|
||||
ReactSpring.animated.div,
|
||||
{
|
||||
className: this.props.className,
|
||||
style: this.props.animatedStyle
|
||||
},
|
||||
this.props.children
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const UnreadStore = WebpackModules.getByProps('getUnreadCount');
|
||||
const MuteModule = WebpackModules.getByProps('isMuted');
|
||||
const AltChannelStore = WebpackModules.find(m => m.getChannels && m.getChannels.length === 1);
|
||||
|
||||
const getUnreadCount = (guildId, includeMuted) => {
|
||||
const channels = AltChannelStore.getChannels(guildId);
|
||||
let count = 0;
|
||||
for (const { channel } of channels.SELECTABLE) {
|
||||
/* isChannelMuted is SLOW! */
|
||||
if (includeMuted || (!MuteModule.isChannelMuted(channel.guild_id, channel.id) && (!channel.parent_id || !MuteModule.isChannelMuted(channel.guild_id, channel.parent_id)))) count += UnreadStore.getUnreadCount(channel.id);
|
||||
}
|
||||
return count;
|
||||
};
|
||||
|
||||
class Slider extends Settings.SettingField {
|
||||
/* ripped out of ZeresPluginLibrary, because it does thingsin a way I DISLIKE!
|
||||
but otherwise full credits to Zerebos
|
||||
https://github.com/rauenzi/BDPluginLibrary/blob/master/src/ui/settings/types/slider.js
|
||||
*/
|
||||
constructor(name, note, min, max, value, onChange, options = {}) {
|
||||
const props = {
|
||||
onChange: _ => _,
|
||||
defaultValue: value,
|
||||
disabled: options.disabled ? true : false,
|
||||
minValue: min,
|
||||
maxValue: max,
|
||||
handleSize: 10,
|
||||
initialValue: value /* added this */
|
||||
};
|
||||
if (options.fillStyles) props.fillStyles = options.fillStyles;
|
||||
if (options.markers) props.markers = options.markers;
|
||||
if (options.stickToMarkers) props.stickToMarkers = options.stickToMarkers;
|
||||
if (typeof options.equidistant != 'undefined') props.equidistant = options.equidistant;
|
||||
super(name, note, onChange, DiscordModules.Slider, Object.assign(props, { onValueChange: v => this.onChange(v) }));
|
||||
}
|
||||
}
|
||||
|
||||
return class UnreadBadgesRedux extends BasePlugin(Plugin) {
|
||||
constructor() {
|
||||
super();
|
||||
XenoLib.changeName(__filename, 'UnreadBadgesRedux');
|
||||
const oOnStart = this.onStart.bind(this);
|
||||
this.onStart = () => {
|
||||
try {
|
||||
oOnStart();
|
||||
} catch (e) {
|
||||
Logger.stacktrace('Failed to start!', e);
|
||||
PluginUpdater.checkForUpdate(this.name, this.version, this._config.info.github_raw);
|
||||
XenoLib.Notifications.error(`[**${this.name}**] Failed to start! 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 });
|
||||
try {
|
||||
this.onStop();
|
||||
} catch (e) { }
|
||||
}
|
||||
};
|
||||
try {
|
||||
WebpackModules.getByProps('openModal', 'hasModalOpen').closeModal(`${this.name}_DEP_MODAL`);
|
||||
} catch (e) { }
|
||||
}
|
||||
onStart() {
|
||||
this.promises = { state: { cancelled: false } };
|
||||
this.patchedModules = [];
|
||||
this.patchAll();
|
||||
PluginUtilities.addStyle(
|
||||
this.short + '-CSS',
|
||||
`
|
||||
.unread-badge {
|
||||
right: unset;
|
||||
}
|
||||
`
|
||||
);
|
||||
}
|
||||
|
||||
onStop() {
|
||||
this.promises.state.cancelled = true;
|
||||
Patcher.unpatchAll();
|
||||
PluginUtilities.removeStyle(this.short + '-CSS');
|
||||
this.forceUpdateAll();
|
||||
}
|
||||
|
||||
buildSetting(data) {
|
||||
if (data.type === 'color') {
|
||||
const setting = new XenoLib.Settings.ColorPicker(data.name, data.note, data.value, data.onChange, data.options);
|
||||
if (data.id) setting.id = data.id;
|
||||
return setting;
|
||||
} else if (data.type === 'slider') {
|
||||
const options = {};
|
||||
const { name, note, value, onChange, min, max } = data;
|
||||
if (typeof data.markers !== 'undefined') options.markers = data.markers;
|
||||
if (typeof data.stickToMarkers !== 'undefined') options.stickToMarkers = data.stickToMarkers;
|
||||
const setting = new Slider(name, note, min, max, value, onChange, options);
|
||||
if (data.id) setting.id = data.id;
|
||||
return setting;
|
||||
}
|
||||
return super.buildSetting(data);
|
||||
}
|
||||
|
||||
saveSettings(_, setting, value) {
|
||||
super.saveSettings(_, setting, value);
|
||||
this.forceUpdateAll();
|
||||
}
|
||||
|
||||
forceUpdateAll() {
|
||||
this.patchedModules.forEach(e => e());
|
||||
}
|
||||
|
||||
/* PATCHES */
|
||||
|
||||
patchAll() {
|
||||
Utilities.suppressErrors(this.patchBlobMask.bind(this), 'BlobMask patch')(this.promises.state);
|
||||
Utilities.suppressErrors(this.patchGuildIcon.bind(this), 'GuildIcon patch')(this.promises.state);
|
||||
Utilities.suppressErrors(this.patchChannelItem.bind(this), 'ChannelItem patch')(this.promises.state);
|
||||
Utilities.suppressErrors(this.patchConnectedGuild.bind(this), 'ConnectedGuild patch')(this.promises.state);
|
||||
Utilities.suppressErrors(this.patchGuildFolder.bind(this), 'GuildFolder patch')(this.promises.state);
|
||||
}
|
||||
|
||||
async patchChannelItem(promiseState) {
|
||||
const TextChannel = await ReactComponents.getComponentByName('TextChannel', `.${XenoLib.getSingleClass('mentionsBadge containerDefault')}`);
|
||||
if (promiseState.cancelled) return;
|
||||
const settings = this.settings;
|
||||
const MentionsBadgeClassname = XenoLib.getClass('iconVisibility mentionsBadge');
|
||||
function UnreadBadge(e) {
|
||||
const unreadCount = StoresModule.useStateFromStores([UnreadStore], () => {
|
||||
if ((e.muted && !settings.misc.mutedChannels) || !settings.misc.channels) return 0;
|
||||
const count = UnreadStore.getUnreadCount(e.channelId);
|
||||
if (count > 1000) return Math.floor(count / 1000) * 1000; /* only trigger rerender if it changes in thousands */
|
||||
return count;
|
||||
});
|
||||
if (!unreadCount) return null;
|
||||
return React.createElement(
|
||||
'div',
|
||||
{
|
||||
className: MentionsBadgeClassname
|
||||
},
|
||||
BadgesModule.NumberBadge({ count: unreadCount, color: e.muted ? ColorConverter.darkenColor(settings.misc.backgroundColor, settings.misc.mutedChannelBadgeDarkness * 100) : settings.misc.backgroundColor, style: { color: e.muted ? ColorConverter.darkenColor(settings.misc.textColor, settings.misc.mutedChannelBadgeDarkness * 100) : settings.misc.textColor } })
|
||||
);
|
||||
}
|
||||
Patcher.after(TextChannel.component.prototype, 'render', (_this, _, ret) => {
|
||||
const popout = Utilities.findInReactTree(ret, e => e && e.type && e.type.displayName === 'Popout');
|
||||
if (!popout || typeof popout.props.children !== 'function') return;
|
||||
const oChildren = popout.props.children;
|
||||
popout.props.children = () => {
|
||||
try {
|
||||
const ret = oChildren();
|
||||
const props = Utilities.findInReactTree(ret, e => e && Array.isArray(e.children) && e.children.find(e => e && e.type && e.type.displayName === 'ConnectedEditButton'));
|
||||
if (!props || !props.children) return ret;
|
||||
const badge = React.createElement(UnreadBadge, { channelId: _this.props.channel.id, muted: _this.props.muted && !_this.props.selected });
|
||||
props.children.splice(this.settings.misc.channelsDisplayOnLeft ? 0 : 2, 0, badge);
|
||||
return ret;
|
||||
} catch (err) {
|
||||
Logger.stacktrace('Failed modifying return of original children in TextChannel!', err);
|
||||
try {
|
||||
return oChildren();
|
||||
} catch (err) {
|
||||
Logger.stacktrace('Failed returning return of original children in TextChannel!', err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
TextChannel.forceUpdateAll();
|
||||
}
|
||||
|
||||
async patchGuildFolder(promiseState) {
|
||||
const settings = this.settings;
|
||||
const FolderStore = WebpackModules.getByProps('isFolderExpanded');
|
||||
function BlobMaskWrapper(e) {
|
||||
e.__UBR_unread_count = StoresModule.useStateFromStores([UnreadStore, MuteModule], () => {
|
||||
if ((e.__UBR_folder_expanded && settings.misc.expandedFolders) || !settings.misc.folders) return 0;
|
||||
let count = 0;
|
||||
for (let i = 0; i < e.__UBR_guildIds.length; i++) {
|
||||
const guildId = e.__UBR_guildIds[i];
|
||||
if (!settings.misc.noMutedGuildsInFolderCount || (settings.misc.noMutedGuildsInFolderCount && !MuteModule.isMuted(guildId))) count += getUnreadCount(guildId, !settings.misc.noMutedChannelsInGuildsInFolderCount);
|
||||
}
|
||||
if (count > 1000) return Math.floor(count / 1000) * 1000; /* only trigger rerender if it changes in thousands */
|
||||
return count;
|
||||
});
|
||||
return React.createElement(e.__UBR_old_type, e);
|
||||
}
|
||||
BlobMaskWrapper.displayName = 'BlobMask';
|
||||
const GuildFolderMemo = WebpackModules.find(m => m.type && ((m.__powercordOriginal_type || m.type).toString().indexOf('.Messages.SERVER_FOLDER_PLACEHOLDER') !== -1 || (m.type.render && (m.type.__powercordOriginal_render || m.type.render).toString().indexOf('.Messages.SERVER_FOLDER_PLACEHOLDER') !== -1)));
|
||||
Patcher.after(GuildFolderMemo.type.render ? GuildFolderMemo.type : GuildFolderMemo, GuildFolderMemo.type.render ? 'render' : 'type', (_, [props], ret) => {
|
||||
const mask = Utilities.findInReactTree(ret, e => e && e.type && e.type.displayName === 'BlobMask');
|
||||
if (!mask) return;
|
||||
mask.props.__UBR_old_type = mask.type;
|
||||
mask.props.__UBR_guildIds = props.guildIds;
|
||||
mask.props.__UBR_folder_expanded = FolderStore.isFolderExpanded(props.folderId);
|
||||
mask.type = BlobMaskWrapper;
|
||||
});
|
||||
const folders = [...document.querySelectorAll('.wrapper-21YSNc')].map(e => ReactTools.getOwnerInstance(e));
|
||||
folders.forEach(instance => {
|
||||
if (!instance) return;
|
||||
const unpatch = Patcher.after(instance, 'render', (_, __, ret) => {
|
||||
unpatch();
|
||||
if (!ret) return;
|
||||
ret.key = `GETGOOD${Math.random()}`;
|
||||
const oRef = ret.props.setFolderRef;
|
||||
ret.props.setFolderRef = (e, n) => {
|
||||
_.forceUpdate();
|
||||
return oRef(e, n);
|
||||
};
|
||||
});
|
||||
instance.forceUpdate();
|
||||
});
|
||||
}
|
||||
|
||||
async patchConnectedGuild(promiseState) {
|
||||
const selector = `.${XenoLib.getSingleClass('listItem', true)}`;
|
||||
const ConnectedGuild = await ReactComponents.getComponentByName('DragSource(ConnectedGuild)', selector);
|
||||
if (!ConnectedGuild.selector) ConnectedGuild.selector = selector;
|
||||
if (promiseState.cancelled) return;
|
||||
const settings = this.settings;
|
||||
function PatchedConnectedGuild(e) {
|
||||
/* get on my level scrublords */
|
||||
e.__UBR_unread_count = StoresModule.useStateFromStores([UnreadStore, MuteModule], () => (!settings.misc.guilds || (!settings.misc.mutedGuilds && MuteModule.isMuted(e.guildId)) ? 0 : getUnreadCount(e.guildId, !settings.misc.noMutedInGuildCount)));
|
||||
return e.__UBR_old_type(e);
|
||||
}
|
||||
PatchedConnectedGuild.displayName = 'ConnectedGuild';
|
||||
Patcher.after(ConnectedGuild.component.prototype, 'render', (_this, _, ret) => {
|
||||
const old = ret.props.children;
|
||||
ret.props.children = e => {
|
||||
const ret2 = old(e);
|
||||
ret2.props.__UBR_old_type = ret2.type;
|
||||
ret2.type = PatchedConnectedGuild;
|
||||
return ret2;
|
||||
};
|
||||
});
|
||||
ConnectedGuild.forceUpdateAll();
|
||||
this.patchedModules.push(ConnectedGuild.forceUpdateAll.bind(ConnectedGuild));
|
||||
}
|
||||
|
||||
async patchGuildIcon(promiseState) {
|
||||
const selector = `.${XenoLib.getSingleClass('listItem', true)}`;
|
||||
const Guild = await ReactComponents.getComponentByName('Guild', selector);
|
||||
if (!Guild.selector) Guild.selector = selector;
|
||||
if (promiseState.cancelled) return;
|
||||
Patcher.after(Guild.component.prototype, 'render', (_this, _, ret) => {
|
||||
const mask = Utilities.findInTree(ret, e => e && e.type && e.type.displayName === 'BlobMask', { walkable: ['props', 'children'] });
|
||||
if (!mask) return;
|
||||
mask.props.__UBR_unread_count = _this.props.__UBR_unread_count;
|
||||
mask.props.guildId = _this.props.guildId;
|
||||
});
|
||||
Guild.forceUpdateAll();
|
||||
}
|
||||
|
||||
async patchBlobMask(promiseState) {
|
||||
const selector = `.${XenoLib.getSingleClass('lowerBadge wrapper')}`;
|
||||
const BlobMask = await ReactComponents.getComponentByName('BlobMask', selector);
|
||||
if (!BlobMask.selector) BlobMask.selector = selector;
|
||||
if (promiseState.cancelled) return;
|
||||
const ensureUnreadBadgeMask = _this => {
|
||||
if (_this.state.unreadBadgeMask) return;
|
||||
_this.state.unreadBadgeMask = new ReactSpring.Controller({
|
||||
spring: 0
|
||||
});
|
||||
};
|
||||
Patcher.after(BlobMask.component.prototype, 'componentDidMount', _this => {
|
||||
if (typeof _this.props.__UBR_unread_count !== 'number') return;
|
||||
ensureUnreadBadgeMask(_this);
|
||||
_this.state.unreadBadgeMask
|
||||
.update({
|
||||
spring: !!_this.props.__UBR_unread_count,
|
||||
immediate: true
|
||||
})
|
||||
.start();
|
||||
});
|
||||
Patcher.after(BlobMask.component.prototype, 'componentWillUnmount', _this => {
|
||||
if (typeof _this.props.__UBR_unread_count !== 'number') return;
|
||||
if (!_this.state.unreadBadgeMask) return;
|
||||
if (typeof _this.state.unreadBadgeMask.destroy === 'function') _this.state.unreadBadgeMask.destroy();
|
||||
else _this.state.unreadBadgeMask.dispose();
|
||||
_this.state.unreadBadgeMask = null;
|
||||
});
|
||||
Patcher.after(BlobMask.component.prototype, 'componentDidUpdate', (_this, [{ __UBR_unread_count }]) => {
|
||||
if (typeof _this.props.__UBR_unread_count !== 'number' || _this.props.__UBR_unread_count === __UBR_unread_count) return;
|
||||
ensureUnreadBadgeMask(_this);
|
||||
_this.state.unreadBadgeMask
|
||||
.update({
|
||||
spring: !!_this.props.__UBR_unread_count,
|
||||
immediate: !document.hasFocus(),
|
||||
config: {
|
||||
friction: 40,
|
||||
tension: 900,
|
||||
mass: 1
|
||||
}
|
||||
})
|
||||
.start();
|
||||
});
|
||||
const LowerBadgeClassname = XenoLib.joinClassNames(XenoLib.getClass('wrapper lowerBadge'), 'unread-badge');
|
||||
Patcher.after(BlobMask.component.prototype, 'render', (_this, _, ret) => {
|
||||
if (typeof _this.props.__UBR_unread_count !== 'number') return;
|
||||
const badges = Utilities.findInTree(ret, e => e && e.type && e.type.displayName === 'TransitionGroup', { walkable: ['props', 'children'] });
|
||||
const masks = Utilities.findInTree(ret, e => e && e.type === 'mask', { walkable: ['props', 'children'] });
|
||||
if (!badges || !masks) return;
|
||||
ensureUnreadBadgeMask(_this);
|
||||
/* if count is 0, we're animating out, and as such, it's better to at least still display the old
|
||||
count while animating out
|
||||
*/
|
||||
const counter = _this.props.__UBR_unread_count || _this.state.__UBR_old_unread_count;
|
||||
if (_this.props.__UBR_unread_count) _this.state.__UBR_old_unread_count = _this.props.__UBR_unread_count;
|
||||
const width = BadgesModule.getBadgeWidthForValue(counter);
|
||||
const unreadCountMaskSpring = (_this.state.unreadBadgeMask.animated || _this.state.unreadBadgeMask.springs).spring;
|
||||
masks.props.children.push(
|
||||
React.createElement(ReactSpring.animated.rect, {
|
||||
x: -4,
|
||||
y: 28,
|
||||
width: width + 8,
|
||||
height: 24,
|
||||
rx: 12,
|
||||
ry: 12,
|
||||
opacity: unreadCountMaskSpring.to([0, 0.5, 1], [0, 0, 1]),
|
||||
transform: unreadCountMaskSpring.to([0, 1], [-16, 0]).to(e => `translate(${e} ${-e})`),
|
||||
fill: 'black'
|
||||
})
|
||||
);
|
||||
badges.props.children.unshift(
|
||||
React.createElement(
|
||||
BadgeContainer,
|
||||
{
|
||||
className: LowerBadgeClassname,
|
||||
animatedStyle: {
|
||||
opacity: unreadCountMaskSpring.to([0, 0.5, 1], [0, 0, 1]),
|
||||
transform: unreadCountMaskSpring.to(e => `translate(${-20 + 20 * e} ${-1 * (16 - 16 * e)})`)
|
||||
}
|
||||
},
|
||||
React.createElement(BadgesModule.NumberBadge, { count: counter, color: this.settings.misc.backgroundColor, style: { color: this.settings.misc.textColor } })
|
||||
)
|
||||
);
|
||||
});
|
||||
BlobMask.forceUpdateAll();
|
||||
}
|
||||
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 New Issue