//META{"name":"MentionAliasesRedux","source":"https://github.com/1Lighty/BetterDiscordPlugins/blob/master/Plugins/MentionAliasesRedux/MentionAliasesRedux.plugin.js","website":"https://1lighty.github.io/BetterDiscordStuff/?plugin=MentionAliasesRedux"}*// /*@cc_on @if (@_jscript) // Offer to self-install for clueless users that try to run this directly. var shell = WScript.CreateObject('WScript.Shell'); var fs = new ActiveXObject('Scripting.FileSystemObject'); var pathPlugins = shell.ExpandEnvironmentStrings('%APPDATA%\\BetterDiscord\\plugins'); var pathSelf = WScript.ScriptFullName; // Put the user at ease by addressing them in the first person shell.Popup('It looks like you\'ve mistakenly tried to run me directly. \n(Don\'t do that!)', 0, 'I\'m a plugin for BetterDiscord', 0x30); if (fs.GetParentFolderName(pathSelf) === fs.GetAbsolutePathName(pathPlugins)) { shell.Popup('I\'m in the correct folder already.\nJust reload Discord with Ctrl+R.', 0, 'I\'m already installed', 0x40); } else if (!fs.FolderExists(pathPlugins)) { shell.Popup('I can\'t find the BetterDiscord plugins folder.\nAre you sure it\'s even installed?', 0, 'Can\'t install myself', 0x10); } else if (shell.Popup('Should I copy myself to BetterDiscord\'s plugins folder for you?', 0, 'Do you need some help?', 0x34) === 6) { fs.CopyFile(pathSelf, fs.BuildPath(pathPlugins, fs.GetFileName(pathSelf)), true); // Show the user where to put plugins in the future shell.Exec('explorer ' + pathPlugins); shell.Popup('I\'m installed!\nJust reload Discord with Ctrl+R.', 0, 'Successfully installed', 0x40); } WScript.Quit(); @else@*/ /* * Copyright © 2019-2020, _Lighty_ * All rights reserved. * Code may not be redistributed, modified or otherwise taken without explicit permission. */ var MentionAliasesRedux = (() => { /* Setup */ const config = { main: 'index.js', info: { name: 'MentionAliasesRedux', authors: [ { name: 'Lighty', discord_id: '239513071272329217', github_username: 'LightyPon', twitter_username: '' } ], version: '2.0.6', description: 'Set custom @mention aliases, that can also appear next to their name (nearly) anywhere, as well as have mention groups to mention multiple people at once.', github: 'https://github.com/1Lighty', github_raw: 'https://raw.githubusercontent.com/1Lighty/BetterDiscordPlugins/master/Plugins/MentionAliasesRedux/MentionAliasesRedux.plugin.js' }, changelog: [ { title: "bug b' gone", type: 'fixed', items: ['Fixed tag not showing within chat'] } ], defaultConfig: [ { type: 'category', id: 'display', name: 'Display settings', collapsible: true, shown: true, settings: [ { name: 'Display tags menu button', id: 'displayButton', type: 'switch', value: true }, { name: 'Display owner tags', id: 'displayOwnerTags', type: 'switch', value: true }, { name: 'Display tag in user popups', id: 'displayPopupTags', type: 'switch', value: true }, { name: 'Display tags in members list', id: 'displayMemberTags', type: 'switch', value: true }, { name: 'Display tag in messages', id: 'displayMessageTags', type: 'switch', value: true }, { name: 'Display tag on the right side of the name in compact mode', id: 'displayRightCompact', type: 'switch', value: true }, { name: 'Display alias in AKA in DMs', id: 'displayAKATags', type: 'switch', value: true }, { name: 'Display tag in DMs list', id: 'displayDMTags', type: 'switch', value: true }, { name: 'Display tag in Mutual Friends tab in user modals', id: 'displayMutualFriendsTags', type: 'switch', value: true }, { name: 'Display tag in friends list', id: 'displayFriendsListTags', type: 'switch', value: true }, { name: 'Tag text color', id: 'tagColor', type: 'color', value: '#ffffff', options: { defaultColor: '#ffffff' } }, { name: 'Tag background color', id: 'tagBackground', type: 'color', value: '#56555e', options: { defaultColor: '#56555e' } }, { name: 'Tag preview', type: 'preview' } ] } ] }; /* Build */ const buildPlugin = ([Plugin, Api]) => { const { ContextMenu, EmulatedTooltip, Toasts, Settings, Popouts, Modals, Utilities, WebpackModules, Filters, DiscordModules, ColorConverter, DOMTools, DiscordClasses, DiscordSelectors, ReactTools, ReactComponents, DiscordAPI, Logger, Patcher, PluginUpdater, PluginUtilities, DiscordClassModules, Structs } = Api; const { React, ModalStack, ContextMenuActions, ContextMenuItem, ContextMenuItemsGroup, ReactDOM, ChannelStore, GuildStore, UserStore, DiscordConstants, Dispatcher, GuildMemberStore, GuildActions, SwitchRow, EmojiUtils, RadioGroup, Permissions, TextElement, FlexChild, PopoutOpener, Textbox, UserSettingsStore, MessageStore } = DiscordModules; const UserStatusStore = WebpackModules.getByProps('getStatus'); const ChannelTextAreaButton = WebpackModules.getByDisplayName('ChannelTextAreaButton'); const Clickable = WebpackModules.getByDisplayName('Clickable'); const DeprecatedModal = WebpackModules.getByDisplayName('DeprecatedModal'); const FormItem = WebpackModules.getByDisplayName('FormItem'); const FormText = WebpackModules.getByDisplayName('FormText'); const FormTitle = WebpackModules.getByDisplayName('FormTitle'); const Icon = WebpackModules.getByDisplayName('Icon'); const ListItem = WebpackModules.getByDisplayName('ListItem'); const AutocompleteContentClassname = XenoLib.getClass('autocomplete content'); const AutocompleteDescriptionClassname = XenoLib.getClass('autocomplete description'); const AvatarWrapperClassname = XenoLib.getClass('layout avatar'); const ChannelTextAreaButtonClassname = XenoLib.getClass('textArea button'); const CloseButtonClassname = XenoLib.joinClassNames(XenoLib.getClass('channel closeButton'), XenoLib.getClass('item clickable')); const CloseIconClassname = XenoLib.getClass('channel closeIcon'); const ContentTitleClassname = XenoLib.getClass('autocomplete contentTitle'); const EmptyPlaceHolderBodyClassname = XenoLib.getClass('emptyPlaceholder body'); const EmptyPlaceHolderClassname = XenoLib.getClass('emptyPlaceholder'); const InputClassname = XenoLib.getClass('reset input'); const ItemChannelClassname = XenoLib.getClass('channel'); const MemberTagClassname = XenoLib.joinClassNames('mentionAlias', XenoLib.getClass('botTagRegular'), XenoLib.getClass('member botTag')); const MessageCompactTagClassname = XenoLib.joinClassNames('mentionAlias', XenoLib.getClass('botTagRegular'), XenoLib.getClass('botTagCompact')); const MessageCozyTagClassname = XenoLib.joinClassNames('mentionAlias', XenoLib.getClass('botTagRegular'), XenoLib.getClass('botTagCozy')); const ModalContainerClassname = XenoLib.getClass('mobile container'); const ModalContentClassname = XenoLib.getClass('mobile container content'); const NoteClassname = XenoLib.getClass('switchItem note'); const PopoutTagClassname = XenoLib.joinClassNames('mentionAlias', XenoLib.getClass('botTagRegular'), XenoLib.getClass('nameTag bot')); const roughlyMatches = WebpackModules.getByRegex(/{var \w=\w\.length,\w=\w\.length;if\(\w>\w\)return!1;if\(\w===\w\)return \w===\w;\w:/); const AnimatedAvatar = (WebpackModules.getByProps('AnimatedAvatar') || {}).AnimatedAvatar; const renderAvatar = user => React.createElement(AnimatedAvatar, { size: 'SIZE_32', src: user.getAvatarURL(), status: UserStatusStore.getStatus(user.id), isMobile: UserStatusStore.isMobileOnline(user.id), isTyping: false, statusTooltip: true }); const renderAlias = (name, description, color, noAt) => React.createElement(FlexChild, { align: FlexChild.Align.CENTER, className: AutocompleteContentClassname }, React.createElement(FlexChild.Child, { grow: 1 }, React.createElement(TextElement.default, { style: { color } }, noAt ? undefined : '@', name)), React.createElement(TextElement.default, { className: AutocompleteDescriptionClassname }, description)); class NewGroupModal extends React.PureComponent { constructor(props) { super(props); this.state = { name: '' }; XenoLib.DiscordUtils.bindAll(this, ['handleClose', 'handleSubmit']); } handleSubmit(e) { e.preventDefault(); if (!this.state.name.length) return Toasts.error('A name is needed!'); Dispatcher.dispatch({ type: 'MA_SET_GROUP', id: Math.floor(4294967296 * Math.random()), name: this.state.name, users: [this.props.userId] }); this.handleClose(); } handleClose() { this.props.onClose(); } render() { return React.createElement( DeprecatedModal, { className: ModalContainerClassname, tag: 'form', onSubmit: this.handleSubmit, size: DeprecatedModal.Sizes.SMALL }, React.createElement(DeprecatedModal.Header, {}, React.createElement(FormTitle, { tag: 'h4' }, 'New Group')), React.createElement(DeprecatedModal.Content, { className: ModalContentClassname }, React.createElement(FormItem, { title: 'Group Name', className: InputClassname }, React.createElement(Textbox, { autoFocus: true, maxLength: 32, value: this.state.name, onChange: name => this.setState({ name }) })), React.createElement(FormText, { className: NoteClassname, type: 'description' }, 'User will be added to the group automatically.')), React.createElement(DeprecatedModal.Footer, {}, React.createElement(XenoLib.ReactComponents.Button, { type: 'submit', color: XenoLib.ReactComponents.Button.Colors.BRAND }, 'Save'), React.createElement(XenoLib.ReactComponents.Button, { type: 'button', look: XenoLib.ReactComponents.Button.Looks.LINK, color: XenoLib.ReactComponents.Button.Colors.PRIMARY, onClick: this.handleClose }, 'Cancel')) ); } } class SetGroupModal extends React.PureComponent { constructor(props) { super(props); this.state = { name: props.group.name, users: props.group.users }; XenoLib.DiscordUtils.bindAll(this, ['handleClose', 'handleSubmit']); } handleSubmit(e) { e.preventDefault(); if (!this.state.name.length) return Toasts.error('A name is needed!'); Dispatcher.dispatch({ type: 'MA_SET_GROUP', id: this.props.group.id, name: this.state.name, users: (this.state.users.length && this.state.users) || null }); this.handleClose(); } handleClose() { this.props.onClose(); } renderUser(user, index) { if (!user) return; return React.createElement( ListItem, { className: ItemChannelClassname, avatar: renderAvatar(user), name: user.username /* possibly their alias? */, /* subText: this.renderSubtitle() */ style: { maxWidth: 'unset', marginLeft: 0 } }, React.createElement( Clickable, { className: CloseButtonClassname, style: { display: 'block' }, onClick: () => { this.state.users.splice(index, 1); this.forceUpdate(); } }, React.createElement(Icon, { className: CloseIconClassname, name: 'Nova_Close' }) ) ); } render() { return React.createElement( DeprecatedModal, { className: ModalContainerClassname, tag: 'form', onSubmit: this.handleSubmit, size: DeprecatedModal.Sizes.SMALL }, React.createElement(DeprecatedModal.Header, {}, React.createElement(Textbox, { maxLength: 32, value: this.state.name, onChange: name => this.setState({ name }) }), React.createElement(XenoLib.ReactComponents.Button, { type: 'button', look: XenoLib.ReactComponents.Button.Looks.LINK, color: XenoLib.ReactComponents.Button.Colors.PRIMARY, onClick: this.handleClose }, 'Cancel'), React.createElement(XenoLib.ReactComponents.Button, { type: 'submit', color: XenoLib.ReactComponents.Button.Colors.BRAND }, 'Save')), React.createElement( DeprecatedModal.Content, { className: ModalContentClassname } /*React.createElement(FlexChild,{justify: FlexChild.Justify.CENTER,className: 'MA-add-user'},React.createElement('img', {className: 'addRoleIcon-3YjErH',src: '/assets/cef02719c12d8aaf38894c16dca7fbe6.svg'})), */, this.state.users.map((userId, index) => this.renderUser(UserStore.getUser(userId), index)) ) ); } } class SetAliasModal extends (WebpackModules.getByDisplayName('ChangeNickname') || (Logger.error('Failed to find modal "ChangeNickname"'), class fuck {})) { constructor(props) { super(props); this.handleSubmit = this.handleSubmitPatch.bind(this); this.renderWarning = () => {}; } handleSubmitPatch(e) { e.preventDefault(); Dispatcher.dispatch({ type: 'MA_SET_ALIAS', userId: this.props.user.id, alias: this.state.nick || null }); this.close(); } render() { this.props.errors = {}; const ret = super.render(); ret.props.children[0].props.children.props.children = 'Change Alias'; ret.props.children[1].props.children[1].props.title = 'Alias'; ret.props.children[1].props.children[2].props.children = 'Reset Alias'; return ret; } } class AliasItem extends React.PureComponent { constructor(props) { super(props); this.state = { hovered: false }; } render() { const m = {}; m[XenoLib.getClass('autocomplete selector')] = this.state.hovered; m[XenoLib.getClass('avatar layout')] = this.props.isUser; return React.createElement('div', { className: XenoLib.joinClassNames(WebpackModules.getByProps('autocomplete', 'selector').selector, WebpackModules.getByProps('autocomplete', 'selector').selectable, m), onMouseEnter: () => this.setState({ hovered: true }), onMouseLeave: () => this.setState({ hovered: false }), onContextMenu: this.props.onContextMenu, onClick: this.props.onClick }, this.props.children); } } class AliasesPopout extends React.PureComponent { constructor(props) { super(props); this.state = { mentions: props.getMentions() }; XenoLib.DiscordUtils.bindAll(this, ['handleContextMenu', 'handleClick', 'handleMentionsChange']); } handleClick(text, rich) { if (!this.props.channelTextAreaRef._editorRef) return Toasts.error('Internal error, cannot get _editorRef'); /* hm.. */ this.props.channelTextAreaRef._editorRef.ref.current.insertText(/* UserSettingsStore.useRichChatTextBox ? rich : */ text, true); } handleMentionsChange() { this.setState({ mentions: this.props.getMentions() }); } handleContextMenu(e, id, isGroup) { ContextMenuActions.openContextMenu(e, e => React.createElement( 'div', { className: DiscordClasses.ContextMenu.contextMenu }, XenoLib.createContextMenuGroup([ XenoLib.createContextMenuItem( 'Remove', () => { if (isGroup) { this.props.setGroup(id, null, null); } else { this.props.setAlias(id, null); } this.handleMentionsChange(); }, { disabled: ((DiscordAPI.currentGuild && DiscordAPI.currentGuild.ownerId) || DiscordAPI.currentChannel.ownerId) === id } ), XenoLib.createContextMenuItem('Edit', () => { if (isGroup) { ModalStack.push(e => React.createElement(SetGroupModal, { ...e, group: Utilities.deepclone(this.props.getGroup(id)) })); } else { ModalStack.push(() => React.createElement(SetAliasModal, { user: UserStore.getUser(id), nick: this.props.getUserAlias(id) })); } }) ]) ) ); } renderHeader(title, paddedTop) { return React.createElement(TextElement.default, { className: ContentTitleClassname, weight: TextElement.Weights.SEMIBOLD, size: TextElement.Sizes.SMALL, style: { paddingBottom: 8, paddingTop: paddedTop ? 8 : 0, paddingLeft: 8 } }, title); } render() { const children = []; try { this.state.mentions.users.forEach(({ userId, alias }, index) => { if (!index) children.push(this.renderHeader(`Users—${this.state.mentions.users.length}`)); const user = UserStore.getUser(userId); children.push(React.createElement(AliasItem, { isUser: true, onContextMenu: e => this.handleContextMenu(e, userId), onClick: () => this.handleClick(`@${user.tag}`, `<@${userId}>`) }, React.createElement('div', { className: AvatarWrapperClassname }, renderAvatar(user)), renderAlias(alias, user.tag, undefined, true))); }); this.state.mentions.groups.forEach((group, index) => { if (!index) children.push(this.renderHeader(`Groups—${this.state.mentions.groups.length}`, true)); const groupUsers = this.props.getGroupUsers(group.users); if (groupUsers.note.length > 32) groupUsers.note = groupUsers.note.substr(0, 32 - 3) + '...'; children.push(React.createElement(AliasItem, { onContextMenu: e => this.handleContextMenu(e, group.id, true), onClick: () => this.handleClick(groupUsers.tags, groupUsers.tagsIds) }, renderAlias(group.name, groupUsers.note, undefined, false))); }); if (!children.length) { children.push(React.createElement('div', { className: EmptyPlaceHolderClassname }, React.createElement('div', { className: EmptyPlaceHolderBodyClassname }, 'Well this is awkward.. No aliases\nfound, not even the owner!'))); } } catch (e) { Logger.stacktrace('Failed to render popup', e); return null; } return children; } } class Preview extends React.Component { render() { return React.createElement( 'div', {}, React.createElement( 'span', { className: XenoLib.getClass('botTagCozy username'), style: { color: 'rgb(126, 0, 255)' } }, '_Lighty_' ), React.createElement('span', { className: MessageCozyTagClassname }, 'Plugin Author') ); } } class PreviewField extends Settings.SettingField { constructor(name, note) { super(name, note, () => {}, Preview); } } return class MentionAliasesRedux extends Plugin { constructor() { super(); XenoLib._.bindAll(this, ['openAliasesPopout', 'queryAliases', 'setAlias', 'setGroup', 'handleSetAliasDispatch', 'handleSetGroupDispatch', 'getUserAlias', 'forceUpdateAll', 'handleContextMenu']); XenoLib.changeName(__filename, 'MentionAliasesRedux'); 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) {} } }; } onStart() { this.__menuBroken = false; this.patchedModules = []; /* migrate settings */ if (typeof this.settings.aliases !== 'undefined') { const settings = Utilities.deepclone(this.defaultSettings); settings.display.displayButton = this.settings.displayButton; settings.display.displayOwnerTags = this.settings.displayOwnerTags; settings.display.displayPopupTags = this.settings.displayPopupTags; settings.display.displayMemberTags = this.settings.displayMemberTags; settings.display.displayMessageTags = this.settings.displayMessageTags; settings.display.displayRightCompact = this.settings.displayRightCompact; settings.display.displayAKATags = this.settings.displayAKATags; settings.display.displayDMTags = this.settings.displayDMTags; this.aliases = this.settings.aliases; this.groups = Object.values(this.settings.groups); this.groups.forEach(group => { group.users = Object.values(group.users); group.id = Math.floor(4294967296 * Math.random()); }); this.settings = settings; this.saveSettings(); this.saveAliases(); this.saveGroups(); } this.aliases = XenoLib.loadData(this.name, 'aliases', { data: { '239513071272329217': 'Author' } }).data; this.groups = XenoLib.loadData(this.name, 'groups', { data: [] }).data; this.promises = { state: { cancelled: false } }; this.patchAll(); Dispatcher.subscribe('MA_SET_ALIAS', this.handleSetAliasDispatch); Dispatcher.subscribe('MA_SET_GROUP', this.handleSetGroupDispatch); PluginUtilities.addStyle( this.short + '-CSS', ` .MA-add-user { flex: 1 1 auto; position: absolute; width: 100%; left: 0; } .MA-add-user > img { opacity: .7; transition: opacity .2s ease; } .MA-add-user > img:hover { opacity: 1; } .MA-add-user > img { cursor: pointer; } ` ); this.toggleTagCSS(); } onStop() { this.promises.state.cancelled = true; Patcher.unpatchAll(); XenoLib.unpatchContext(this.handleContextMenu); Dispatcher.unsubscribe('MA_SET_ALIAS', this.handleSetAliasDispatch); Dispatcher.unsubscribe('MA_SET_GROUP', this.handleSetGroupDispatch); PluginUtilities.removeStyle(this.short + '-CSS'); this.toggleTagCSS(true); this.forceRerenderMessages(); } 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 === 'preview') { return new PreviewField(data.name, data.note); } return super.buildSetting(data); } /* 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)); } saveSettings(_, setting, value) { super.saveSettings(_, setting, value); if (setting === 'tagColor' || setting === 'tagBackground') this.toggleTagCSS(); else this.forceUpdateAll(); } toggleTagCSS(removeOnly) { PluginUtilities.removeStyle(this.short + '-tags-CSS'); if (removeOnly) return; PluginUtilities.addStyle( this.short + '-tags-CSS', ` .mentionAlias { background: ${this.settings.display.tagBackground}; color: ${this.settings.display.tagColor}; }` ); } saveAliases() { PluginUtilities.saveData(this.name, 'aliases', { data: this.aliases }); } saveGroups() { PluginUtilities.saveData(this.name, 'groups', { data: this.groups }); } handleContextMenu(_this, ret) { if (!_this.props.user) return; ret.props.children.push( XenoLib.createContextMenuGroup([ XenoLib.createContextMenuSubMenu('Mention Aliases', [ XenoLib.createContextMenuItem('Set Alias', () => ModalStack.push(() => React.createElement(SetAliasModal, { user: _this.props.user, nick: this.getUserAlias(_this.props.user.id) }))), XenoLib.createContextMenuSubMenu('Groups', [ this.groups.map(group => { const users = group.users; const isPart = users.indexOf(_this.props.user.id) !== -1; const onClick = () => { if (isPart) { users.splice(users.indexOf(_this.props.user.id), 1); if (!users.length) { this.setGroup(group.id, null, null); } else { Toasts.success('Removed!'); } } else { users.push(_this.props.user.id); Toasts.success('Added!'); } }; return XenoLib.createContextMenuSubMenu( group.name, [ XenoLib.createContextMenuItem((isPart && 'Remove') || 'Add', onClick), XenoLib.createContextMenuItem('Edit', () => ModalStack.push(e => React.createElement(SetGroupModal, { ...e, group: Utilities.deepclone(group) }))), XenoLib.createContextMenuItem('Delete Group', () => { this.setGroup(group.id, null, null); }) ], { action: () => { ContextMenuActions.closeContextMenu(); onClick(); } } ); }), XenoLib.createContextMenuItem('New Group', () => ModalStack.push(e => React.createElement(NewGroupModal, { ...e, userId: _this.props.user.id }))) ]) ]) ]) ); } handleSetAliasDispatch({ userId, alias }) { this.setAlias(userId, alias); } handleSetGroupDispatch({ id, name, users }) { this.setGroup(id, name, users); } getUserAlias(id) { const alias = this.aliases[id]; if (alias) return alias; if (this.settings.display.displayOwnerTags && DiscordAPI.currentGuild && DiscordAPI.currentGuild.ownerId === id) return 'Owner'; } createAlias(alias, className) { return React.createElement('span', { className }, alias); } patchTag(tag, alias) { const orig = tag.type; tag.type = e => { const ret = orig(e); const orig2 = ret.type; ret.type = e => { const ret2 = orig2(e); ret2.props.children.push(this.createAlias(alias, PopoutTagClassname)); return ret2; }; return ret; }; } getGroupUsers(users) { const ret = { note: '', tags: '', tagsIds: '' }; for (const userId of users) { const user = UserStore.getUser(userId); if (!user) continue; if (ret.note.length) ret.note += ' '; if (ret.tags.length) ret.tags += ' '; ret.note += user.username; ret.tags += `@${user.username}#${user.discriminator}`; ret.tagsIds += `<@${user.id}>`; } return ret; } queryAliases(query = null) { const ret = { users: [], groups: [], lowPriority: { users: [], groups: [] } }; const canTag = userId => { if (DiscordAPI.currentChannel.discordObject.isPrivate()) { return DiscordAPI.currentChannel.discordObject.recipients.indexOf(userId) !== -1 || userId === DiscordAPI.currentUser.id; } else { return GuildMemberStore.isMember(DiscordAPI.currentGuild.id, userId) && Permissions.can(DiscordConstants.Permissions.VIEW_CHANNEL, userId, DiscordAPI.currentChannel.discordObject); } }; for (const userId in this.aliases) { if (!UserStore.getUser(userId) || !canTag(userId)) continue; const alias = this.getUserAlias(userId); if (null === query || alias.toLowerCase().includes(query)) ret.users.push({ userId, alias }); else if (Object.keys(ret.lowPriority.users).length < 3 && roughlyMatches(query, alias.toLowerCase())) ret.lowPriority.users.push({ userId, alias }); } const ownerId = (DiscordAPI.currentGuild && DiscordAPI.currentGuild.ownerId) || DiscordAPI.currentChannel.ownerId; if (UserStore.getUser(ownerId)) { if (ownerId && ('owner'.includes(query) || null === query)) ret.users.push({ userId: ownerId, alias: 'Owner' }); else if (ownerId && roughlyMatches(query, 'owner')) ret.lowPriority.users.push({ userId: ownerId, alias: 'Owner' }); } for (const group of this.groups) { let lowPriority = false; if (null !== query && !group.name.toLowerCase().includes(query)) { lowPriority = roughlyMatches(query, group.name.toLowerCase()); if (!lowPriority) continue; } const users = []; for (const userId of group.users) { if (!UserStore.getUser(userId) || !canTag(userId)) continue; users.push(userId); } if (!users.length) continue; ((lowPriority && ret.lowPriority.groups) || ret.groups).push({ name: group.name, users: users, id: group.id }); } return ret; } async forceRerenderMessages() { if (ZeresPluginLibrary.DiscordAPI.currentChannel) { const Messages = await ReactComponents.getComponentByName('Messages', `.${XenoLib.getSingleClass('messages messagesWrapper')}`); const unpatch = ZeresPluginLibrary.Patcher.after(this.name + 'RERENDER', Messages.component.prototype, 'render', (_this, _, ret) => { unpatch(); const scroller = Utilities.getNestedProp(ret, 'props.children.1'); if (!scroller) return; /* crash repellent */ scroller.props.children[1].forEach(e => (e.key = DiscordModules.KeyGenerator())); }); Messages.forceUpdateAll(); } } /* PATCHES */ patchAll() { Utilities.suppressErrors(this.patchUserPopouts.bind(this), 'UserPopout patch')(this.promises.state); Utilities.suppressErrors(this.patchUserModals.bind(this), 'UserProfileBody patch')(this.promises.state); Utilities.suppressErrors(this.patchMemberListItem.bind(this), 'MemberListItem patch')(this.promises.state); Utilities.suppressErrors(this.patchPrivateChannel.bind(this), 'PrivateChannel patch')(this.promises.state); Utilities.suppressErrors(this.patchPeopleListItem.bind(this), 'FriendRow patch')(this.promises.state); Utilities.suppressErrors(this.patchMutualFriends.bind(this), 'MutualFriends patch')(this.promises.state); Utilities.suppressErrors(this.patchChannelTextArea.bind(this), 'ChannelTextArea patch')(this.promises.state); Utilities.suppressErrors(this.patchMessageUsername.bind(this), 'MessageUsername patch')(this.promises.state); Utilities.suppressErrors(this.patchGetNicknames.bind(this), 'getNicknames patch')(this.promises.state); Utilities.suppressErrors(this.patchQueryMentionResults.bind(this), 'queryMentionResults patch')(this.promises.state); Utilities.suppressErrors(this.patchRoleAutoComplete.bind(this), 'RoleAutoComplete patch')(this.promises.state); Utilities.suppressErrors(this.patchSelectAutocompletion.bind(this), 'selectAutocompletion patch')(this.promises.state); XenoLib.patchContext(this.handleContextMenu); } async patchUserPopouts(promiseState) { const UserPopout = await ReactComponents.getComponentByName('UserPopout', DiscordSelectors.UserPopout.userPopout); if (promiseState.cancelled) return; Patcher.after(UserPopout.component.prototype, 'render', (_this, _, ret) => { const alias = this.getUserAlias(_this.props.userId); const tag = Utilities.getNestedProp(ret, 'props.children.props.children.0.props.children.0.props.children.1.props.children.1.props.children'); if (!this.settings.display.displayPopupTags || !alias || !tag) return; this.patchTag(tag, alias); }); UserPopout.forceUpdateAll(); } async patchUserModals(promiseState) { const UserProfileBody = await ReactComponents.getComponentByName('UserProfileBody', DiscordSelectors.UserModal.root); if (promiseState.cancelled) return; Patcher.after(UserProfileBody.component.prototype, 'render', (_this, _, ret) => { const alias = this.getUserAlias(_this.props.user.id); const tag = Utilities.getNestedProp(ret, 'props.children.props.children.0.props.children.0.props.children.1.props.children.0'); if (!this.settings.display.displayPopupTags || !alias || !tag) return; this.patchTag(tag, alias); }); UserProfileBody.forceUpdateAll(); } patchMessageUsername() { const MessageHeader = WebpackModules.getByIndex(WebpackModules.getIndex(e => e.default && e.default.toString().indexOf('.ComponentActions.ANIMATE_CHAT_AVATAR') !== -1)); Patcher.after(MessageHeader, 'default', (_, [props], ret) => { const forceUpdate = React.useState()[1]; React.useEffect( function() { const e = function() { forceUpdate({}); }; Dispatcher.subscribe('MAR_FORCE_UPDATE', e); /* this will make it easier to update the message later */ return function() { Dispatcher.unsubscribe('MAR_FORCE_UPDATE', e); }; }, [props.message.id, forceUpdate] ); if (!props.message.author || !this.settings.display.displayMessageTags) return; const alias = this.getUserAlias(props.message.author.id); if (!alias) return; const username = Utilities.getNestedProp( Utilities.findInReactTree(ret.props.children, e => e && e.props && Array.isArray(e.props.children) && e.props.children.findIndex(m => m && m.type && m.type.displayName === 'Popout') !== -1), 'props.children' ); if (!username) return; /* eh? */ if (DiscordAPI.UserSettings.displayCompact && !this.settings.display.displayRightCompact) username.unshift(this.createAlias(alias, MessageCompactTagClassname)); else username.push(this.createAlias(alias, MessageCozyTagClassname)); }); if (!DiscordAPI.currentChannel) return; this.forceRerenderMessages(); } async patchMemberListItem(promiseState) { const MemberListItem = await ReactComponents.getComponentByName('MemberListItem', `.${XenoLib.getSingleClass('offline member', true)}`); if (promiseState.cancelled) return; Patcher.after(MemberListItem.component.prototype, 'render', (_this, _, ret) => { if (!_this.props.user || !this.settings.display.displayMemberTags) return; const alias = this.getUserAlias(_this.props.user.id); if (!alias) return; ret.props.decorators.props.children.push(this.createAlias(alias, MemberTagClassname)); }); this.patchedModules.push(MemberListItem); MemberListItem.forceUpdateAll(); } async patchPrivateChannel(promiseState) { const PrivateChannel = await ReactComponents.getComponentByName('PrivateChannel', `.${XenoLib.getSingleClass('closeButton channel', true)}`); if (promiseState.cancelled) return; const TypePatch = function(e) { const ret = e.__oldTypeMA(e); const nameAndDecorators = Utilities.getNestedProp(ret, 'props.children.props.children.1.props.children.0.props.children'); if (!nameAndDecorators) return ret; nameAndDecorators.push(this.createAlias(e.__aliasMA, MemberTagClassname)); return ret; }.bind(this); Patcher.after(PrivateChannel.component.prototype, 'render', (_this, _, ret) => { if (!_this.props.user || !this.settings.display.displayDMTags) return; const alias = this.getUserAlias(_this.props.user.id); if (!alias) return; ret.props.__oldTypeMA = ret.type; ret.props.__aliasMA = alias; ret.type = TypePatch; }); this.patchedModules.push(PrivateChannel); PrivateChannel.forceUpdateAll(); } /* friends list */ async patchPeopleListItem(promiseState) { const PeopleListItem = await ReactComponents.getComponentByName('PeopleListItem', `.${XenoLib.getSingleClass('peopleListItem', true)}`); if (promiseState.cancelled) return; const TypePatch3 = function(e) { try { const ret = new e.__oldType3MA(e); ret.props.children.splice(1, 0, this.createAlias(e.__aliasMA, MemberTagClassname)); return ret; } catch (err) { Logger.stacktrace('Error in TypePatch3 for PeopleListItem patch', err); try { return new e.__oldType3MA(e); } catch (err2) { Logger.stacktrace('Error 2 in TypePatch3 for PeopleListItem patch', err2); return null; } } }.bind(this); const TypePatch2 = function(e) { try { const ret = new e.__oldType2MA(e); ret.props.__oldType3MA = ret.type; ret.type = TypePatch3; return ret; } catch (err) { Logger.stacktrace('Error in TypePatch2 for PeopleListItem patch', err); try { return new e.__oldType2MA(e); } catch (err2) { Logger.stacktrace('Error 2 in TypePatch2 for PeopleListItem patch', err2); return null; } } }.bind(this); const TypePatch1 = function(e) { try { const ret = new e.__oldType1MA(e); ret.props.__oldType2MA = ret.type; const DiscordTag = Utilities.getNestedProp(ret, 'props.children.1.props.children.0'); if (DiscordTag) { DiscordTag.props.__aliasMA = e.__aliasMA; DiscordTag.props.__oldType2MA = DiscordTag.type; DiscordTag.type = TypePatch2; } return ret; } catch (err) { Logger.stacktrace('Error in TypePatch1 for PeopleListItem patch', err); try { return new e.__oldType1MA(e); } catch (err2) { Logger.stacktrace('Error 2 in TypePatch1 for PeopleListItem patch', err2); return null; } } }.bind(this); TypePatch2.displayName = 'DiscordTag'; TypePatch3.displayName = 'NameTag'; Patcher.after(PeopleListItem.component.prototype, 'render', (_this, _, ret) => { if (!this.settings.display.displayFriendsListTags) return; const alias = this.getUserAlias(_this.props.user.id); if (!alias) return; const UserInfo = Utilities.getNestedProp(ret, 'props.children.props.children.0'); if (!UserInfo) return; UserInfo.props.__aliasMA = alias; UserInfo.props.__oldType1MA = UserInfo.type; UserInfo.type = TypePatch1; }); this.patchedModules.push(PeopleListItem); PeopleListItem.forceUpdateAll(); } /* mutual friends */ async patchMutualFriends(promiseState) { const MutualFriends = await ReactComponents.getComponentByName('MutualFriends', `.${XenoLib.getSingleClass('scroller themeGhostHairline', true)}`); if (promiseState.cancelled) return; const TypePatch3 = function(e) { try { const ret = new e.__oldType3MA(e); ret.props.children.push(this.createAlias(e.__aliasMA, MemberTagClassname)); return ret; } catch (err) { Logger.stacktrace('Error in TypePatch3 for MutualFriends patch', err); try { return new e.__oldType3MA(e); } catch (err2) { Logger.stacktrace('Error 2 in TypePatch3 for MutualFriends patch', err2); return null; } } }.bind(this); const TypePatch2 = function(e) { try { const ret = new e.__oldType2MA(e); ret.props.__oldType3MA = ret.type; ret.type = TypePatch3; return ret; } catch (err) { Logger.stacktrace('Error in TypePatch2 for MutualFriends patch', err); try { return new e.__oldType2MA(e); } catch (err2) { Logger.stacktrace('Error 2 in TypePatch2 for MutualFriends patch', err2); return null; } } }.bind(this); const TypePatch1 = function(e) { try { const ret = new e.__oldType1MA(e); const DiscordTag = Utilities.getNestedProp(ret, 'props.children.1'); if (!DiscordTag) return ret; DiscordTag.props.__aliasMA = e.__aliasMA; DiscordTag.props.__oldType2MA = DiscordTag.type; DiscordTag.type = TypePatch2; return ret; } catch (err) { Logger.stacktrace('Error in TypePatch1 for MutualFriends patch', err); try { return new e.__oldType1MA(e); } catch (err2) { Logger.stacktrace('Error 2 in TypePatch1 for MutualFriends patch', err2); return null; } } }.bind(this); TypePatch1.displayName = 'FriendRow'; TypePatch2.displayName = 'DiscordTag'; TypePatch3.displayName = 'NameTag'; Patcher.after(MutualFriends.component.prototype, 'render', (_this, _, ret) => { if (!this.settings.display.displayMutualFriendsTags) return; const children = Utilities.getNestedProp(ret, 'props.children'); if (!children || !Array.isArray(children)) return; children.forEach(item => { const alias = this.getUserAlias(item.props.user.id); if (!alias) return; item.props.__aliasMA = alias; item.props.__oldType1MA = item.type; item.type = TypePatch1; }); return; }); MutualFriends.forceUpdateAll(); } /* add mentions popout button */ async patchChannelTextArea(promiseState) { const ChannelTextAreaContainer = WebpackModules.find(m => m.type && m.type.render && m.type.render.displayName === 'ChannelTextAreaContainer'); Patcher.after(ChannelTextAreaContainer.type, 'render', (_this, _, ret) => { if (this.__menuBroken) return; const ChannelEditorContainer = Utilities.getNestedProp(ret, 'props.children.0.props.children.props.children.1'); if (!ChannelEditorContainer || ChannelEditorContainer.props.disabled || !this.settings.display.displayButton) return; const buttons = Utilities.getNestedProp(ret, 'props.children.0.props.children.props.children.2.props.children'); if (!buttons) return; const _editorRef = Utilities.getNestedProp(ChannelEditorContainer, 'ref.current'); buttons.unshift( React.createElement(ChannelTextAreaButton, { iconName: 'Nova_At', label: 'Open Aliases', className: ChannelTextAreaButtonClassname, onClick: e => !this.__menuBroken && this.openAliasesPopout(e, { _editorRef }) }) ); }); } openAliasesPopout({ target }, ref) { PopoutOpener.openPopout( target, { showArrow: true, position: 'top', zIndexBoost: 1, render: _ => { try { return React.createElement( XenoLib.ReactComponents.ErrorBoundary, { label: 'Popout', onError: () => _.onClose() }, React.createElement( 'div', { className: WebpackModules.getByProps('header', 'messagesPopoutWrap').messagesPopoutWrap, style: { maxHeight: Structs.Screen.height - 43 - 25 - 40 } }, WebpackModules.getByProps('Header', 'EmptyStateBottom').Header({ title: 'Defined User Aliases' }), React.createElement( WebpackModules.getByDisplayName('VerticalScroller'), { className: XenoLib.getClass('messagesPopoutWrap messagesPopout') }, React.createElement(AliasesPopout, { getUserAlias: this.getUserAlias, getGroup: id => this.groups.find(m => m.id === id), getMentions: this.queryAliases, getGroupUsers: this.getGroupUsers, setAlias: this.setAlias, setGroup: this.setGroup, channelTextAreaRef: ref }) ), false ) ); } catch (e) { Logger.stacktrace('There has been an issue loading the menu', e); XenoLib.Notifications.error('There has been an issue loading the menu. Open up the console using CTRL + SHIFT + I, click console, and show any errors to Lighty in the support server. Menu button has been disabled.', { timeout: 0 }); this.__menuBroken = true; this.forceUpdateAll(); _.onClose(); return null; } } }, 'MentionAliasesRedux' ); } patchGetNicknames() { Patcher.after(WebpackModules.getByProps('getNicknames'), 'getNicknames', (_this, args, ret) => { if (!this.settings.display.displayAKATags) return; const userId = args[0]; const alias = this.getUserAlias(userId); if (!alias) return; ret.push(alias); }); } patchQueryMentionResults() { Patcher.after(WebpackModules.getByProps('queryMentionResults'), 'queryMentionResults', (_, [query], ret) => { if (!query.length) return; const mentions = this.queryAliases(query.toLowerCase()); const appendUsers = (object, lowpr) => { object.forEach(({ userId, alias }) => { const idx = ret.users.findIndex(m => m.user.id == userId); if (idx !== -1) ret.users.splice(idx, 1); /* it's easier to just move it to the top */ ret.users.unshift({ nick: alias, score: (lowpr && 1) || 10, // I don't think this does anything // months later, I STILL don't know what this does user: UserStore.getUser(userId), status: UserStatusStore.getStatus(userId) }); }); }; appendUsers(mentions.lowPriority.users, true); appendUsers(mentions.users); if (ret.users.length > 10) ret.users.splice(9, ret.users.length - 10); const appendGroups = groups => { groups.forEach(group => { const groupUsers = this.getGroupUsers(group.users); ret.roles.unshift({ name: group.name, note: groupUsers.note, mentioned_users: groupUsers.tags, mentioned_users_ids: groupUsers.tagsIds, MA: true }); }); }; appendGroups(mentions.lowPriority.groups); appendGroups(mentions.groups); }); } patchRoleAutoComplete() { Patcher.instead(WebpackModules.getByDisplayName('Autocomplete').Role.prototype, 'renderContent', (_this, _, orig) => { var role = _this.props.role; if (!role.MA) return orig(); return renderAlias(role.name, role.note, role.colorString); }); } patchSelectAutocompletion() { Patcher.instead(WebpackModules.getByPrototypes('selectAutocompletion').prototype, 'selectAutocompletion', (_this, args, orig) => { const selected = args[0]; const type = _this.state.autocompleteType; const _editorRef = _this.props.editorRef.current; if (type !== 'MENTIONS' || !_editorRef) return orig(...args); const autocompletes = _this.state.autocompletes; const role = autocompletes.roles[selected - autocompletes.users.length - autocompletes.globals.length]; if (!role || !role.MA) return orig(...args); _editorRef.insertAutocomplete(role.mentioned_users, role.mentioned_users_ids); }); } /* PATCHES */ setAlias(userId, alias) { if (!alias || !alias.length) { delete this.aliases[userId]; Toasts.success('Removed!'); } else { this.aliases[userId] = alias; Toasts.success('Saved!'); } this.saveAliases(); this.forceUpdateAll(); } setGroup(id, name, users) { const groupIdx = this.groups.findIndex(m => m.id === id); if (!users || !users.length) { this.groups.splice(groupIdx, 1); Toasts.success('Removed!'); } else { if (groupIdx !== -1) { const group = this.groups[groupIdx]; group.name = name; group.users = users; } else { this.groups.push({ id, name, users }); } Toasts.success('Saved!'); } this.saveGroups(); } forceUpdateAll() { this.patchedModules.forEach(module => module.forceUpdateAll()); Dispatcher.dirtyDispatch({ type: 'MAR_FORCE_UPDATE' }); } getSettingsPanel() { return this.buildSettingsPanel().getElement(); } get [Symbol.toStringTag]() { return 'Plugin'; } get css() { return this._css; } get name() { return config.info.name; } get short() { let string = ''; for (let i = 0, len = config.info.name.length; i < len; i++) { const char = config.info.name[i]; if (char === char.toUpperCase()) string += char; } return string; } get author() { return config.info.authors.map(author => author.name).join(', '); } get version() { return config.info.version; } get description() { return config.info.description; } }; }; /* Finalize */ return !global.ZeresPluginLibrary || !global.XenoLib ? class { getName() { return this.name.replace(/\s+/g, ''); } getAuthor() { return this.author; } getVersion() { return this.version; } getDescription() { return this.description; } stop() {} load() { const XenoLibMissing = !global.XenoLib; const zlibMissing = !global.ZeresPluginLibrary; const bothLibsMissing = XenoLibMissing && zlibMissing; const header = `Missing ${(bothLibsMissing && 'Libraries') || 'Library'}`; const content = `The ${(bothLibsMissing && 'Libraries') || 'Library'} ${(zlibMissing && 'ZeresPluginLibrary') || ''} ${(XenoLibMissing && (zlibMissing ? 'and XenoLib' : 'XenoLib')) || ''} required for ${this.name} ${(bothLibsMissing && 'are') || 'is'} missing.`; const ModalStack = BdApi.findModuleByProps('push', 'update', 'pop', 'popWithKey'); const TextElement = BdApi.findModuleByProps('Sizes', 'Weights'); const ConfirmationModal = BdApi.findModule(m => m.defaultProps && m.key && m.key() === 'confirm-modal'); const onFail = () => BdApi.getCore().alert(header, `${content}
Due to a slight mishap however, you'll have to download the libraries yourself. After opening the links, do CTRL + S to download the library.
${(zlibMissing && '
Click here to download ZeresPluginLibrary') || ''}${(zlibMissing && '
Click here to download XenoLib') || ''}`); if (!ModalStack || !ConfirmationModal || !TextElement) return onFail(); class TempErrorBoundary extends BdApi.React.PureComponent { constructor(props) { super(props); this.state = { hasError: false }; } componentDidCatch(err, inf) { console.error(`Error in ${this.props.label}, screenshot or copy paste the error above to Lighty for help.`); this.setState({ hasError: true }); if (typeof this.props.onError === 'function') this.props.onError(err); } render() { if (this.state.hasError) return null; return this.props.children; } } let modalId; const onHeckWouldYouLookAtThat = (() => { if (!global.pluginModule || !global.BDEvents) return; if (XenoLibMissing) { const listener = () => { BDEvents.off('xenolib-loaded', listener); ModalStack.popWithKey(modalId); /* make it easier on the user */ pluginModule.reloadPlugin(this.name); }; BDEvents.on('xenolib-loaded', listener); return () => BDEvents.off('xenolib-loaded', listener); } else { const onLoaded = e => { if (e !== 'ZeresPluginLibrary') return; BDEvents.off('plugin-loaded', onLoaded); ModalStack.popWithKey(modalId); /* make it easier on the user */ pluginModule.reloadPlugin(this.name); }; BDEvents.on('plugin-loaded', onLoaded); return () => BDEvents.off('plugin-loaded', onLoaded); } })(); modalId = ModalStack.push(props => { return BdApi.React.createElement( TempErrorBoundary, { label: 'missing dependency modal', onError: () => { ModalStack.popWithKey(modalId); /* smh... */ onFail(); } }, BdApi.React.createElement( ConfirmationModal, Object.assign( { header, children: [BdApi.React.createElement(TextElement, { color: TextElement.Colors.PRIMARY, children: [`${content} Please click Download Now to install ${(bothLibsMissing && 'them') || 'it'}.`] })], red: false, confirmText: 'Download Now', cancelText: 'Cancel', onConfirm: () => { onHeckWouldYouLookAtThat(); const request = require('request'); const fs = require('fs'); const path = require('path'); const waitForLibLoad = callback => { if (!global.BDEvents) return callback(); const onLoaded = e => { if (e !== 'ZeresPluginLibrary') return; BDEvents.off('plugin-loaded', onLoaded); callback(); }; BDEvents.on('plugin-loaded', onLoaded); }; const onDone = () => { if (!global.pluginModule || (!global.BDEvents && !global.XenoLib)) return; if (!global.BDEvents || global.XenoLib) pluginModule.reloadPlugin(this.name); else { const listener = () => { BDEvents.off('xenolib-loaded', listener); pluginModule.reloadPlugin(this.name); }; BDEvents.on('xenolib-loaded', listener); } }; const downloadXenoLib = () => { if (global.XenoLib) return onDone(); request('https://raw.githubusercontent.com/1Lighty/BetterDiscordPlugins/master/Plugins/1XenoLib.plugin.js', (error, response, body) => { if (error) return onFail(); onDone(); fs.writeFile(path.join(window.ContentManager.pluginsFolder, '1XenoLib.plugin.js'), body, () => {}); }); }; if (!global.ZeresPluginLibrary) { request('https://rauenzi.github.io/BDPluginLibrary/release/0PluginLibrary.plugin.js', (error, response, body) => { if (error) return onFail(); waitForLibLoad(downloadXenoLib); fs.writeFile(path.join(window.ContentManager.pluginsFolder, '0PluginLibrary.plugin.js'), body, () => {}); }); } else downloadXenoLib(); } }, props ) ) ); }); } start() {} 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@*/