1Lighty-BetterDiscordPlugins/Plugins/BetterTypingUsers/BetterTypingUsers.plugin.js

458 lines
19 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//META{"name":"BetterTypingUsers","source":"https://github.com/1Lighty/BetterDiscordPlugins/blob/master/Plugins/BetterTypingUsers/BetterTypingUsers.plugin.js","website":"https://1lighty.github.io/BetterDiscordStuff/?plugin=BetterTypingUsers","authorId":"239513071272329217","invite":"NYvWdN5","donate":"https://paypal.me/lighty13"}*//
/*@cc_on
@if (@_jscript)
// Offer to self-install for clueless users that try to run this directly.
var shell = WScript.CreateObject('WScript.Shell');
var fs = new ActiveXObject('Scripting.FileSystemObject');
var pathPlugins = shell.ExpandEnvironmentStrings('%APPDATA%\\BetterDiscord\\plugins');
var pathSelf = WScript.ScriptFullName;
// Put the user at ease by addressing them in the first person
shell.Popup('It looks like you\'ve mistakenly tried to run me directly. \n(Don\'t do that!)', 0, 'I\'m a plugin for BetterDiscord', 0x30);
if (fs.GetParentFolderName(pathSelf) === fs.GetAbsolutePathName(pathPlugins)) {
shell.Popup('I\'m in the correct folder already.\nJust reload Discord with Ctrl+R.', 0, 'I\'m already installed', 0x40);
} else if (!fs.FolderExists(pathPlugins)) {
shell.Popup('I can\'t find the BetterDiscord plugins folder.\nAre you sure it\'s even installed?', 0, 'Can\'t install myself', 0x10);
} else if (shell.Popup('Should I copy myself to BetterDiscord\'s plugins folder for you?', 0, 'Do you need some help?', 0x34) === 6) {
fs.CopyFile(pathSelf, fs.BuildPath(pathPlugins, fs.GetFileName(pathSelf)), true);
// Show the user where to put plugins in the future
shell.Exec('explorer ' + pathPlugins);
shell.Popup('I\'m installed!\nJust reload Discord with Ctrl+R.', 0, 'Successfully installed', 0x40);
}
WScript.Quit();
@else@*/
/*
* Copyright © 2019-2020, _Lighty_
* All rights reserved.
* Code may not be redistributed, modified or otherwise taken without explicit permission.
*/
var BetterTypingUsers = (() => {
/* Setup */
const config = {
main: 'index.js',
info: {
name: 'BetterTypingUsers',
authors: [
{
name: 'Lighty',
discord_id: '239513071272329217',
github_username: 'LightyPon',
twitter_username: ''
}
],
version: '1.0.0',
description: 'Replaces "Several people are typing" with who is actually typing, plus "x others" if it can\'t fit. Number of shown people typing can be changed.',
github: 'https://github.com/1Lighty',
github_raw: 'https://raw.githubusercontent.com/1Lighty/BetterDiscordPlugins/master/Plugins/BetterTypingUsers/BetterTypingUsers.plugin.js'
},
changelog: [
{
title: 'Initial release!',
type: 'added',
items: ['Max users typing changed to 5, with showing and "x others" if more people than that are typing.']
},
{
title: 'future plans',
type: 'progress',
items: ['Auto fit max number of people typing, until there is no space left.']
}
],
defaultConfig: [
{
name: 'Max visible typing users',
id: 'maxVisible',
type: 'slider',
value: 5,
min: 3,
max: 20,
markers: Array.from(Array(18), (_, i) => i + 3),
stickToMarkers: true
},
{
name: 'Previews',
type: 'preview'
}
],
strings: {
en: { AND_1_OTHER: ' and 1 other', AND_X_OTHERS: ' and ${count} others', AND: ' and ', ARE_TYPING: ' are typing...' },
de: { AND_1_OTHER: ' und 1 andere', AND_X_OTHERS: ' und ${count} andere', AND: ' und ', ARE_TYPING: ' schreiben...' },
da: { AND_1_OTHER: ' og 1 anden', AND_X_OTHERS: ' og ${count} andre', AND: ' og ', ARE_TYPING: ' skriver...' },
es: { AND_1_OTHER: ' y 1 otro', AND_X_OTHERS: ' y otros ${count}', AND: ' y ', ARE_TYPING: ' están escribiendo...' },
fr: { AND_1_OTHER: ' et 1 autre', AND_X_OTHERS: ' et ${count} autres', AND: ' et ', ARE_TYPING: ' écrivent...' },
hr: { AND_1_OTHER: ' i 1 drugi', AND_X_OTHERS: ' i ${count} drugih', AND: ' i ', ARE_TYPING: ' pišu...' },
it: { AND_1_OTHER: ' e 1 altro', AND_X_OTHERS: ' e altri ${count}', AND: ' e ', ARE_TYPING: ' stanno scrivendo...' },
tr: { AND_1_OTHER: ' ve 1 kişi daha', AND_X_OTHERS: ' ve ${count} kişi daha', AND: ' ve ', ARE_TYPING: ' yazıyor...' }
}
};
/* Build */
const buildPlugin = ([Plugin, Api]) => {
const { ContextMenu, EmulatedTooltip, Toasts, Settings, Popouts, Modals, Utilities, WebpackModules, Filters, DiscordModules, ColorConverter, DOMTools, DiscordClasses, DiscordSelectors, ReactTools, ReactComponents, DiscordAPI, Logger, 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, RelationshipStore, UserSettingsStore } = DiscordModules;
const NameUtils = WebpackModules.getByProps('getName');
const CUser = WebpackModules.getByPrototypes('getAvatarSource', 'isLocalBot');
const CChannel = WebpackModules.getByPrototypes('isGroupDM', 'isMultiUserDM');
const L337 = (() => {
try {
return new CChannel({ id: '1337' });
} catch (e) {
Logger.stacktrace('Failed to create 1337 channel', e);
}
})();
let CTypingUsers = (() => {
try {
const WrappedTypingUsers = WebpackModules.find(m => m.displayName && m.displayName.indexOf('TypingUsers') !== -1);
return new WrappedTypingUsers({ channel: L337 }).render().type;
} catch (e) {
Logger.stacktrace('Failed to get TypingUsers!', e);
return null;
}
})();
const ComponentDispatch = (() => {
try {
return WebpackModules.getByProps('ComponentDispatch').ComponentDispatch;
} catch (e) {
Logger.stacktrace('Failed to get ComponentDispatch', e);
}
})();
class CTypingUsersPreview extends React.PureComponent {
constructor(props) {
super(props);
this.forceUpdate = this.forceUpdate.bind(this);
const iUsers = UserStore.getUsers();
for (let i = 0; i < 20; i++) iUsers[(1337 + i).toString()] = new CUser({ username: `User ${i + 1}`, id: (1337 + i).toString(), discriminator: '9999' });
}
componentDidMount() {
ComponentDispatch.subscribe('BTU_SETTINGS_UPDATED', this.forceUpdate);
}
componentWillUnmount() {
ComponentDispatch.unsubscribe('BTU_SETTINGS_UPDATED', this.forceUpdate);
const iUsers = UserStore.getUsers();
for (let i = 0; i < 20; i++) delete iUsers[(1337 + i).toString()];
}
renderTyping(num) {
const typingUsers = {};
for (let i = 0; i < num; i++) typingUsers[(1337 + i).toString()] = 1;
return React.createElement(CTypingUsers, {
channel: L337,
guildId: '',
isFocused: true,
slowmodeCooldownGuess: 0,
theme: UserSettingsStore.theme,
typingUsers
});
}
render() {
return React.createElement(
'div',
{
className: 'BTU-preview'
},
this.renderTyping(4),
this.renderTyping(6),
this.renderTyping(20)
);
}
}
class TypingUsersPreview extends Settings.SettingField {
constructor(name, note) {
super(name, note, null, CTypingUsersPreview);
}
}
/* since XenoLib is absent from this plugin (since it serves no real purpose),
we can only hope the user doesn't rename the plugin..
*/
return class BetterTypingUsers extends Plugin {
constructor() {
super();
try {
ModalStack.popWithKey(`${this.name}_DEP_MODAL`);
} catch (e) {}
}
onStart() {
this.promises = { state: { cancelled: false } };
this.patchAll();
PluginUtilities.addStyle(
this.short + '-CSS',
`
.BTU-preview > .${WebpackModules.getByProps('slowModeIcon', 'typing').typing.split(' ')[0]} {
position: unset !important;
}
`
);
DiscordConstants.MAX_TYPING_USERS = 99;
/* theoretical max is 5 users typing at once.. welp */
}
onStop() {
this.promises.state.cancelled = true;
Patcher.unpatchAll();
PluginUtilities.removeStyle(this.short + '-CSS');
}
/* zlib uses reference to defaultSettings instead of a cloned object, which sets settings as default settings, messing everything up */
loadSettings(defaultSettings) {
return PluginUtilities.loadSettings(this.name, Utilities.deepclone(this.defaultSettings ? this.defaultSettings : defaultSettings));
}
buildSetting(data) {
if (data.type === 'preview') return new TypingUsersPreview(data.name, data.note);
return super.buildSetting(data);
}
saveSettings(_, setting, value) {
super.saveSettings(_, setting, value);
ComponentDispatch.safeDispatch('BTU_SETTINGS_UPDATED');
}
filterTypingUsers(typingUsers) {
return Object.keys(typingUsers)
.filter(e => e != DiscordAPI.currentUser.id)
.filter(e => !RelationshipStore.isBlocked(e))
.map(e => UserStore.getUser(e))
.filter(e => e != null);
}
/* PATCHES */
patchAll() {
Utilities.suppressErrors(this.patchBetterRoleColors.bind(this), 'BetterRoleColors patch')();
Utilities.suppressErrors(this.patchTypingUsers.bind(this), 'TypingUsers patch')(this.promises.state);
}
patchBetterRoleColors() {
const BetterRoleColors = BdApi.getPlugin('BetterRoleColors');
if (!BetterRoleColors) return;
/* stop errors */
/* modify BRCs behavior so it won't unexpectedly try to modify an entry that does not exist
by simply limiting it to the max number of usernames visible in total
*/
Patcher.after(BetterRoleColors, 'filterTypingUsers', (_this, __, ret) => ret.slice(0, this.settings.maxVisible));
}
async patchTypingUsers(promiseState) {
const TypingUsers = await ReactComponents.getComponentByName('TypingUsers', DiscordSelectors.Typing.typing);
if (!TypingUsers.selector) TypingUsers.selector = DiscordSelectors.Typing.typing;
const TypingTextClassname = WebpackModules.getByProps('typing', 'text').text.split(' ')[0];
if (promiseState.cancelled) return;
if (!CTypingUsers) CTypingUsers = typingUsers.component; /* failsafe */
/* use `instead` so that we modify the return before BetterRoleColors */
/* Patcher.after(TypingUsers.component.prototype, 'componentDidUpdate', (_this, [props, state], ret) => {
const filtered1 = this.filterTypingUsers(_this.props.typingUsers);
const filtered2 = this.filterTypingUsers(props.typingUsers);
if (filtered1.length !== filtered2.length || _this.state.numLess === state.numLess) {
_this.state.numLess = 0;
_this.triedLess = false;
_this.triedMore = false;
}
}); */
Patcher.instead(TypingUsers.component.prototype, 'render', (_this, _, orig) => {
/* if (!_this.state) _this.state = { numLess: 0 }; */
const ret = orig();
if (!ret) {
/* _this.state.numLess = 0; */
return ret;
}
const filtered = this.filterTypingUsers(_this.props.typingUsers);
if (filtered.length <= 3) return ret;
/* ret.ref = e => {
_this.__baseRef = e;
if (!e) return;
if (!_this.__textRef) return;
_this.maxWidth = parseInt(getComputedStyle(_this.__baseRef.parentElement).width) - (_this.__textRef.offsetLeft + parseInt(getComputedStyle(_this.__textRef)['margin-left']) - _this.__baseRef.offsetLeft);
if (_this.__textRef.scrollWidth > _this.maxWidth) {
if (_this.triedMore) return;
if (filtered.length - _this.state.numLess <= 3) return;
_this.setState({ numLess: _this.state.numLess + 1 });
}
}; */
const typingUsers = Utilities.findInReactTree(ret, e => e && e.props && typeof e.props.className === 'string' && e.props.className.indexOf(TypingTextClassname) !== -1);
if (!typingUsers) return ret;
/* if (typeof _this.state.numLess !== 'number') _this.state.numLess = 0;
typingUsers.ref = e => {
_this.__textRef = e;
}; */
typingUsers.props.children = [];
/* I don't think this method works for every language..? */
for (let i = 0; i < filtered.length; i++) {
if (this.settings.maxVisible /* filtered.length - _this.state.numLess */ === i) {
const others = filtered.length - i;
if (others === 1) typingUsers.props.children.push(this.strings.AND_1_OTHER);
else typingUsers.props.children.push(Utilities.formatTString(this.strings.AND_X_OTHERS, { count: others }));
break;
} else if (i === filtered.length - 1) typingUsers.props.children.push(this.strings.AND);
else if (i !== 0) typingUsers.props.children.push(', ');
const name = NameUtils.getName(_this.props.guildId, _this.props.channel.id, filtered[i]);
typingUsers.props.children.push(React.createElement('strong', {}, name));
}
typingUsers.props.children.push(this.strings.ARE_TYPING);
return ret;
});
TypingUsers.forceUpdateAll();
}
/* PATCHES */
getSettingsPanel() {
return this.buildSettingsPanel().getElement();
}
get [Symbol.toStringTag]() {
return 'Plugin';
}
get name() {
return config.info.name;
}
get short() {
let string = '';
for (let i = 0, len = config.info.name.length; i < len; i++) {
const char = config.info.name[i];
if (char === char.toUpperCase()) string += char;
}
return string;
}
get author() {
return config.info.authors.map(author => author.name).join(', ');
}
get version() {
return config.info.version;
}
get description() {
return config.info.description;
}
};
};
/* Finalize */
let ZeresPluginLibraryOutdated = false;
try {
if (global.BdApi && 'function' == typeof BdApi.getPlugin) {
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.getPlugin('ZeresPluginLibrary');
((b, c) => b && b._config && b._config.info && b._config.info.version && a(b._config.info.version, c))(b, '1.2.14') && (ZeresPluginLibraryOutdated = !0);
}
} catch (e) {
console.error('Error checking if ZeresPluginLibrary is out of date', e);
}
return !global.ZeresPluginLibrary || ZeresPluginLibraryOutdated
? class {
constructor() {
this._config = config;
this.start = this.load = this.handleMissingLib;
}
getName() {
return this.name.replace(/\s+/g, '');
}
getAuthor() {
return this.author;
}
getVersion() {
return this.version;
}
getDescription() {
return this.description + ' You are missing ZeresPluginLibrary for this plugin, please enable the plugin and click Download Now.';
}
stop() {}
handleMissingLib() {
const a = BdApi.findModuleByProps('isModalOpenWithKey');
if (a && a.isModalOpenWithKey(`${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.findModuleByProps('push', 'update', 'pop', 'popWithKey'),
f = BdApi.findModuleByDisplayName('Text'),
g = BdApi.findModule(a => a.defaultProps && a.key && 'confirm-modal' === a.key()),
h = () => 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.`, 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 (!e || !g || !f) return h();
class i 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;
}
}
class j extends g {
submitModal() {
this.props.onConfirm();
}
}
let k = !1;
const l = e.push(
a =>
BdApi.React.createElement(
i,
{
label: 'missing dependency modal',
onError: () => {
e.popWithKey(l), h();
}
},
BdApi.React.createElement(
j,
Object.assign(
{
header: c,
children: [BdApi.React.createElement(f, { size: f.Sizes.SIZE_16, children: [`${d} Please click Download Now to download it.`] })],
red: !1,
confirmText: 'Download Now',
cancelText: 'Cancel',
onConfirm: () => {
if (k) return;
k = !0;
const a = require('request'),
b = require('fs'),
c = require('path');
a('https://raw.githubusercontent.com/rauenzi/BDPluginLibrary/master/release/0PluginLibrary.plugin.js', (a, d, f) => (a || 200 !== d.statusCode ? (e.popWithKey(l), h()) : void b.writeFile(c.join(BdApi.Plugins.folder, '0PluginLibrary.plugin.js'), f, () => {})));
}
},
a
)
)
),
void 0,
`${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@*/