diff --git a/discord/README.md b/discord/README.md deleted file mode 100644 index becd074..0000000 --- a/discord/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# [Plugins BetterDiscord](https://betterdiscord.app/plugins) - -- [BDFDB](https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/master/Library/0BDFDB.plugin.js) -- [DoNotTrack](https://raw.githubusercontent.com/rauenzi/BetterDiscordAddons/master/Plugins/DoNotTrack/DoNotTrack.plugin.js) -- [GameActivityToggle](https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/master/Plugins/GameActivityToggle/GameActivityToggle.plugin.js) -- [MessageLoggerV2](https://git.mylloon.fr/Anri/confOS/raw/branch/main/discord/plugins/MessageLoggerV2.plugin.js) -- [NoSpotifyPause](https://raw.githubusercontent.com/bepvte/bd-addons/main/plugins/NoSpotifyPause.plugin.js) -- [SpotifyListenAlong](https://raw.githubusercontent.com/ordinall/BetterDiscord-Stuff/master/Plugins/SpotifyListenAlong/SpotifyListenAlong.plugin.js) -- [XenoLib](https://git.mylloon.fr/Anri/confOS/raw/branch/main/discord/plugins/XenoLib.plugin.js) -- [ZeresPluginLibrary](https://betterdiscord.app/Download?id=9) diff --git a/discord/custom.css b/discord/custom.css deleted file mode 100644 index 52c5975..0000000 --- a/discord/custom.css +++ /dev/null @@ -1,13 +0,0 @@ -@import url("https://api.fonts.coollabs.io/css2?family=Fira+Sans&display=swap"); - -body, -input, -textarea { - font-family: "Fira Sans" !important; - --font-display: "Fira Sans"; - --font-primary: "Fira Sans"; -} - -pre { - font-family: "Fira Code" !important; -} diff --git a/discord/plugins/MessageLoggerV2.plugin.js b/discord/plugins/MessageLoggerV2.plugin.js deleted file mode 100644 index 5e01f43..0000000 --- a/discord/plugins/MessageLoggerV2.plugin.js +++ /dev/null @@ -1,5474 +0,0 @@ -/** - * @name MessageLoggerV2 - * @version 1.8.21 - * @invite NYvWdN5 - * @donate https://paypal.me/lighty13 - * @website https://1lighty.github.io/BetterDiscordStuff/?plugin=MessageLoggerV2 - * @source https://github.com/1Lighty/BetterDiscordPlugins/blob/master/Plugins/MessageLoggerV2/MessageLoggerV2.plugin.js - * @updateUrl https://raw.githubusercontent.com/1Lighty/BetterDiscordPlugins/master/Plugins/MessageLoggerV2/MessageLoggerV2.plugin.js - */ -/*@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 @*/ -// extra TODOs: -// special edited message https://i.clouds.tf/guli/mric.png -// modal for checking which servers/channels/users are blacklisted/whitelisted -// option to show all hidden - -const MLV2_TYPE_L1 = Symbol('MLV2_TYPE_L1'); -const MLV2_TYPE_L2 = Symbol('MLV2_TYPE_L2'); -const MLV2_TYPE_L3 = Symbol('MLV2_TYPE_L3'); - -module.exports = class MessageLoggerV2 { - getName() { - return 'MessageLoggerV2'; - } - getVersion() { - return '1.8.21'; - } - getAuthor() { - return 'Lighty'; - } - getDescription() { - return 'Saves all deleted and purged messages, as well as all edit history and ghost pings. With highly configurable ignore options, and even restoring deleted messages after restarting Discord.'; - } - load() { } - start() { - let onLoaded = () => { - try { - if (global.ZeresPluginLibrary && !this.UserStore) this.UserStore = ZeresPluginLibrary.WebpackModules.getByProps('getCurrentUser', 'getUser'); - if (!global.ZeresPluginLibrary || !this.UserStore || !(this.localUser = this.UserStore.getCurrentUser())) setTimeout(onLoaded, 1000); - else this.initialize(); - } catch (err) { - ZeresPluginLibrary.Logger.stacktrace(this.getName(), 'Failed to start!', err); - ZeresPluginLibrary.Logger.err(this.getName(), `If you cannot solve this yourself, contact ${this.getAuthor()} and provide the errors shown here.`); - this.stop(); - XenoLib.Notifications.error(`[**${this.getName()}**] Failed to start! Try to CTRL + R, or update the plugin, like so\n![image](https://i.imgur.com/tsv6aW8.png)`, { timeout: 0 }); - } - }; - this.pluginDir = (BdApi.Plugins && BdApi.Plugins.folder) || window.ContentManager.pluginsFolder; - this.__isPowerCord = !!window.powercord && typeof BdApi.__getPluginConfigPath === 'function' || typeof global.isTab !== 'undefined'; - let XenoLibOutdated = false; - let ZeresPluginLibraryOutdated = false; - if (global.BdApi && BdApi.Plugins && typeof BdApi.Plugins.get === 'function' /* you never know with those retarded client mods */) { - const versionChecker = (a, b) => ((a = a.split('.').map(a => parseInt(a))), (b = b.split('.').map(a => parseInt(a))), !!(b[0] > a[0])) || !!(b[0] == a[0] && b[1] > a[1]) || !!(b[0] == a[0] && b[1] == a[1] && b[2] > a[2]); - const isOutOfDate = (lib, minVersion) => lib && lib._config && lib._config.info && lib._config.info.version && versionChecker(lib._config.info.version, minVersion) || typeof global.isTab !== 'undefined'; - let iXenoLib = BdApi.Plugins.get('XenoLib'); - let iZeresPluginLibrary = BdApi.Plugins.get('ZeresPluginLibrary'); - if (iXenoLib && iXenoLib.instance) iXenoLib = iXenoLib.instance; - if (iZeresPluginLibrary && iZeresPluginLibrary.instance) iZeresPluginLibrary = iZeresPluginLibrary.instance; - if (isOutOfDate(iXenoLib, '1.4.11')) XenoLibOutdated = true; - if (isOutOfDate(iZeresPluginLibrary, '2.0.3')) ZeresPluginLibraryOutdated = true; - } - if (!global.XenoLib || !global.ZeresPluginLibrary || XenoLibOutdated || ZeresPluginLibraryOutdated) { - this._XL_PLUGIN = true; - const a = !!window.powercord && "function" == typeof BdApi.__getPluginConfigPath, - b = BdApi.findModuleByProps("openModal", "hasModalOpen"); - if (b && b.hasModalOpen(`${this.getName()}_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.getName()} ${e ? "are" : "is"} ${c || d ? "missing" : ""}${XenoLibOutdated || ZeresPluginLibraryOutdated ? c || d ? " and/or outdated" : "outdated" : ""}.`, a - })(), - h = BdApi.findModuleByDisplayName("Text") || BdApi.findModule(e => e.Text?.displayName === 'Text')?.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.app/Download?id=9", - target: "_blank" - }, "Click here to download ZeresPluginLibrary")) : null, c || XenoLibOutdated ? BdApi.React.createElement("div", {}, BdApi.React.createElement("a", { - href: "https://astranika.com/bd/xenolib", - target: "_blank" - }, "Click here to download XenoLib")) : null)); - 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 l = false, - 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, - variant: 'text-md/normal', - children: [`${g} Please click Download Now to download ${e ? "them" : "it"}.`] - }), - red: !1, - confirmText: "Download Now", - cancelText: "Cancel", - onCancel: c.onClose, - onConfirm: () => { - if (l) return; - l = !0; - 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 ? (BdApi.isSettingEnabled("fork-ps-5") || BdApi.isSettingEnabled("autoReload")) && !a ? void 0 : void BdApi.showToast("Reload to load the libraries and plugin!") : void c("https://raw.githubusercontent.com/1Lighty/BetterDiscordPlugins/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") || BdApi.isSettingEnabled("autoReload")) && !a || BdApi.showToast("Reload to load the libraries and plugin!") - }) - } 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.getName()}_DEP_MODAL` - }); - } else onLoaded(); - } - stop() { - try { - this.shutdown(); - } catch (err) { - // ZeresPluginLibrary.Logger.stacktrace(this.getName(), 'Failed to stop!', err); - } - } - getChanges() { - return [ - { - title: 'Fixed', - type: 'fixed', - items: ['Fixed incompatibility with another plugin by Tharki.'] - } - ]; - } - initialize() { - if (this.__started) return XenoLib.Notifications.warning(`[**${this.getName()}**] Tried to start twice..`, { timeout: 0 }); - this.__started = true; - /* - * why are we letting Zere, the braindead American let control BD when he can't even - * fucking read clearly documented and well known standards, such as __filename being - * the files full fucking path and not just the filename itself, IS IT REALLY SO HARD - * TO FUCKING READ?! https://nodejs.org/api/modules.html#modules_filename - */ - const _zerecantcode_path = require('path'); - const theActualFileNameZere = _zerecantcode_path.join(__dirname, _zerecantcode_path.basename(__filename)); - XenoLib.changeName(theActualFileNameZere, 'MessageLoggerV2'); /* To everyone who renames plugins: FUCK YOU! */ - try { - ZeresPluginLibrary.WebpackModules.getByProps('openModal', 'hasModalOpen').closeModal(`${this.getName()}_DEP_MODAL`); - } catch (e) { } - // force update - try { - ZeresPluginLibrary.PluginUpdater.checkForUpdate(this.getName(), this.getVersion(), 'https://raw.githubusercontent.com/1Lighty/BetterDiscordPlugins/master/Plugins/MessageLoggerV2/MessageLoggerV2.plugin.js'); - } catch (err) {} - if (window.PluginUpdates && window.PluginUpdates.plugins) delete PluginUpdates.plugins['https://gitlab.com/_Lighty_/bdstuff/raw/master/public/plugins/MessageLoggerV2.plugin.js']; - if (BdApi.Plugins && BdApi.Plugins.get('NoDeleteMessages') && BdApi.Plugins.isEnabled('NoDeleteMessages')) XenoLib.Notifications.warning(`[**${this.getName()}**] Using **NoDeleteMessages** with **${this.getName()}** is completely unsupported and will cause issues. Please either disable **NoDeleteMessages** or delete it to avoid issues.`, { timeout: 0 }); - if (BdApi.Plugins && BdApi.Plugins.get('SuppressUserMentions') && BdApi.Plugins.isEnabled('SuppressUserMentions')) XenoLib.Notifications.warning(`[**${this.getName()}**] Using **SuppressUserMentions** with **${this.getName()}** is completely unsupported and will cause issues. Please either disable **SuppressUserMentions** or delete it to avoid issues.`, { timeout: 0 }); - const shouldPass = e => e && e.constructor && typeof e.constructor.name === 'string' && e.constructor.name.indexOf('HTML'); - let defaultSettings = { - obfuscateCSSClasses: true, - autoBackup: false, - dontSaveData: false, - displayUpdateNotes: true, - ignoreMutedGuilds: true, - ignoreMutedChannels: true, - ignoreBots: true, - ignoreSelf: false, - ignoreBlockedUsers: true, - ignoreNSFW: false, - ignoreLocalEdits: false, - ignoreLocalDeletes: false, - alwaysLogGhostPings: false, - showOpenLogsButton: true, - messageCacheCap: 1000, - savedMessagesCap: 1000, - reverseOrder: true, - onlyLogWhitelist: true, - whitelist: [], - blacklist: [], - notificationBlacklist: [], - toastToggles: { - sent: false, - edited: true, - deleted: true, - ghostPings: true - }, - toastTogglesDMs: { - sent: false, - edited: true, - deleted: true, - ghostPings: true, - disableToastsForLocal: false - }, - useNotificationsInstead: true, - blockSpamEdit: false, - disableKeybind: false, - cacheAllImages: true, - dontDeleteCachedImages: false, - aggresiveMessageCaching: true, - // openLogKeybind: [ - // /* 162, 77 */ - // ], // ctrl + m on windows - // openLogFilteredKeybind: [ - // /* 162, 78 */ - // ], // ctrl + n on windows - renderCap: 50, - maxShownEdits: 0, - hideNewerEditsFirst: true, - displayDates: true, - deletedMessageColor: '', - editedMessageColor: '', - useAlternativeDeletedStyle: false, - showEditedMessages: true, - showDeletedMessages: true, - showPurgedMessages: true, - showDeletedCount: true, - showEditedCount: true, - alwaysLogSelected: true, - alwaysLogDM: true, - restoreDeletedMessages: true, - contextmenuSubmenuName: 'Message Logger', - streamSafety: { - showEdits: false, - showDeletes: false, - showButton: false, - showNotifications: false, - showContextMenu: false - }, - imageCacheDir: this.pluginDir + '/MLV2_IMAGE_CACHE', - flags: 0, - autoUpdate: true, - versionInfo: '' - }; - const Flags = { - STOLEN: 1 << 0, - STARTUP_HELP: 1 << 1 - }; - - this.settings = ZeresPluginLibrary.PluginUtilities.loadSettings(this.getName(), defaultSettings); - let settingsChanged = false; - - if (!this.settings || !Object.keys(this.settings).length) { - XenoLib.Notifications.error(`[${this.getName()}] Settings file corrupted! All settings restored to default.`, { timeout: 0 }); - this.settings = defaultSettings; // todo: does defaultSettings get changed? - settingsChanged = true; - } - if (this.settings.versionInfo === '1.7.55') { - this.settings = defaultSettings; // bad default settings - settingsChanged = true; - } - // if (!this.settings.openLogKeybind.length) { - // this.settings.openLogKeybind = [162, 77]; - // settingsChanged = true; - // } - // if (!this.settings.openLogFilteredKeybind.length) { - // this.settings.openLogFilteredKeybind = [162, 78]; - // settingsChanged = true; - // } - - if (this.settings.autoUpdate) { - if (this._autoUpdateInterval) clearInterval(this._autoUpdateInterval); - this._autoUpdateInterval = setInterval(_ => this.automaticallyUpdate(), 1000 * 60 * 60); // 1 hour - this.automaticallyUpdate(); - } - if (this.settings.versionInfo !== this.getVersion() && this.settings.displayUpdateNotes) { - XenoLib.showChangelog(`${this.getName()} has been updated!`, this.getVersion(), this.getChanges()); - this.settings.versionInfo = this.getVersion(); - this.saveSettings(); - settingsChanged = false; - } - - if (settingsChanged) this.saveSettings(); - - this.nodeModules = { - electron: require('electron'), - request: require('request'), - fs: require('fs'), - path: require('path') - }; - - let defaultConstruct = () => { - return Object.assign( - {}, - { - messageRecord: {}, - deletedMessageRecord: {}, - editedMessageRecord: {}, - purgedMessageRecord: {} - } - ); - }; - let data; - if (this.settings.dontSaveData) { - data = defaultConstruct(); - } else { - data = XenoLib.loadData(this.getName() + 'Data', 'data', defaultConstruct(), true); - const isBad = map => !(map && map.messageRecord && map.editedMessageRecord && map.deletedMessageRecord && map.purgedMessageRecord && typeof map.messageRecord == 'object' && typeof map.editedMessageRecord == 'object' && typeof map.deletedMessageRecord == 'object' && typeof map.purgedMessageRecord == 'object'); - if (isBad(data)) { - if (this.settings.autoBackup) { - data = XenoLib.loadData(this.getName() + 'DataBackup', 'data', defaultConstruct(), true); - if (isBad(data)) { - XenoLib.Notifications.error(`[${this.getName()}] Data and backup files were corrupted. All deleted/edited/purged messages have been erased.`, { timeout: 0 }); - data = defaultConstruct(); - } else { - XenoLib.Notifications.warning(`[${this.getName()}] Data was corrupted, loaded backup!`, { timeout: 5000 }); - } - } else { - XenoLib.Notifications.error(`[${this.getName()}] Data was corrupted! Recommended to turn on auto backup in settings! All deleted/edited/purged messages have been erased.`, { timeout: 0 }); - data = defaultConstruct(); - } - } - } - /* - const dataFileSize = this.nodeModules.fs.statSync(this.pluginDir + '/MessageLoggerV2Data.config.json').size / 1024 / 1024; - // SEVERITY - // 0 OK < 5MiB - // 1 MILD < 10MiB - // 2 DANGER < 20MiB - // 3 EXTREME > 20MiB - this.slowSaveModeStep = dataFileSize > 20 ? 3 : dataFileSize > 10 ? 2 : dataFileSize > 5 ? 1 : 0; - ZeresPluginLibrary.Logger.info(this.getName(), `Data file size is ${dataFileSize.toFixed(2)}MB`); - if (this.slowSaveModeStep) ZeresPluginLibrary.Logger.warn(this.getName(), 'Data file is too large, severity level', this.slowSaveModeStep); -*/ - - this.messageStore = ZeresPluginLibrary.WebpackModules.getByProps('getMessages', 'getMessage'); - - this.ChannelStore = ZeresPluginLibrary.WebpackModules.getByProps('getChannel', 'getDMFromUserId'); - if (!this.settings.dontSaveData) { - const records = data.messageRecord; - // data structure changed a wee bit, compensate instead of deleting user data or worse, erroring out - for (let a in records) { - const record = records[a]; - if (record.deletedata) { - if (record.deletedata.deletetime) { - record.delete_data = {}; - record.delete_data.time = record.deletedata.deletetime; - } - delete record.deletedata; - } else if (record.delete_data && typeof record.delete_data.rel_ids !== 'undefined') delete record.delete_data.rel_ids; - if (record.editHistory) { - record.edit_history = []; - for (let b in record.editHistory) { - record.edit_history.push({ content: record.editHistory[b].content, time: record.editHistory[b].editedAt }); - } - delete record.editHistory; - } - record.message = this.cleanupMessageObject(record.message); // fix up our past mistakes by sweeping it under the rug! - } - } - - this.cachedMessageRecord = []; - this.messageRecord = data.messageRecord; - this.deletedMessageRecord = data.deletedMessageRecord; - this.editedMessageRecord = data.editedMessageRecord; - this.purgedMessageRecord = data.purgedMessageRecord; - this.tempEditedMessageRecord = {}; - this.editHistoryAntiSpam = {}; - this.localDeletes = []; - - this.settings.imageCacheDir = this.pluginDir + '/MLV2_IMAGE_CACHE'; - - const imageCacheDirFailure = () => { - this.settings.imageCacheDir = this.pluginDir + '/MLV2_IMAGE_CACHE'; - XenoLib.Notifications.error(`[**${this.getName()}**] Failed to access custom image cache dir. It has been reset to plugins folder!`); - }; - - if (this.settings.cacheAllImages && !this.nodeModules.fs.existsSync(this.settings.imageCacheDir)) { - try { - this.nodeModules.fs.mkdirSync(this.settings.imageCacheDir); - } catch (e) { - imageCacheDirFailure(); - } - } - - if (!this._imageCacheServer) { - class ImageCacheServer { - constructor(imagePath, name) { - try { - ZeresPluginLibrary.WebpackModules.getByProps('bindAll', 'debounce').bindAll(this, ['_requestHandler', '_errorHandler']); - this._server = require('http').createServer(this._requestHandler); - this._getMimetype = require('mime-types').lookup; - this._parseURL = require('url').parse; - this._fs = require('fs'); - this._path = require('path'); - this._imagePath = imagePath; - this._name = name; - } catch (err) {} - } - start() { - try { - this._server.listen(7474, 'localhost', this._errorHandler); - } catch (err) {} - } - stop() { - try { - this._server.close(); - } catch (err) {} - } - _errorHandler(err) { - if (err) return ZeresPluginLibrary.Logger.err(this._name, 'Error in ImageCacheServer', err); - ZeresPluginLibrary.Logger.info(this._name, 'ImageCacheServer: OK'); - } - _requestHandler(req, res) { - // parse URL - const parsedUrl = this._parseURL(req.url); - const parsedFile = this._path.parse(parsedUrl.pathname); - // extract URL path - let pathname = this._path.join(this._imagePath, parsedFile.base); - this._fs.readFile(pathname, (err, data) => { - if (err) { - res.statusCode = 404; - res.end(`No such file file: ${err}.`); - } else { - // if the file is found, set Content-type and send data - res.setHeader('Content-type', this._getMimetype(parsedFile.ext)); - res.end(data); - } - }); - } - } - this._imageCacheServer = new ImageCacheServer(this.settings.imageCacheDir, this.getName()); - } - this._imageCacheServer.start(); - - defaultConstruct = undefined; - - /* backport from MLV3/rewrite */ - const CUser = ZeresPluginLibrary.WebpackModules.getByPrototypes('getAvatarSource', 'isLocalBot'); - const userRecord = {}; - const lastSeenUser = {}; - for (const messageId in this.messageRecord) { - const record = this.messageRecord[messageId]; - const userObj = record.message.author; - if (!userObj || typeof userObj === 'string') continue; - const date = new Date(record.message.timestamp); - if (!(userRecord[userObj.id] && lastSeenUser[userObj.id] && lastSeenUser[userObj.id] > date)) { - userRecord[userObj.id] = userObj; - lastSeenUser[userObj.id] = date; - } - } - - this.Patcher = XenoLib.createSmartPatcher({ before: (moduleToPatch, functionName, callback, options = {}) => ZeresPluginLibrary.Patcher.before(this.getName(), moduleToPatch, functionName, callback, options), instead: (moduleToPatch, functionName, callback, options = {}) => ZeresPluginLibrary.Patcher.instead(this.getName(), moduleToPatch, functionName, callback, options), after: (moduleToPatch, functionName, callback, options = {}) => ZeresPluginLibrary.Patcher.after(this.getName(), moduleToPatch, functionName, callback, options) }); - - this.unpatches = []; - - this.unpatches.push( - this.Patcher.after(this.UserStore, 'getUser', (_this, args, ret) => { - if (!ret && !args[1]) { - const userId = args[0]; - const users = this.UserStore.getUsers(); - if (userRecord[userId]) return (users[userId] = new CUser(userRecord[userId])); - } - }) - ); - - const isMentioned = ZeresPluginLibrary.WebpackModules.getModule(e => typeof e === 'function' && e?.toString()?.includes('mentionEveryone') && e?.toString()?.includes('roles.includes'), { searchExports: true }); - - this.tools = { - openUserContextMenu: null /* NeatoLib.Modules.get('openUserContextMenu').openUserContextMenu */, // TODO: move here - getMessage: this.messageStore.getMessage, - fetchMessages: ZeresPluginLibrary.DiscordModules.MessageActions.fetchMessages, - transitionTo: null /* NeatoLib.Modules.get('transitionTo').transitionTo */, - getChannel: this.ChannelStore.getChannel, - copyToClipboard: global.copy, - getServer: ZeresPluginLibrary.WebpackModules.getByProps('getGuild', 'getGuildCount').getGuild, - getUser: this.UserStore.getUser, - parse: ZeresPluginLibrary.WebpackModules.getByProps('parse', 'astParserFor').parse, - getUserAsync: /* ZeresPluginLibrary.WebpackModules.getByProps('getUser', 'acceptAgreements').getUser */ () => Promise.resolve(), - isBlocked: ZeresPluginLibrary.WebpackModules.getByProps('isBlocked').isBlocked, - createMomentObject: ZeresPluginLibrary.WebpackModules.getByProps('createFromInputFallback'), - isMentioned: (e, id) => isMentioned({ userId: id, channelId: e.channel_id, mentionEveryone: e.mentionEveryone || e.mention_everyone, mentionUsers: e.mentions.map(e => e.id || e), mentionRoles: e.mentionRoles || e.mention_roles }), - DiscordUtils: ZeresPluginLibrary.WebpackModules.getByProps('bindAll', 'debounce') - }; - - this.createButton.classes = { - button: (function () { - let buttonData = ZeresPluginLibrary.WebpackModules.getByProps('button', 'colorBrand'); - return `${buttonData.button} ${buttonData.lookFilled} ${buttonData.colorBrand} ${buttonData.sizeSmall} ${buttonData.grow}`; - })(), - buttonContents: ZeresPluginLibrary.WebpackModules.getByProps('button', 'colorBrand').contents - }; - - this.safeGetClass = (func, fail, heckoff) => { - try { - return func(); - } catch (e) { - if (heckoff) return fail; - return fail + '-MLV2'; - } - }; - - this.createMessageGroup.classes = { - containerBounded: this.safeGetClass(() => ZeresPluginLibrary.WebpackModules.getByProps('containerCozyBounded').containerCozyBounded, 'containerCozyBounded'), - message: this.safeGetClass(() => `.${ZeresPluginLibrary.WebpackModules.getByProps('containerCozyBounded').containerCozyBounded.split(/ /g)[0]} > div`, '.containerCozyBounded-MLV2 > div', true), - header: this.safeGetClass(() => ZeresPluginLibrary.WebpackModules.getByProps('containerCozyBounded').headerCozy, 'headerCozy'), - avatar: this.safeGetClass(() => XenoLib.getClass('header avatar', true), 'avatar'), - headerMeta: this.safeGetClass(() => ZeresPluginLibrary.WebpackModules.getByProps('containerCozyBounded').headerCozyMeta, 'headerCozyMeta'), - username: this.safeGetClass(() => ZeresPluginLibrary.WebpackModules.getByProps('containerCozyBounded').username, 'username'), - timestamp: this.safeGetClass(() => ZeresPluginLibrary.WebpackModules.getByProps('containerCozyBounded').timestampCozy, 'timestampCozy'), - timestampSingle: this.safeGetClass(() => ZeresPluginLibrary.WebpackModules.getByProps('containerCozyBounded').timestampCozy.split(/ /g)[0], 'timestampCozy'), - content: this.safeGetClass(() => ZeresPluginLibrary.WebpackModules.getByProps('containerCozyBounded').contentCozy, 'contentCozy'), - avatarSingle: this.safeGetClass(() => ZeresPluginLibrary.WebpackModules.getByProps('containerCozyBounded').avatar.split(/ /g)[0], 'avatar'), - avatarImg: XenoLib.getClass('clickable avatar'), - avatarImgSingle: XenoLib.getSingleClass('clickable avatar'), - botTag: ZeresPluginLibrary.WebpackModules.getByProps('botTagRegular').botTagRegular + ' ' + ZeresPluginLibrary.WebpackModules.getByProps('botTagCozy').botTagCozy, - markupSingle: ZeresPluginLibrary.WebpackModules.getByProps('markup').markup.split(/ /g)[0] - }; - - this.multiClasses = { - defaultColor: ZeresPluginLibrary.WebpackModules.getByProps('defaultColor').defaultColor, - item: ZeresPluginLibrary.WebpackModules.find(m => m.item && m.selected && m.topPill).item, - /* tabBarItem: ZeresPluginLibrary.DiscordClassModules.UserModal.tabBarItem, */ - tabBarContainer: ZeresPluginLibrary.DiscordClassModules.UserModal.tabBarContainer, - tabBar: ZeresPluginLibrary.DiscordClassModules.UserModal.tabBar, - edited: XenoLib.joinClassNames(XenoLib.getClass('separator timestamp'), XenoLib.getClass('separator timestampInline')), - markup: ZeresPluginLibrary.WebpackModules.getByProps('markup')['markup'], - message: { - cozy: { - containerBounded: this.safeGetClass(() => ZeresPluginLibrary.WebpackModules.getByProps('containerCozyBounded').containerCozyBounded, 'containerCozyBounded'), - header: this.safeGetClass(() => ZeresPluginLibrary.WebpackModules.getByProps('containerCozyBounded').headerCozy, 'headerCozy'), - avatar: this.safeGetClass(() => ZeresPluginLibrary.WebpackModules.getByProps('containerCozyBounded').avatar, 'avatar'), - headerMeta: this.safeGetClass(() => ZeresPluginLibrary.WebpackModules.getByProps('containerCozyBounded').headerCozyMeta, 'headerCozyMeta'), - username: this.safeGetClass(() => ZeresPluginLibrary.WebpackModules.getByProps('containerCozyBounded').username, 'username'), - timestamp: this.safeGetClass(() => ZeresPluginLibrary.WebpackModules.getByProps('containerCozyBounded').timestampCozy, 'timestampCozy'), - content: this.safeGetClass(() => ZeresPluginLibrary.WebpackModules.getByProps('containerCozyBounded').contentCozy, 'contentCozy') - } - } - }; - - this.classes = { - markup: ZeresPluginLibrary.WebpackModules.getByProps('markup')['markup'].split(/ /g)[0], - hidden: ZeresPluginLibrary.WebpackModules.getByProps('spoilerText', 'hidden').hidden.split(/ /g)[0], - messages: this.safeGetClass( - () => `.${ZeresPluginLibrary.WebpackModules.getByProps('container', 'containerCompactBounded').container.split(/ /g)[0]} > div:not(.${ZeresPluginLibrary.WebpackModules.getByProps('content', 'marginCompactIndent').content.split(/ /g)[0]})`, - this.safeGetClass(() => `.${XenoLib.getSingleClass('scroller messages')} > .${XenoLib.getSingleClass('channelTextArea message')}`, 'Lighty-youre-a-failure-my-fucking-god'), - true - ), - avatar: this.safeGetClass(() => XenoLib.getSingleClass('header avatar', true), 'avatar-MLV2') - }; - - this.muteModule = ZeresPluginLibrary.WebpackModules.find(m => m.isChannelMuted); - - this.menu = {}; - this.menu.classes = {}; - this.menu.filter = ''; - this.menu.open = false;; - - const Modals = ZeresPluginLibrary.WebpackModules.getByProps('ModalRoot'); - const ImageModalClasses = ZeresPluginLibrary.WebpackModules.getByProps('modal', 'image'); - - const ImageModal = ZeresPluginLibrary.WebpackModules.getByDisplayName('ImageModal'); - - const { default: MaskedLink } = ZeresPluginLibrary.WebpackModules.find(e => e?.default?.type?.toString()?.includes('default.MASKED_LINK')) || {}; - const renderLinkComponent = props => ZeresPluginLibrary.DiscordModules.React.createElement(MaskedLink, props); - - const MLV2ImageModal = props => - ZeresPluginLibrary.DiscordModules.React.createElement( - Modals.ModalRoot, - { className: ImageModalClasses.modal, ...props, size: Modals.ModalSize.DYNAMIC }, - ZeresPluginLibrary.DiscordModules.React.createElement( - ImageModal, - Object.assign( - { - renderLinkComponent, - className: ImageModalClasses.image, - shouldAnimate: true - }, - props - ) - ) - ); - - this.createModal.imageModal = MLV2ImageModal; - - const chatContent = ZeresPluginLibrary.WebpackModules.getByProps('chatContent'); - this.observer.chatContentClass = ((chatContent && chatContent.chatContent) || 'chat-3bRxxu').split(/ /g)[0]; - this.observer.chatClass = 'chat-3bRxxu'; - this.observer.titleClass = !chatContent ? 'ERROR-CLASSWTF' : ZeresPluginLibrary.WebpackModules.getByProps('title', 'chatContent').title.split(/ /g)[0]; - this.observer.containerCozyClass = this.safeGetClass(() => ZeresPluginLibrary.WebpackModules.getByProps('containerCozyBounded').containerCozyBounded.split(/ /g)[0], 'containerCozyBounded'); - - this.localUser = this.UserStore.getCurrentUser(); - - this.deletedChatMessagesCount = {}; - this.editedChatMessagesCount = {}; - - this.channelMessages = ZeresPluginLibrary.WebpackModules.find(m => m._channelMessages)._channelMessages; - - this.autoBackupSaveInterupts = 0; - - this.dispatcher = ZeresPluginLibrary.WebpackModules.find(e => e.dispatch && !e.emitter); - - this.unpatches.push( - this.Patcher.instead( - this.dispatcher, - 'dispatch', - (_, args, original) => this.onDispatchEvent(args, original) - ) - ); - this.unpatches.push( - this.Patcher.instead(ZeresPluginLibrary.DiscordModules.MessageActions, 'startEditMessage', (_, args, original) => { - const channelId = args[0]; - const messageId = args[1]; - if (this.deletedMessageRecord[channelId] && this.deletedMessageRecord[channelId].indexOf(messageId) !== -1) return; - return original(...args); - }) - ); - - this.noTintIds = []; - this.editModifiers = {}; - - this.style = {}; - - this.style.deleted = this.obfuscatedClass('ml2-deleted'); - this.style.deletedAlt = this.obfuscatedClass('ml2-deleted-alt'); - this.style.edited = this.obfuscatedClass('ml2-edited'); - this.style.editedCompact = this.obfuscatedClass('ml2-edited-compact'); - this.style.tab = this.obfuscatedClass('ml2-tab'); - this.style.tabSelected = this.obfuscatedClass('ml2-tab-selected'); - this.style.textIndent = this.obfuscatedClass('ml2-help-text-indent'); - this.style.menu = this.obfuscatedClass('ML2-MENU'); - this.style.openLogs = this.obfuscatedClass('ML2-OL'); - this.style.filter = this.obfuscatedClass('ML2-FILTER'); - this.style.menuMessages = this.obfuscatedClass('ML2-MENU-MESSAGES'); - this.style.menuTabBar = this.obfuscatedClass('ML2-MENU-TABBAR'); - this.style.menuRoot = this.obfuscatedClass('MLv2-menu-root'); - this.style.imageRoot = this.obfuscatedClass('MLv2-image-root'); - this.style.inputWrapper = this.obfuscatedClass('MLv2-input-wrapper'); - this.style.multiInput = this.obfuscatedClass('MLv2-input'); - this.style.multiInputFirst = this.obfuscatedClass('MLv2-input-first'); - this.style.input = this.obfuscatedClass('MLv2-input-input'); - this.style.questionMark = this.obfuscatedClass('MLv2-question-mark'); - this.style.tabBarContainer = this.obfuscatedClass('MLv2-tab-bar-container'); - this.style.tabBar = this.obfuscatedClass('MLv2-tab-bar'); - this.style.tabBarItem = this.obfuscatedClass('MLv2-tab-bar-item'); - - this.invalidateAllChannelCache(); - this.selectedChannel = this.getSelectedTextChannel(); - if (this.selectedChannel) this.cacheChannelMessages(this.selectedChannel.id); - - // todo: custom deleted message text color - ZeresPluginLibrary.PluginUtilities.addStyle( - (this.style.css = !this.settings.obfuscateCSSClasses ? 'ML2-CSS' : this.randomString()), - ` - .${this.style.deleted} .${this.classes.markup}, .${this.style.deleted} .${this.classes.markup} .hljs, .${this.style.deleted} .container-1ov-mD *{ - color: #f04747 !important; - } - html #app-mount .${this.style.deletedAlt} { - background-color: rgba(240, 71, 71, 0.15) !important; - } - html #app-mount .${this.style.deletedAlt}:hover, html #app-mount .${this.style.deletedAlt}.selected-2P5D_Z { - background-color: rgba(240, 71, 71, 0.10) !important; - } - .theme-dark .${this.classes.markup}.${this.style.edited} .${this.style.edited} { - filter: brightness(70%); - } - .theme-light .${this.classes.markup}.${this.style.edited} .${this.style.edited} { - opacity: 0.5; - } - - .${this.style.editedCompact} { - text-indent: 0; - } - - .theme-dark .${this.style.deleted}:not(:hover) img:not(.${this.classes.avatar}), .${this.style.deleted}:not(:hover) .mention, .${this.style.deleted}:not(:hover) .reactions, .${this.style.deleted}:not(:hover) a { - filter: grayscale(100%) !important; - } - - .${this.style.deleted} img:not(.${this.classes.avatar}), .${this.style.deleted} .mention, .${this.style.deleted} .reactions, .${this.style.deleted} a { - transition: filter 0.3s !important; - } - - .theme-dark .${this.style.tab} { - border-color: transparent; - color: rgba(255, 255, 255, 0.4); - padding: 0px 24px; - } - .theme-light .${this.style.tab} { - border-color: transparent; - color: rgba(0, 0, 0, 0.4); - padding: 0px 24px; - } - - #sent.${this.style.tab} { - display: none; - } - - .theme-dark .${this.style.tabSelected} { - border-color: rgb(255, 255, 255); - color: rgb(255, 255, 255); - } - .theme-light .${this.style.tabSelected} { - border-color: rgb(0, 0, 0); - color: rgb(0, 0, 0); - } - - #${this.style.menuTabBar} { - justify-content: space-around; - } - - .${this.style.textIndent} { - margin-left: 40px; - } - - .${this.style.imageRoot} { - pointer-events: all; - } - - #${this.style.menuMessages} { - max-height: 0px; - } - .${this.style.menuRoot} .${XenoLib.getSingleClass('base wrapper')} { - width: 100%; - } - .${this.style.menuRoot} .${this.style.questionMark} { - margin-left: 5px; - } - .${this.style.menuRoot} { - width: 960px; - } - #${this.style.filter} { - opacity: 1; - } - .${this.style.inputWrapper} { - display: -webkit-box; - display: -ms-flexbox; - display: flex; - -webkit-box-orient: vertical; - -webkit-box-direction: normal; - -ms-flex-direction: column; - flex-direction: column; - } - .${this.style.multiInput} { - font-size: 16px; - -webkit-box-sizing: border-box; - box-sizing: border-box; - width: 100%; - border-radius: 3px; - color: var(--text-normal); - background-color: var(--deprecated-text-input-bg); - border: 1px solid var(--deprecated-text-input-border); - -webkit-transition: border-color .2s ease-in-out; - transition: border-color .2s ease-in-out; - display: -webkit-box; - display: -ms-flexbox; - display: flex; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - } - .${this.style.multiInputFirst} { - -webkit-box-flex: 1; - -ms-flex-positive: 1; - flex-grow: 1; - } - .${this.style.input} { - font-size: 16px; - -webkit-box-sizing: border-box; - box-sizing: border-box; - width: 100%; - border-radius: 3px; - color: var(--text-normal); - background-color: var(--deprecated-text-input-bg); - border: 1px solid var(--deprecated-text-input-border); - -webkit-transition: border-color .2s ease-in-out; - transition: border-color .2s ease-in-out; - padding: 10px; - height: 40px; - border: none; - background-color: transparent; - } - .${this.style.questionMark} { - display: -webkit-box; - display: -ms-flexbox; - display: flex; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - -webkit-box-pack: center; - -ms-flex-pack: center; - justify-content: center; - width: 32px; - height: 32px; - border-radius: 2px; - margin-right: 4px; - padding: 0; - min-width: 0; - min-height: 0; - background-color: var(--brand-experiment); - } - .${this.style.tabBarContainer} { - border-bottom: 1px solid var(--background-modifier-accent); - padding-left: 20px; - } - .${this.style.tabBar} { - display: flex; - height: 55px; - align-items: stretch; - -ms-flex-align: stretch; - -webkit-box-align: stretch; - } - .${this.style.tabBarItem} { - display: flex; - font-size: 14px; - margin-right: 40px; - border-bottom: 2px solid transparent; - align-items: center; - -ms-flex-align: center; - -webkit-box-align: center; - cursor: pointer; - line-height: 20px; - font-size: 16px; - position: relative; - font-weight: 500; - flex-shrink: 0; - -ms-flex-negative: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - ` - ); - this.patchMessages(); - this.patchModal(); - - // const createKeybindListener = () => { - // this.keybindListener = new (ZeresPluginLibrary.WebpackModules.getModule(m => typeof m === 'function' && m.toString().includes('.default.setOnInputEventCallback')))(); - // this.keybindListener.on('change', e => { - // if (this.settings.disableKeybind) return; // todo: destroy if disableKeybind is set to true and don't make one if it was true from the start - // // this is the hackiest thing ever but it works xdd - // if (!ZeresPluginLibrary.WebpackModules.getByProps('isFocused').isFocused() || document.getElementsByClassName('bda-slist').length) return; - // const isKeyBind = keybind => { - // if (e.combo.length != keybind.length) return false; - // // console.log(e.combo); - // for (let i = 0; i < e.combo.length; i++) { - // if (e.combo[i][1] != keybind[i]) { - // return false; - // } - // } - // return true; - // }; - // const close = () => { - // this.menu.filter = ''; - // this.menu.open = false; - // this.ModalStack.closeModal(this.style.menu); - // }; - // if (isKeyBind(this.settings.openLogKeybind)) { - // if (this.menu.open) return close(); - // return this.openWindow(); - // } - // if (isKeyBind(this.settings.openLogFilteredKeybind)) { - // if (this.menu.open) return close(); - // if (!this.selectedChannel) { - // this.showToast('No channel selected', { type: 'error' }); - // return this.openWindow(); - // } - // this.menu.filter = `channel:${this.selectedChannel.id}`; - // this.openWindow(); - // } - // }); - // }; - - //this.powerMonitor = ZeresPluginLibrary.WebpackModules.getByProps('remotePowerMonitor').remotePowerMonitor; - - // const refreshKeykindListener = () => { - // this.keybindListener.destroy(); - // createKeybindListener(); - // }; - - //this.keybindListenerInterval = setInterval(refreshKeykindListener, 30 * 1000 * 60); // 10 minutes - - //createKeybindListener(); - - // this.powerMonitor.on( - // 'resume', - // (this.powerMonitorResumeListener = () => { - // setTimeout(refreshKeykindListener, 1000); - // }) - // ); -/* - this.unpatches.push( - this.Patcher.instead(ZeresPluginLibrary.WebpackModules.getByDisplayName('TextAreaAutosize').prototype, 'focus', (thisObj, args, original) => { - if (this.menu.open) return; - return original(...args); - }) - ); - - this.unpatches.push( - this.Patcher.instead(ZeresPluginLibrary.WebpackModules.getByDisplayName('LazyImage').prototype, 'getSrc', (thisObj, args, original) => { - let indx; - if (thisObj && thisObj.props && thisObj.props.src && ((indx = thisObj.props.src.indexOf('?ML2=true')), indx !== -1)) return thisObj.props.src.substr(0, indx); - return original(...args); - }) - ); */ - - this.dataManagerInterval = setInterval(() => { - this.handleMessagesCap(); - }, 60 * 1000 * 5); // every 5 minutes, no need to spam it, could be intensive - - this.ContextMenuActions = ZeresPluginLibrary.DiscordModules.ContextMenuActions; - - this.menu.randomValidChannel = (() => { - const channels = this.ChannelStore.getChannels ? this.ChannelStore.getChannels() : ZeresPluginLibrary.WebpackModules.getByProps('getChannels').getChannels(); - var keys = Object.keys(channels); - return channels[keys[(keys.length * Math.random()) << 0]]; - })(); - - this.menu.userRequestQueue = []; - - this.menu.deleteKeyDown = false; - document.addEventListener( - 'keydown', - (this.keydownListener = e => { - if (e.repeat) return; - if (e.keyCode === 46) this.menu.deleteKeyDown = true; - }) - ); - document.addEventListener( - 'keyup', - (this.keyupListener = e => { - if (e.repeat) return; - if (e.keyCode === 46) this.menu.deleteKeyDown = false; - }) - ); - - this.menu.shownMessages = -1; - const iconShit = ZeresPluginLibrary.WebpackModules.getByProps('container', 'children', 'toolbar', 'iconWrapper'); - // Icon by font awesome - // https://fontawesome.com/license - this.channelLogButton = this.parseHTML(`
- -
`); - this.channelLogButton.addEventListener('click', () => { - this.openWindow(); - }); - this.channelLogButton.addEventListener('contextmenu', () => { - if (!this.selectedChannel) return; - this.menu.filter = `channel:${this.selectedChannel.id}`; - this.openWindow(); - }); - new ZeresPluginLibrary.Tooltip(this.channelLogButton, 'Open Logs', { side: 'bottom' }); - - if (this.settings.showOpenLogsButton) this.addOpenLogsButton(); - - this.unpatches.push( - this.Patcher.instead(ZeresPluginLibrary.DiscordModules.MessageActions, 'deleteMessage', (_, args, original) => { - const messageId = args[1]; - if (this.messageRecord[messageId] && this.messageRecord[messageId].delete_data) return; - this.localDeletes.push(messageId); - if (this.localDeletes.length > 10) this.localDeletes.shift(); - return original(...args); - }) - ); - - this.unpatches.push( - this.Patcher.instead(this.messageStore, 'getLastEditableMessage', (_this, [channelId]) => { - const me = XenoLib.DiscordAPI.userId; - return _this - .getMessages(channelId) - .toArray() - .reverse() - .find(iMessage => iMessage.author.id === me && iMessage.state === 'SENT' && (!this.messageRecord[iMessage.id] || !this.messageRecord[iMessage.id].delete_data)); - }) - ); - this.patchContextMenus(); - - if (!(this.settings.flags & Flags.STARTUP_HELP)) { - this.settings.flags |= Flags.STARTUP_HELP; - this.showLoggerHelpModal(true); - this.saveSettings(); - } - - this.selfTestInterval = setInterval(() => { - this.selfTestTimeout = setTimeout(() => { - if (this.selfTestFailures > 4) { - clearInterval(this.selfTestInterval); - this.selfTestInterval = 0; - return BdApi.alert(`${this.getName()}: internal error.`, `Self test failure: Failed to hook dispatch. Recommended to reload your discord (CTRL + R) as the plugin may be in a broken state! If you still see this error, open up the devtools console (CTRL + SHIFT + I, click console tab) and report the errors to ${this.getAuthor()} for further assistance.`); - } - ZeresPluginLibrary.Logger.warn(this.getName(), 'Dispatch is not hooked, all our hooks may be invalid, attempting to reload self'); - this.selfTestFailures++; - this.stop(); - this.start(); - }, 3000); - this.dispatcher.dispatch({ - type: 'MESSAGE_LOGGER_V2_SELF_TEST' - }); - }, 10000); - - if (this.selfTestInited) return; - this.selfTestFailures = 0; - this.selfTestInited = true; - } - shutdown() { - if (!global.ZeresPluginLibrary) return; - this.__started = false; - const tryUnpatch = fn => { - if (typeof fn !== 'function') return; - try { - // things can bug out, best to reload tbh, should maybe warn the user? - fn(); - } catch (e) { - ZeresPluginLibrary.Logger.stacktrace(this.getName(), 'Error unpatching', e); - } - }; - if (Array.isArray(this.unpatches)) for (let unpatch of this.unpatches) tryUnpatch(unpatch); - ZeresPluginLibrary.Patcher.unpatchAll(this.getName()); - if (this.MessageContextMenuPatch) tryUnpatch(this.MessageContextMenuPatch); - if (this.ChannelContextMenuPatch) tryUnpatch(this.ChannelContextMenuPatch); - if (this.GuildContextMenuPatch) tryUnpatch(this.GuildContextMenuPatch); - try { - this.Patcher.unpatchAll(); - } catch (e) { } - this.forceReloadMessages(); - // if (this.keybindListener) this.keybindListener.destroy(); - if (this.style && this.style.css) ZeresPluginLibrary.PluginUtilities.removeStyle(this.style.css); - if (this.dataManagerInterval) clearInterval(this.dataManagerInterval); - // if (this.keybindListenerInterval) clearInterval(this.keybindListenerInterval); - if (this.selfTestInterval) clearInterval(this.selfTestInterval); - if (this.selfTestTimeout) clearTimeout(this.selfTestTimeout); - if (this._autoUpdateInterval) clearInterval(this._autoUpdateInterval); - if (this.keydownListener) document.removeEventListener('keydown', this.keydownListener); - if (this.keyupListener) document.removeEventListener('keyup', this.keyupListener); - // if (this.powerMonitor) this.powerMonitor.removeListener('resume', this.powerMonitorResumeListener); - if (this.channelLogButton) this.channelLogButton.remove(); - if (this._imageCacheServer) this._imageCacheServer.stop(); - if (typeof this._modalsApiUnsubcribe === 'function') - try { - this._modalsApiUnsubcribe(); - } catch { } - // console.log('invalidating cache'); - this.invalidateAllChannelCache(); - // if (this.selectedChannel) this.cacheChannelMessages(this.selectedChannel.id); // bad idea? - } - automaticallyUpdate(tryProxy) { - const updateFail = () => XenoLib.Notifications.warning(`[${this.getName()}] Unable to check for updates!`, { timeout: 7500 }); - new Promise(resolve => { - const https = require('https'); - const req = https.get(tryProxy ? 'https://cors-anywhere.herokuapp.com/https://raw.githubusercontent.com/1Lighty/BetterDiscordPlugins/master/Plugins/MessageLoggerV2/MessageLoggerV2.plugin.js' : 'https://raw.githubusercontent.com/1Lighty/BetterDiscordPlugins/master/Plugins/MessageLoggerV2/MessageLoggerV2.plugin.js', { headers: { 'origin': 'discord.com' } }, res => { - let body = ''; - res.on('data', chunk => ((body += new TextDecoder("utf-8").decode(chunk)), void 0)); - res.on('end', (rez) => { - if (rez.statusCode !== 200) { - if (!tryProxy) return this.automaticallyUpdate(true); - updateFail(); - return; - } - if (!XenoLib.versionComparator(this.getVersion(), XenoLib.extractVersion(body))) return; - const fs = require('fs'); - /* - * why are we letting Zere, the braindead American let control BD when he can't even - * fucking read clearly documented and well known standards, such as __filename being - * the files full fucking path and not just the filename itself, IS IT REALLY SO HARD - * TO FUCKING READ?! https://nodejs.org/api/modules.html#modules_filename - */ - const _zerecantcode_path = require('path'); - const theActualFileNameZere = _zerecantcode_path.join(__dirname, _zerecantcode_path.basename(__filename)); - fs.writeFileSync(theActualFileNameZere, body); - XenoLib.Notifications.success(`[${this.getName()}] Successfully updated!`, { timeout: 0 }); - if (BdApi.isSettingEnabled('fork-ps-5') && !this.__isPowerCord) return; - BdApi.Plugins.reload(this.getName()); - }); - }); - req.on('error', _ => { - if (!tryProxy) return this.automaticallyUpdate(true); - updateFail(); - }); - //req.end(); - }); - } - // title-3qD0b- da-title container-1r6BKw da-container themed-ANHk51 da-themed - // chatContent-a9vAAp da-chatContent - observer({ addedNodes }) { - let isChat = false; - let isTitle = false; - for (const change of addedNodes) { - if ((isTitle = isChat = typeof change.className === 'string' && change.className.indexOf(this.observer.chatClass) !== -1) || (isChat = typeof change.className === 'string' && change.className.indexOf(this.observer.chatContentClass) !== -1) || (isTitle = typeof change.className === 'string' && change.className.indexOf(this.observer.titleClass) !== -1) || (change.style && change.style.cssText === 'border-radius: 2px; background-color: rgba(114, 137, 218, 0);') || (typeof change.className === 'string' && change.className.indexOf(this.observer.containerCozyClass) !== -1)) { - try { - if (isChat) { - this.selectedChannel = this.getSelectedTextChannel(); - this.noTintIds = []; - this.editModifiers = {}; - } - if (!this.selectedChannel) return ZeresPluginLibrary.Logger.warn(this.getName(), 'Chat was loaded but no text channel is selected'); - if (isTitle && this.settings.showOpenLogsButton) { - let srch = change.querySelector('div[class*="search-"]'); - if (!srch) return ZeresPluginLibrary.Logger.warn(this.getName(), 'Observer caught title loading, but no search bar was found! Open Logs button will not show!'); - if (this.channelLogButton && srch.parentElement) { - srch.parentElement.insertBefore(this.channelLogButton, srch); // memory leak..? - } - srch = null; - if (!isChat) return; - } - const showStuff = (map, name) => { - if (map[this.selectedChannel.id] && map[this.selectedChannel.id]) { - if (this.settings.useNotificationsInstead) { - XenoLib.Notifications.info(`There are ${map[this.selectedChannel.id]} new ${name} messages in ${this.selectedChannel.name && this.selectedChannel.type !== 3 ? '<#' + this.selectedChannel.id + '>' : 'DMs'}`, { timeout: 3000 }); - } else { - this.showToast(`There are ${map[this.selectedChannel.id]} new ${name} messages in ${this.selectedChannel.name ? '#' + this.selectedChannel.name : 'DMs'}`, { - type: 'info', - onClick: () => this.openWindow(name), - timeout: 3000 - }); - } - map[this.selectedChannel.id] = 0; - } - }; - if (this.settings.showDeletedCount) showStuff(this.deletedChatMessagesCount, 'deleted'); - if (this.settings.showEditedCount) showStuff(this.editedChatMessagesCount, 'edited'); - } catch (e) { - ZeresPluginLibrary.Logger.stacktrace(this.getName(), 'Error in observer', e); - } - break; - } - } - } - buildSetting(data) { - const { id } = data; - const setting = XenoLib.buildSetting(data); - if (id) setting.getElement().id = this.obfuscatedClass(id); - return setting; - } - createSetting(data) { - const current = Object.assign({}, data); - if (!current.onChange) { - current.onChange = value => { - this.settings[current.id] = value; - if (current.callback) current.callback(value); - }; - } - if (typeof current.value === 'undefined') current.value = this.settings[current.id]; - return this.buildSetting(current); - } - createGroup(group) { - const { name, id, collapsible, shown, settings } = group; - - const list = []; - for (let s = 0; s < settings.length; s++) list.push(this.createSetting(settings[s])); - - const settingGroup = new ZeresPluginLibrary.Settings.SettingGroup(name, { shown, collapsible }).append(...list); - settingGroup.group.id = id; // should generate the id in here instead? - return settingGroup; - } - getSettingsPanel() { - // todo, sort out the menu - const list = []; - // list.push( - // this.createGroup({ - // name: 'Keybinds', - // id: this.obfuscatedClass('ml2-settings-keybinds'), - // collapsible: true, - // shown: false, - // settings: [ - // { - // name: 'Open menu keybind', - // id: 'openLogKeybind', - // type: 'keybind' - // }, - // { - // name: 'Open log filtered by selected channel', - // id: 'openLogFilteredKeybind', - // type: 'keybind' - // }, - // { - // name: 'Disable keybinds', - // id: 'disableKeybind', - // type: 'switch' - // } - // ] - // }) - // ); - list.push( - this.createGroup({ - name: 'Ignores and overrides', - id: this.obfuscatedClass('ml2-settings-ignores-overrides'), - collapsible: true, - shown: false, - settings: [ - { - name: 'Ignore muted servers', - id: 'ignoreMutedGuilds', - type: 'switch' - }, - { - name: 'Ignore muted channels', - id: 'ignoreMutedChannels', - type: 'switch' - }, - { - name: 'Ignore bots', - id: 'ignoreBots', - type: 'switch' - }, - { - name: 'Ignore messages posted by you', - id: 'ignoreSelf', - type: 'switch' - }, - { - name: 'Ignore message edits from you', - id: 'ignoreLocalEdits', - type: 'switch' - }, - { - name: 'Ignore message deletes from you', - note: 'Only ignores if you delete your own message.', - id: 'ignoreLocalDeletes', - type: 'switch' - }, - { - name: 'Ignore blocked users', - id: 'ignoreBlockedUsers', - type: 'switch' - }, - { - name: 'Ignore NSFW channels', - id: 'ignoreNSFW', - type: 'switch' - }, - { - name: 'Only log whitelist', - id: 'onlyLogWhitelist', - type: 'switch' - }, - { - name: 'Always log selected channel, regardless of whitelist/blacklist', - id: 'alwaysLogSelected', - type: 'switch' - }, - { - name: 'Always log DMs, regardless of whitelist/blacklist', - id: 'alwaysLogDM', - type: 'switch' - }, - { - name: 'Always log ghost pings, regardless of whitelist/blacklist', - note: 'Messages sent in ignored/muted/blacklisted servers and channels will be logged and shown in sent, but only gets saved if a ghost ping occurs.', - id: 'alwaysLogGhostPings', - type: 'switch' - } - ] - }) - ); - list.push( - this.createGroup({ - name: 'Display settings', - id: this.obfuscatedClass('ml2-settings-display'), - collapsible: true, - shown: false, - settings: [ - { - name: 'Display dates with timestamps', - id: 'displayDates', - type: 'switch', - callback: () => { - if (this.selectedChannel) { - // change NOW - this.invalidateAllChannelCache(); - this.cacheChannelMessages(this.selectedChannel.id); - } - } - }, - { - name: 'Display deleted messages in chat', - id: 'showDeletedMessages', - type: 'switch', - callback: () => { - this.invalidateAllChannelCache(); - if (this.selectedChannel) this.cacheChannelMessages(this.selectedChannel.id); - } - }, - { - name: 'Display edited messages in chat', - id: 'showEditedMessages', - type: 'switch', - callback: () => this.dispatcher.dispatch({ type: 'MLV2_FORCE_UPDATE_MESSAGE_CONTENT' }) - }, - { - name: 'Max number of shown edits', - id: 'maxShownEdits', - type: 'textbox', - onChange: val => { - if (isNaN(val)) return this.showToast('Value must be a number!', { type: 'error' }); - this.settings.maxShownEdits = parseInt(val); - this.dispatcher.dispatch({ type: 'MLV2_FORCE_UPDATE_MESSAGE_CONTENT' }); - } - }, - { - name: 'Show oldest edit instead of newest if over the shown edits limit', - id: 'hideNewerEditsFirst', - type: 'switch', - callback: () => this.dispatcher.dispatch({ type: 'MLV2_FORCE_UPDATE_MESSAGE_CONTENT' }) - }, - { - name: 'Use red background instead of red text for deleted messages', - id: 'useAlternativeDeletedStyle', - type: 'switch', - callback: () => this.dispatcher.dispatch({ type: 'MLV2_FORCE_UPDATE_MESSAGE' }) - }, - { - name: 'Display purged messages in chat', - id: 'showPurgedMessages', - type: 'switch', - callback: () => { - this.invalidateAllChannelCache(); - if (this.selectedChannel) this.cacheChannelMessages(this.selectedChannel.id); - } - }, - { - name: 'Restore deleted messages after reload', - id: 'restoreDeletedMessages', - type: 'switch', - callback: val => { - if (val) { - this.invalidateAllChannelCache(); - if (this.selectedChannel) this.cacheChannelMessages(this.selectedChannel.id); - } - } - }, - { - name: 'Show amount of new deleted messages when entering a channel', - id: 'showDeletedCount', - type: 'switch' - }, - { - name: 'Show amount of new edited messages when entering a channel', - id: 'showEditedCount', - type: 'switch' - }, - { - name: 'Display update notes', - id: 'displayUpdateNotes', - type: 'switch' - }, - { - name: 'Menu sort direction', - id: 'reverseOrder', - type: 'radio', - options: [ - { - name: 'New - old', - value: false - }, - { - name: 'Old - new', - value: true - } - ] - }, - { - name: 'Use XenoLib notifications instead of toasts', - note: "This works for edit, send, delete and purge toasts, as well as delete and edit count toasts. Toggle it if you don't know what this does.", - id: 'useNotificationsInstead', - type: 'switch', - callback: e => (e ? XenoLib.Notifications.success('Using Xenolib notifications', { timeout: 5000 }) : this.showToast('Using toasts', { type: 'success', timeout: 5000 })) - } - ] - }) - ); - list.push( - this.createGroup({ - name: 'Misc settings', - id: this.obfuscatedClass('ml2-settings-misc'), - collapsible: true, - shown: false, - settings: [ - { - name: 'Disable saving data. Logged messages are erased after reload/restart. Disables auto backup.', - id: 'dontSaveData', - type: 'switch', - callback: val => { - if (!val) this.saveData(); - if (!val && this.settings.autoBackup) this.saveBackup(); - } - }, - { - name: "Auto backup data (won't fully prevent losing data, just prevent total data loss)", - id: 'autoBackup', - type: 'switch', - callback: val => { - if (val && !this.settings.dontSaveData) this.saveBackup(); - } - } /* - { - // no time, TODO! - name: 'Deleted messages color', - id: 'deletedMessageColor', - type: 'color' - }, */, - { - name: 'Aggresive message caching (makes sure we have the data of any deleted or edited messages)', - id: 'aggresiveMessageCaching', - type: 'switch' - }, - { - name: 'Cache all images by storing them locally in the MLV2_IMAGE_CACHE folder inside the plugins folder', - id: 'cacheAllImages', - type: 'switch' - }, - { - name: "Don't delete cached images", - note: "If the message the image is from is erased from data, the cached image will be kept. You'll have to monitor disk usage on your own!", - id: 'dontDeleteCachedImages', - type: 'switch' - }, - { - name: 'Display open logs button next to the search box top right in channels', - id: 'showOpenLogsButton', - type: 'switch', - callback: val => { - if (val) return this.addOpenLogsButton(); - this.removeOpenLogsButton(); - } - }, - { - name: 'Block spam edit notifications (if enabled)', - id: 'blockSpamEdit', - type: 'switch' - } - ] - }) - ); - list.push( - this.createGroup({ - name: 'Toast notifications for guilds', - id: this.obfuscatedClass('ml2-settings-toast-guilds'), - collapsible: true, - shown: false, - settings: [ - { - name: 'Message sent', - id: 'sent', - type: 'switch', - value: this.settings.toastToggles.sent, - onChange: val => { - this.settings.toastToggles.sent = val; - } - }, - { - name: 'Message edited', - id: 'edited', - type: 'switch', - value: this.settings.toastToggles.edited, - onChange: val => { - this.settings.toastToggles.edited = val; - } - }, - { - name: 'Message deleted', - id: 'deleted', - type: 'switch', - value: this.settings.toastToggles.deleted, - onChange: val => { - this.settings.toastToggles.deleted = val; - } - }, - { - name: 'Ghost pings', - id: 'ghostPings', - type: 'switch', - value: this.settings.toastToggles.ghostPings, - onChange: val => { - this.settings.toastToggles.ghostPings = val; - } - }, - { - name: 'Disable toasts for local user (yourself)', - id: 'disableToastsForLocal', - type: 'switch', - value: this.settings.toastToggles.disableToastsForLocal, - onChange: val => { - this.settings.toastToggles.disableToastsForLocal = val; - } - } - ] - }) - ); - - list.push( - this.createGroup({ - name: 'Toast notifications for DMs', - id: this.obfuscatedClass('ml2-settings-toast-dms'), - collapsible: true, - shown: false, - settings: [ - { - name: 'Message sent', - id: 'sent', - type: 'switch', - value: this.settings.toastTogglesDMs.sent, - onChange: val => { - this.settings.toastTogglesDMs.sent = val; - } - }, - { - name: 'Message edited', - id: 'edited', - type: 'switch', - value: this.settings.toastTogglesDMs.edited, - onChange: val => { - this.settings.toastTogglesDMs.edited = val; - } - }, - { - name: 'Message deleted', - id: 'deleted', - type: 'switch', - value: this.settings.toastTogglesDMs.deleted, - onChange: val => { - this.settings.toastTogglesDMs.deleted = val; - } - }, - { - name: 'Ghost pings', - id: 'ghostPings', - type: 'switch', - value: this.settings.toastTogglesDMs.ghostPings, - onChange: val => { - this.settings.toastTogglesDMs.ghostPings = val; - } - } - ] - }) - ); - - list.push( - this.createGroup({ - name: 'Message caps', - id: this.obfuscatedClass('ml2-settings-caps'), - collapsible: true, - shown: false, - settings: [ - { - name: 'Cached messages cap', - note: 'Max number of sent messages logger should keep track of', - id: 'messageCacheCap', - type: 'textbox', - onChange: val => { - if (isNaN(val)) return this.showToast('Value must be a number!', { type: 'error' }); - this.settings.messageCacheCap = parseInt(val); - clearInterval(this.dataManagerInterval); - this.dataManagerInterval = setInterval(() => { - this.handleMessagesCap(); - }, 60 * 1000 * 5); - } - }, - { - name: 'Saved messages cap', - note: "Max number of messages saved to disk, this limit is for deleted, edited and purged INDIVIDUALLY. So if you have it set to 1000, it'll be 1000 edits, 1000 deletes and 1000 purged messages max", - id: 'savedMessagesCap', - type: 'textbox', - onChange: val => { - if (isNaN(val)) return this.showToast('Value must be a number!', { type: 'error' }); - this.settings.savedMessagesCap = parseInt(val); - clearInterval(this.dataManagerInterval); - this.dataManagerInterval = setInterval(() => { - this.handleMessagesCap(); - }, 60 * 1000 * 5); - } - }, - { - name: 'Menu message render cap', - note: 'How many messages will show before the LOAD MORE button will show', - id: 'renderCap', - type: 'textbox', - onChange: val => { - if (isNaN(val)) return this.showToast('Value must be a number!', { type: 'error' }); - this.settings.renderCap = parseInt(val); - clearInterval(this.dataManagerInterval); - } - } - ] - }) - ); - - list.push( - this.createGroup({ - name: 'Advanced', - id: this.obfuscatedClass('ml2-settings-advanced'), - collapsible: true, - shown: false, - settings: [ - { - name: 'Obfuscate CSS classes', - note: 'Enable this if some plugin, library or theme is blocking you from using the plugin', - id: 'obfuscateCSSClasses', - type: 'switch' - }, - { - name: 'Automatic updates', - note: "Do NOT disable unless you really don't want automatic updates", - id: 'autoUpdate', - type: 'switch', - callback: val => { - if (val) { - this._autoUpdateInterval = setInterval(_ => this.automaticallyUpdate(), 1000 * 60 * 15); // 15 minutes - this.automaticallyUpdate(); - } else { - clearInterval(this._autoUpdateInterval); - try { - ZeresPluginLibrary.PluginUpdater.checkForUpdate(this.getName(), this.getVersion(), 'https://raw.githubusercontent.com/1Lighty/BetterDiscordPlugins/master/Plugins/MessageLoggerV2/MessageLoggerV2.plugin.js'); - } catch (err) {} - } - } - }, - { - name: 'Contextmenu submenu name', - note: "Instead of saying Message Logger, make it say something else, so it's screenshot friendly", - id: 'contextmenuSubmenuName', - type: 'textbox' - } /* , - { - name: 'Image cache directory', - note: 'Press enter to save the path', - id: 'imageCacheDir', - type: 'path', - onChange: val => { - console.log(this.settings.imageCacheDir, val, 'what?'); - if (this.settings.imageCacheDir === val) return; - const savedImages = this.nodeModules.fs.readdirSync(this.settings.imageCacheDir); - console.log(savedImages); - if (!savedImages.length) return; - https://stackoverflow.com/questions/10420352/ - function humanFileSize(bytes, si) { - const thresh = si ? 1000 : 1024; - if (Math.abs(bytes) < thresh) return `${bytes} B`; - const units = si ? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] : ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']; - let u = -1; - do { - bytes /= thresh; - ++u; - } while (Math.abs(bytes) >= thresh && u < units.length - 1); - return `${bytes.toFixed(1)}${units[u]}`; - } - let sz = 0; - for (let image of savedImages) ; - const size = humanFileSize(this.nodeModules.fs.statSync(this.settings.imageCacheDir).size); - ZeresPluginLibrary.Modals.showModal('Move images', ZeresPluginLibrary.DiscordModules.React.createElement(ZeresPluginLibrary.DiscordModules.TextElement.default, { color: ZeresPluginLibrary.DiscordModules.TextElement.Colors.PRIMARY, children: [`Would you like to move ${savedImages.length} images from the old folder to the new? Size of all images is ${size}.`] }), { - confirmText: 'Yes', - onConfirm: () => {} - }); - //this.settings.imageCacheDir = val; - } - } */ - ] - }) - ); - - const div = document.createElement('div'); - div.id = this.obfuscatedClass('ml2-settings-buttonbox'); - div.style.display = 'inline-flex'; - div.appendChild(this.createButton('Changelog', () => XenoLib.showChangelog(`${this.getName()} has been updated!`, this.getVersion(), this.getChanges()))); - div.appendChild(this.createButton('Stats', () => this.showStatsModal())); - div.appendChild(this.createButton('Donate', () => this.nodeModules.electron.shell.openExternal('https://paypal.me/lighty13'))); - div.appendChild( - this.createButton('Support server', () => { - ZeresPluginLibrary.DiscordModules.LayerManager.popLayer(); - if (this.tools.getServer('389049952732446731')) { - ZeresPluginLibrary.DiscordModules.GuildActions.transitionToGuildSync('389049952732446731'); - } else { - ZeresPluginLibrary.DiscordModules.InviteActions.openNativeAppModal('NYvWdN5'); - } - }) - ); - div.appendChild(this.createButton('Help', () => this.showLoggerHelpModal())); - let button = div.firstElementChild; - while (button) { - button.style.marginRight = button.style.marginLeft = `5px`; - button = button.nextElementSibling; - } - - list.push(div); - - return ZeresPluginLibrary.Settings.SettingPanel.build(_ => this.saveSettings(), ...list); - } - /* ==================================================-|| START HELPERS ||-================================================== */ - saveSettings() { - ZeresPluginLibrary.PluginUtilities.saveSettings(this.getName(), this.settings); - } - handleDataSaving() { - // saveData/setPluginData is synchronous, can get slow with bigger files - if (!this.handleDataSaving.errorPageClass) this.handleDataSaving.errorPageClass = '.' + XenoLib.getClass('errorPage'); - /* refuse saving on error page */ - if (!this.messageRecord || document.querySelector(this.handleDataSaving.errorPageClass)) return; /* did we crash? */ - if (!Object.keys(this.messageRecord).length) return BdApi.deleteData(this.getName() + 'Data', 'data'); - const callback = err => { - if (err) { - XenoLib.Notifications.error('There has been an error saving the data file'); - ZeresPluginLibrary.Logger.stacktrace(this.getName(), 'There has been an error saving the data file', err); - } - if (this.settings.autoBackup) { - if (this.saveBackupTimeout) this.autoBackupSaveInterupts++; - if (this.autoBackupSaveInterupts < 4) { - if (this.saveBackupTimeout) clearTimeout(this.saveBackupTimeout); - // 20 seconds after, in case shits going down y'know, better not to spam save and corrupt it, don't become the thing you're trying to eliminate - this.saveBackupTimeout = setTimeout(() => this.saveBackup(), 20 * 1000); - } - } - this.requestedDataSave = 0; - }; - const useEfficient = !window.ED; - if (useEfficient) { - this.efficientlySaveData( - this.getName() + 'Data', - 'data', - { - messageRecord: this.messageRecord, - deletedMessageRecord: this.deletedMessageRecord, - editedMessageRecord: this.editedMessageRecord, - purgedMessageRecord: this.purgedMessageRecord - }, - callback - ); - } else { - ZeresPluginLibrary.PluginUtilities.saveData(this.getName() + 'Data', 'data', { - messageRecord: this.messageRecord, - deletedMessageRecord: this.deletedMessageRecord, - editedMessageRecord: this.editedMessageRecord, - purgedMessageRecord: this.purgedMessageRecord - }); - callback(); - } - } - saveData() { - if (!this.settings.dontSaveData && !this.requestedDataSave) this.requestedDataSave = setTimeout(() => this.handleDataSaving(), 1000); // needs to be async - } - efficientlySaveData(name, key, data, callback) { - try { - let loadedData; - try { - /* bd gay bruh */ - loadedData = BdApi.loadData(name, key); - } catch (err) { } - if (loadedData) for (const key in data) loadedData[key] = data[key]; - this.nodeModules.fs.writeFile(this.__isPowerCord ? BdApi.__getPluginConfigPath(name) : this.nodeModules.path.join(this.pluginDir, `${name}.config.json`), JSON.stringify({ [key]: data }), callback); - } catch (e) { - XenoLib.Notifications.error('There has been an error saving the data file'); - ZeresPluginLibrary.Logger.stacktrace(this.getName(), 'There has been an error saving the data file', e); - } - } - saveBackup() { - const callback = err => { - if (err) { - XenoLib.Notifications.error('There has been an error saving the data file'); - ZeresPluginLibrary.Logger.stacktrace(this.getName(), 'There has been an error saving the data file', err); - } - this.saveBackupTimeout = 0; - this.autoBackupSaveInterupts = 0; - if (!XenoLib.loadData(this.getName() + 'DataBackup', 'data').messageRecord) this.saveBackupTimeout = setTimeout(() => this.saveBackup, 300); // don't be taxing - }; - const useEfficient = !window.ED; - if (useEfficient) { - this.efficientlySaveData( - this.getName() + 'DataBackup', - 'data', - { - messageRecord: this.messageRecord, - deletedMessageRecord: this.deletedMessageRecord, - editedMessageRecord: this.editedMessageRecord, - purgedMessageRecord: this.purgedMessageRecord - }, - callback - ); - } else { - ZeresPluginLibrary.PluginUtilities.saveData(this.getName() + 'DataBackup', 'data', { - messageRecord: this.messageRecord, - deletedMessageRecord: this.deletedMessageRecord, - editedMessageRecord: this.editedMessageRecord, - purgedMessageRecord: this.purgedMessageRecord - }); - callback(); - } - } - parseHTML(html) { - // TODO: drop this func, it's 75% slower than just making the elements manually - var template = document.createElement('template'); - html = html.trim(); // Never return a text node of whitespace as the result - template.innerHTML = html; - return template.content.firstChild; - } - randomString() { - let start = rand(); - while (start[0].toUpperCase() == start[0].toLowerCase()) start = rand(); - return start + '-' + rand(); - function rand() { - return Math.random().toString(36).substr(2, 7); - } - } - obfuscatedClass(selector) { - if (!this.obfuscatedClass.obfuscations) this.obfuscatedClass.obfuscations = {}; - if (this.settings.obfuscateCSSClasses) { - const { obfuscations } = this.obfuscatedClass; - return obfuscations[selector] || (obfuscations[selector] = this.randomString()); - } - return selector; - } - createTimeStamp(from = undefined, forcedDate = false) { - // todo: timestamp for edited tooltip - let date; - if (from) date = new Date(from); - else date = new Date(); - return (this.settings.displayDates || forcedDate) && forcedDate !== -1 ? `${date.toLocaleTimeString()}, ${date.toLocaleDateString()}` : forcedDate !== -1 ? date.toLocaleTimeString() : date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); - } - getCachedMessage(id, channelId = 0) { - let cached = this.cachedMessageRecord.find(m => m.id == id); - if (cached) return cached; - if (channelId) return this.tools.getMessage(channelId, id); // if the message isn't cached, it returns undefined - return null; - } - getEditedMessage(messageId, channelId) { - if (this.editedMessageRecord[channelId] && this.editedMessageRecord[channelId].findIndex(m => m === messageId) != -1) { - return this.messageRecord[messageId]; - } - return null; - } - getSavedMessage(id) { - /* DEPRECATED */ - return this.messageRecord[id]; - } - cleanupUserObject(user) { - /* backported from MLV2 rewrite */ - return { - discriminator: user.discriminator, - username: user.username, - avatar: user.avatar, - id: user.id, - bot: user.bot, - public_flags: typeof user.publicFlags !== 'undefined' ? user.publicFlags : user.public_flags - }; - } - cleanupMessageObject(message) { - const ret = { - mention_everyone: typeof message.mention_everyone !== 'boolean' ? typeof message.mentionEveryone !== 'boolean' ? false : message.mentionEveryone : message.mention_everyone, - edited_timestamp: message.edited_timestamp || message.editedTimestamp && new Date(message.editedTimestamp).getTime() || null, - attachments: message.attachments || [], - channel_id: message.channel_id, - reactions: (message.reactions || []).map(e => (!e.emoji.animated && delete e.emoji.animated, !e.me && delete e.me, e)), - guild_id: message.guild_id || (this.ChannelStore.getChannel(message.channel_id) ? this.ChannelStore.getChannel(message.channel_id).guild_id : undefined), - content: message.content, - type: message.type, - embeds: message.embeds || [], - author: this.cleanupUserObject(message.author), - mentions: (message.mentions || []).map(e => (typeof e === 'string' ? this.UserStore.getUser(e) ? this.cleanupUserObject(this.UserStore.getUser(e)) : e : this.cleanupUserObject(e))), - mention_roles: message.mention_roles || message.mentionRoles || [], - id: message.id, - flags: message.flags, - timestamp: new Date(message.timestamp).getTime(), - referenced_message: null - }; - if (ret.type === 19) { - ret.message_reference = message.message_reference || message.messageReference; - if (ret.message_reference) { - if (message.referenced_message) { - ret.referenced_message = this.cleanupMessageObject(message.referenced_message); - } else if (this.messageStore.getMessage(ret.message_reference.channel_id, ret.message_reference.message_id)) { - ret.referenced_message = this.cleanupMessageObject(this.messageStore.getMessage(ret.message_reference.channel_id, ret.message_reference.message_id)); - } - } - } - this.fixEmbeds(ret); - return ret; - } - createMiniFormattedData(message) { - message = XenoLib.DiscordUtils.cloneDeep(message); - const obj = { - message: this.cleanupMessageObject(message), // works! - local_mentioned: this.tools.isMentioned(message, this.localUser.id), - /* ghost_pinged: false, */ - delete_data: null /* { - time: integer, - hidden: bool - } */, - edit_history: null /* [ - { - content: string, - timestamp: string - } - ], - edits_hidden: bool */ - }; - return obj; - } - getSelectedTextChannel() { - return this.ChannelStore.getChannel(ZeresPluginLibrary.DiscordModules.SelectedChannelStore.getChannelId()); - } - invalidateAllChannelCache() { - for (let channelId in this.channelMessages) this.invalidateChannelCache(channelId); - } - invalidateChannelCache(channelId) { - if (!this.channelMessages[channelId]) return; - this.channelMessages[channelId].ready = false; - } - cacheChannelMessages(id, relative) { - // TODO figure out if I can use this to get messages at a certain point - this.tools.fetchMessages({ channelId: id, limit: 50, jump: (relative && { messageId: relative, ML2: true }) || undefined }); - } - /* UNUSED */ - cachenChannelMessagesRelative(channelId, messageId) { - ZeresPluginLibrary.DiscordModules.APIModule.get({ - url: ZeresPluginLibrary.DiscordModules.DiscordConstants.Endpoints.MESSAGES(channelId), - query: { - before: null, - after: null, - limit: 50, - around: messageId - } - }) - .then(res => { - if (res.status != 200) return; - const results = res.body; - const final = results.filter(x => this.cachedMessageRecord.findIndex(m => x.id === m.id) == -1); - this.cachedMessageRecord.push(...final); - }) - .catch(err => { - ZeresPluginLibrary.Logger.stacktrace(this.getName(), `Error caching messages from ${channelId} around ${messageId}`, err); - }); - } - formatMarkup(content, channelId) { - const markup = document.createElement('div'); - - const parsed = this.tools.parse(content, true, channelId ? { channelId: channelId } : {}); - // console.log(parsed); - // error, this render doesn't work with tags - // TODO: this parser and renderer sucks - // this may be causing a severe memory leak over the course of a few hours - ZeresPluginLibrary.DiscordModules.ReactDOM.render(ZeresPluginLibrary.DiscordModules.React.createElement('div', { className: '' }, parsed), markup); - - const hiddenClass = this.classes.hidden; - - const hidden = markup.getElementsByClassName(hiddenClass); - - for (let i = 0; i < hidden.length; i++) { - hidden[i].classList.remove(hiddenClass); - } - const child = markup.firstChild; - let previousTab = this.menu.selectedTab; - let previousOpen = this.menu.open; - const callback = () => { - if (this.menu.open === previousOpen && this.menu.selectedTab === previousTab) return; /* lol ez */ - try { - markup.appendChild(child); - ZeresPluginLibrary.DiscordModules.ReactDOM.unmountComponentAtNode(markup); - } catch (e) { } - ZeresPluginLibrary.DOMTools.observer.unsubscribe(callback); - }; - ZeresPluginLibrary.DOMTools.observer.subscribe(callback, mutation => { - const nodes = Array.from(mutation.removedNodes); - const directMatch = nodes.indexOf(child) > -1; - const parentMatch = nodes.some(parent => parent.contains(child)); - return directMatch || parentMatch; - }); - return child; - } - async showToast(content, options = {}) { - // credits to Zere, copied from Zeres Plugin Library - const { type = '', icon = '', timeout = 3000, onClick = () => { }, onContext = () => { } } = options; - ZeresPluginLibrary.Toasts.ensureContainer(); - const toast = ZeresPluginLibrary['DOMTools'].parseHTML(ZeresPluginLibrary.Toasts.buildToast(content.replace(/&/g, '&').replace(/ { - toast.classList.add('closing'); - sto2 = setTimeout(() => { - toast.remove(); - if (!document.querySelectorAll('.toasts .toast').length) document.querySelector('.toasts').remove(); - }, 300); - }; - const sto = setTimeout(wait, timeout); - const toastClicked = () => { - clearTimeout(sto); - clearTimeout(sto2); - wait(); - }; - toast.addEventListener('auxclick', toastClicked); - toast.addEventListener('click', () => { - toastClicked(); - onClick(); - }); - toast.addEventListener('contextmenu', () => { - toastClicked(); - onContext(); - }); - } - clamp(val, min, max) { - // this is so sad, can we hit Metalloriff? - // his message logger added the func to Math obj and I didn't realize - return Math.max(min, Math.min(val, max)); - } - deleteEditedMessageFromRecord(id, editNum) { - const record = this.messageRecord[id]; - if (!record) return; - - record.edit_history.splice(editNum, 1); - if (!record.edit_history.length) record.edit_history = null; - else return this.saveData(); - - const channelId = record.message.channel_id; - const channelMessages = this.editedMessageRecord[channelId]; - channelMessages.splice( - channelMessages.findIndex(m => m === id), - 1 - ); - if (this.deletedMessageRecord[channelId] && this.deletedMessageRecord[channelId].findIndex(m => m === id) != -1) return this.saveData(); - if (this.purgedMessageRecord[channelId] && this.purgedMessageRecord[channelId].findIndex(m => m === id) != -1) return this.saveData(); - delete this.messageRecord[id]; - this.saveData(); - } - jumpToMessage(channelId, messageId, guildId) { - if (this.menu.open) this.ModalStack.closeModal(this.style.menu); - ZeresPluginLibrary.DiscordModules.NavigationUtils.transitionTo(`/channels/${guildId || '@me'}/${channelId}${messageId ? '/' + messageId : ''}`); - } - isImage(url) { - return /\.(jpe?g|png|gif|bmp)$/i.test(url); - } - cleanupEmbed(embed) { - /* backported code from MLV2 rewrite */ - if (!embed.id) return embed; /* already cleaned */ - const retEmbed = {}; - if (typeof embed.rawTitle === 'string') retEmbed.title = embed.rawTitle; - if (typeof embed.rawDescription === 'string') retEmbed.description = embed.rawDescription; - if (typeof embed.referenceId !== 'undefined') retEmbed.reference_id = embed.referenceId; - if (typeof embed.color === 'string') retEmbed.color = ZeresPluginLibrary.ColorConverter.hex2int(embed.color); - if (typeof embed.type !== 'undefined') retEmbed.type = embed.type; - if (typeof embed.url !== 'undefined') retEmbed.url = embed.url; - if (typeof embed.provider === 'object') retEmbed.provider = { name: embed.provider.name, url: embed.provider.url }; - if (typeof embed.footer === 'object') retEmbed.footer = { text: embed.footer.text, icon_url: embed.footer.iconURL, proxy_icon_url: embed.footer.iconProxyURL }; - if (typeof embed.author === 'object') retEmbed.author = { name: embed.author.name, url: embed.author.url, icon_url: embed.author.iconURL, proxy_icon_url: embed.author.iconProxyURL }; - if (typeof embed.timestamp === 'object' && embed.timestamp._isAMomentObject) retEmbed.timestamp = embed.timestamp.milliseconds(); - if (typeof embed.thumbnail === 'object') { - if (typeof embed.thumbnail.proxyURL === 'string' || (typeof embed.thumbnail.url === 'string' && !embed.thumbnail.url.endsWith('?format=jpeg'))) { - retEmbed.thumbnail = { - url: embed.thumbnail.url, - proxy_url: typeof embed.thumbnail.proxyURL === 'string' ? embed.thumbnail.proxyURL.split('?format')[0] : undefined, - width: embed.thumbnail.width, - height: embed.thumbnail.height - }; - } - } - if (typeof embed.image === 'object') { - retEmbed.image = { - url: embed.image.url, - proxy_url: embed.image.proxyURL, - width: embed.image.width, - height: embed.image.height - }; - } - if (typeof embed.video === 'object') { - retEmbed.video = { - url: embed.video.url, - proxy_url: embed.video.proxyURL, - width: embed.video.width, - height: embed.video.height - }; - } - if (Array.isArray(embed.fields) && embed.fields.length) { - retEmbed.fields = embed.fields.map(e => ({ name: e.rawName, value: e.rawValue, inline: e.inline })); - } - return retEmbed; - } - fixEmbeds(message) { - message.embeds = message.embeds.map(this.cleanupEmbed); - } - isCompact() { - return false; // fix if someone complains, no one has so far so who cares - } - /* ==================================================-|| END HELPERS ||-================================================== */ - /* ==================================================-|| START MISC ||-================================================== */ - addOpenLogsButton() { - if (!this.selectedChannel) return; - const parent = document.querySelector('div[class*="chat-"] div[class*="toolbar-"]'); - if (!parent) return; - const srch = parent.querySelector('div[class*="search-"]'); // you know who you are that think this is my issue - if (!srch) return; - parent.insertBefore(this.channelLogButton, srch); - } - removeOpenLogsButton() { - this.channelLogButton.remove(); - } - showLoggerHelpModal(initial = false) { - return; - this.createModal({ - confirmText: 'OK', - header: 'Logger help', - size: this.createModal.confirmationModal.Sizes.LARGE, - children: [ - ZeresPluginLibrary.ReactTools.createWrappedElement([ - this.parseHTML( - `
- ${initial ? '
As you are a first time user, you must know in order to have a server be logged, you must RIGHT CLICK a server or channel and add it to the whitelist.
Alternatively if this behavior is unwanted, you can always log all unmuted servers and channels by disabling Only log whitelist in logger settings under IGNORES AND OVERRIDES.


' : ''} - Hello! This is the ${this.getName()} help modal! You may at any time open this in plugin settings by clicking the help button, or in the menu by pressing the question mark button and then then Logger help button.
- Menu:

-
- DELETE + LEFT-CLICK:
-
- Clicking on a message, deletes the message
- Clicking on an edit deletes that specific edit
- Clicking on the timestamp deletes all messages in that message group -

- RIGHT-CLICK:
-
- Right-clicking the timestamp opens up options for the entire message group -

-
- Toasts:
-
- Note: Little "notifications" in discord that tell you if a message was edited, deleted, purged etc are called Toasts!

- LEFT-CLICK:
-
- Opens menu with the relevant tab
-

- RIGHT-CLICK:
-
- Jumps to relevant message in the relevant channel -

- MIDDLE-CLICK/SCROLLWHEEL-CLICK:
-
- Only dismisses/closes the Toast. -

-
- Notifications:
-
- Note: They show in the top right corner and are called XenoLib notifications. Can be enabled in Settings > Display Settings, all the way at the bottom.

- LEFT-CLICK:
-
- Opens menu with the relevant tab
-

- RIGHT-CLICK:
-
- Jumps to relevant message in the relevant channel -

-
- Open Logs button (top right next to search):
-
- LEFT-CLICK:
-
- Opens menu
-

- RIGHT-CLICK:
-
- Opens filtered menu that only shows messages from selected channel
-

-
- Whitelist/blacklist, ignores and overrides:
-
- WHITELIST-ONLY:
-
- All servers are ignored unless whitelisted
- Muted channels in whitelisted servers are ignored unless whitelisted or "Ignore muted channels" is disabled
- All channels in whitelisted servers are logged unless blacklisted, or muted and "Ignore muted channels" is enabled -

- DEFAULT:
-
- All servers are logged unless blacklisted or muted and "Ignore muted servers" is enabled
- Muted channels are ignored unless whitelisted or "Ignore muted channels" is disabled
- Muted servers are ignored unless whitelisted or "Ignore muted servers" is disabled
- Whitelisted channels in muted or blacklisted servers are logged
-

- ALL:
-
- Whitelisted channels in blacklisted servers are logged
- Blacklisted channels in whitelisted servers are ignored
- "Always log selected channel" overrides blacklist, whitelist-only mode, NSFW channel ignore, mute
- "Always log DMs" overrides blacklist as well as whitelist-only mode
- Channels marked NSFW and not whitelisted are ignored unless "Ignore NSFW channels" is disabled -

-
- Chat:
-
- RIGHT-CLICK:
-
- Right-clicking an edit (darkened text) allows you to delete that edit, or hide edits
- Right-clicking on a edited or deleted message gives you the option to hide the deleted message or hide or unhide edits, remove the edited or deleted message from log and remove deleted tint which makes the message look like it isn't deleted. -

-
-
` - ) - ]) - ], - red: false - }); - } - showStatsModal() { - const elements = []; - let totalMessages = Object.keys(this.messageRecord).length; - let messageCounts = []; - let spaceUsageMB = 0; - let cachedImageCount = 0; - let cachedImagesUsageMB = 0; - - let mostDeletesChannel = { num: 0, id: '' }; - let mostEditsChannel = { num: 0, id: '' }; - let deleteDataTemp = {}; - let editDataTemp = {}; - - for (const map of [this.deletedMessageRecord, this.editedMessageRecord, this.cachedMessageRecord]) { - let messageCount = 0; - if (!Array.isArray(map)) { - for (const channelId in map) { - if (!deleteDataTemp[channelId]) deleteDataTemp[channelId] = []; - if (!editDataTemp[channelId]) editDataTemp[channelId] = []; - for (const messageId of map[channelId]) { - messageCount++; - const record = this.messageRecord[messageId]; - if (!record) continue; // wtf? - if (record.delete_data && deleteDataTemp[channelId].findIndex(m => m === messageId)) deleteDataTemp[channelId].push(messageId); - if (record.edit_history && editDataTemp[channelId].findIndex(m => m === messageId)) editDataTemp[channelId].push(messageId); - } - } - } - for (const channelId in deleteDataTemp) if (deleteDataTemp[channelId].length > mostDeletesChannel.num) mostDeletesChannel = { num: deleteDataTemp[channelId].length, id: channelId }; - for (const channelId in editDataTemp) if (editDataTemp[channelId].length > mostEditsChannel.num) mostEditsChannel = { num: editDataTemp[channelId].length, id: channelId }; - - messageCounts.push(messageCount); - } - const addLine = (name, value) => { - elements.push(this.parseHTML(`
${name}: ${value}

`)); - }; - addLine('Total messages', totalMessages); - addLine('Deleted message count', messageCounts[0]); - addLine('Edited message count', messageCounts[1]); - addLine('Sent message count', this.cachedMessageRecord.length); - - let channel = this.tools.getChannel(mostDeletesChannel.id); - if (channel) addLine('Most deletes', mostDeletesChannel.num + ' ' + this.getLiteralName(channel.guild_id, channel.id)); - if (channel) addLine('Most edits', mostEditsChannel.num + ' ' + this.getLiteralName(channel.guild_id, channel.id)); - - // addLine('Data file size', (this.nodeModules.fs.statSync(this.pluginDir + '/MessageLoggerV2Data.config.json').size / 1024 / 1024).toFixed(2) + 'MB'); - // addLine('Data file size severity', this.slowSaveModeStep == 0 ? 'OK' : this.slowSaveModeStep == 1 ? 'MILD' : this.slowSaveModeStep == 2 ? 'BAD' : 'EXTREME'); - this.createModal({ - confirmText: 'OK', - header: 'Data stats', - size: ZeresPluginLibrary.Modals.ModalSizes.SMALL, - children: [ZeresPluginLibrary.ReactTools.createWrappedElement(elements)], - red: false - }); - } - _findLastIndex(array, predicate) { - let l = array.length; - while (l--) { - if (predicate(array[l], l, array)) - return l; - } - return -1; - } - /* - how it works: - messages, stripped into IDs and times into var IDs: - [1, 2, 3, 4, 5, 6, 7] - ^ ^ - lowestTime highestTime - deletedMessages, stripped into IDs and times into var savedIDs: - sorted by time, newest to oldest - lowest IDX that is higher than lowestTime, unless channelEnd, then it's 0 - highest IDX that is lower than highestTime, unless channelStart, then it's savedIDs.length - 1 - - savedIDs sliced start lowest IDX, end highest IDX + 1 - appended IDs - sorted by time, oldest to newest - iterated, checked if ID is in messages, if not, fetch from this.messageRecord and splice it in at - specified index - */ - reAddDeletedMessages(messages, deletedMessages, channelStart, channelEnd) { - if (!messages.length || !deletedMessages.length) return; - const DISCORD_EPOCH = 14200704e5; - const IDs = []; - const savedIDs = []; - for (let i = 0, len = messages.length; i < len; i++) { - const { id } = messages[i]; - IDs.push({ id: id, time: (id / 4194304) + DISCORD_EPOCH }); - } - for (let i = 0, len = deletedMessages.length; i < len; i++) { - const id = deletedMessages[i]; - const record = this.messageRecord[id]; - if (!record) continue; - if (!record.delete_data) { - /* SOME WIZARD BROKE THE LOGGER LIKE THIS, WTFFFF */ - this.deleteMessageFromRecords(id); - continue; - } - if (record.delete_data.hidden) continue; - savedIDs.push({ id: id, time: (id / 4194304) + DISCORD_EPOCH }); - } - savedIDs.sort((a, b) => a.time - b.time); - if (!savedIDs.length) return; - const { time: lowestTime } = IDs[IDs.length - 1]; - const [{ time: highestTime }] = IDs; - const lowestIDX = channelEnd ? 0 : savedIDs.findIndex(e => e.time > lowestTime); - if (lowestIDX === -1) return; - const highestIDX = channelStart ? savedIDs.length - 1 : this._findLastIndex(savedIDs, e => e.time < highestTime); - if (highestIDX === -1) return; - const reAddIDs = savedIDs.slice(lowestIDX, highestIDX + 1); - reAddIDs.push(...IDs); - reAddIDs.sort((a, b) => b.time - a.time); - for (let i = 0, len = reAddIDs.length; i < len; i++) { - const { id } = reAddIDs[i]; - if (messages.findIndex((e) => e.id === id) !== -1) continue; - const { message } = this.messageRecord[id]; - messages.splice(i, 0, message); - } - } - getLiteralName(guildId, channelId, useTags = false) { - // TODO, custom channel server failure text - const guild = this.tools.getServer(guildId); - const channel = this.tools.getChannel(channelId); // todo - /* if (typeof guildNameBackup !== 'number' && guild && guildNameBackup) */ if (guildId) { - const channelName = (channel ? channel.name : 'unknown-channel'); - const guildName = (guild ? guild.name : 'unknown-server'); - if (useTags && channel) return `${guildName}, <#${channel.id}>`; - return `${guildName}, #${channelName}`; - } else if (channel && channel.name.length) { - return `group ${channel.name}`; - } else if (channel && channel.type == 3) { - let finalGroupName = ''; - for (let i of channel.recipients) { - const user = this.tools.getUser(i); - if (!user) continue; - if (useTags) finalGroupName += ', <@' + user.id + '>'; - else finalGroupName += ',' + user.username; - } - if (!finalGroupName.length) { - return 'unknown group'; - } else { - finalGroupName = finalGroupName.substr(1); - if (useTags) return `group ${finalGroupName}`; - finalGroupName = finalGroupName.length > 10 ? finalGroupName.substr(0, 10 - 1) + '...' : finalGroupName; - return `group ${finalGroupName}`; - } - } else if (channel && channel.recipients) { - const user = this.tools.getUser(channel.recipients[0]); - if (!user) return 'DMs'; - if (useTags) return `<@${user.id}> DMs`; - return `${user.username} DMs`; - } else { - return 'DMs'; - } - } - saveDeletedMessage(message, targetMessageRecord) { - let result = this.createMiniFormattedData(message); - result.delete_data = {}; - const id = message.id; - const channelId = message.channel_id; - result.delete_data.time = new Date().getTime(); - result.ghost_pinged = result.local_mentioned; // it's simple bruh - if (!Array.isArray(targetMessageRecord[channelId])) targetMessageRecord[channelId] = []; - if (this.messageRecord[id]) { - const record = this.messageRecord[id]; - record.delete_data = result.delete_data; - record.ghost_pinged = result.ghost_pinged; - } else { - this.messageRecord[id] = result; - } - if (this.messageRecord[id].message.attachments) { - const attachments = this.messageRecord[id].message.attachments; - for (let i = 0; i < attachments.length; i++) { - attachments[i].url = attachments[i].proxy_url; // proxy url lasts longer - } - } - if (this.settings.cacheAllImages) this.cacheMessageImages(this.messageRecord[id].message); - targetMessageRecord[channelId].push(id); - } - createButton(label, callback) { - const classes = this.createButton.classes; - const ret = this.parseHTML(``); - if (callback) ret.addEventListener('click', callback); - return ret; - } - createModal(options, image, name) { - const modal = image ? this.createModal.imageModal : this.createModal.confirmationModal; - this.ModalStack.openModal(props => ZeresPluginLibrary.DiscordModules.React.createElement(modal, Object.assign({}, options, props, options.onClose ? { onClose: options.onClose } : {})), { modalKey: name }); - } - getMessageAny(id) { - const record = this.messageRecord[id]; - if (!record) return this.cachedMessageRecord.find(m => m.id == id); - return record.message; - } - cacheImage(url, attachmentIdx, attachmentId, messageId, channelId, attempts = 0) { - this.nodeModules.request({ url: url, encoding: null, headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) discord/1.0.9002 Chrome/83.0.4103.122 Electron/9.3.5 Safari/537.36' } }, (err, res, buffer) => { - try { - if (err || res.statusCode != 200) { - if (res.statusCode == 404 || res.statusCode == 403) return; - attempts++; - if (attempts > 3) return ZeresPluginLibrary.Logger.warn(this.getName(), `Failed to get image ${attachmentId} for caching, error code ${res.statusCode}`); - return setTimeout(() => this.cacheImage(url, attachmentIdx, attachmentId, messageId, channelId, attempts), 1000); - } - const fileExtension = url.match(/\.[0-9a-z]+$/i)[0]; - this.nodeModules.fs.writeFileSync(this.settings.imageCacheDir + `/${attachmentId}${fileExtension}`, buffer, { encoding: null }); - } catch (err) { - console.error('Failed to save image cache', err.message); - } - }); - } - cacheMessageImages(message) { - // don't block it, ugly but works, might rework later - setTimeout(() => { - for (let i = 0; i < message.attachments.length; i++) { - const attachment = message.attachments[i]; - if (!this.isImage(attachment.url)) continue; - this.cacheImage(attachment.url, i, attachment.id, message.id, message.channel_id); - } - }, 0); - } - /* ==================================================-|| END MISC ||-================================================== */ - /* ==================================================-|| START MESSAGE MANAGMENT ||-================================================== */ - deleteMessageFromRecords(id) { - const record = this.messageRecord[id]; - if (!record) { - for (let map of [this.deletedMessageRecord, this.editedMessageRecord, this.purgedMessageRecord]) { - for (let channelId in map) { - const index = map[channelId].findIndex(m => m === id); - if (index == -1) continue; - map[channelId].splice(index, 1); - if (!map[channelId].length) delete map[channelId]; - } - } - return; - } - // console.log('Deleting', record); - const channelId = record.message.channel_id; - for (let map of [this.deletedMessageRecord, this.editedMessageRecord, this.purgedMessageRecord]) { - if (!map[channelId]) continue; - const index = map[channelId].findIndex(m => m === id); - if (index == -1) continue; - map[channelId].splice(index, 1); - if (!map[channelId].length) delete map[channelId]; - } - delete this.messageRecord[id]; - } - handleMessagesCap() { - try { - // TODO: add empty record and infinite loop checking for speed improvements - const extractAllMessageIds = map => { - let ret = []; - for (let channelId in map) { - for (let messageId of map[channelId]) { - ret.push(messageId); - } - } - return ret; - }; - if (this.cachedMessageRecord.length > this.settings.messageCacheCap) this.cachedMessageRecord.splice(0, this.cachedMessageRecord.length - this.settings.messageCacheCap); - let changed = false; - const deleteMessages = map => { - this.sortMessagesByAge(map); - const toDelete = map.length - this.settings.savedMessagesCap; - for (let i = map.length - 1, deleted = 0; i >= 0 && deleted != toDelete; i--, deleted++) { - this.deleteMessageFromRecords(map[i]); - } - changed = true; - }; - const handleInvalidEntries = map => { - for (let channelId in map) { - for (let messageIdIdx = map[channelId].length - 1; messageIdIdx >= 0; messageIdIdx--) { - if (!Array.isArray(map[channelId])) { - delete map[channelId]; - changed = true; - continue; - } - if (!this.messageRecord[map[channelId][messageIdIdx]]) { - map[channelId].splice(messageIdIdx, 1); - changed = true; - } - } - if (!map[channelId].length) { - delete map[channelId]; - changed = true; - } - } - }; - for (let map of [this.deletedMessageRecord, this.editedMessageRecord, this.purgedMessageRecord]) handleInvalidEntries(map); - // I have no idea how to optimize this, HELP! - //const checkIsInRecords = (channelId, messageId) => { - // // for (let map of [this.deletedMessageRecord, this.editedMessageRecord, this.purgedMessageRecord]) if (map[channelId] && map[channelId].indexOf(messageId) !== -1) return true; - // let map = this.deletedMessageRecord[channelId]; - // if (map && map.indexOf(messageId) !== -1) return true; - // map = this.editedMessageRecord[channelId]; - // if (map && map.indexOf(messageId) !== -1) return true; - // map = this.purgedMessageRecord[channelId]; - // if (map && map.indexOf(messageId) !== -1) return true; - // return false; - //}; - - //for (const messageId in this.messageRecord) { - // if (!checkIsInRecords(this.messageRecord[messageId].message.channel_id, messageId)) {/* delete this.messageRecord[messageId]; */ } - //} - let deletedMessages = extractAllMessageIds(this.deletedMessageRecord); - let editedMessages = extractAllMessageIds(this.editedMessageRecord); - let purgedMessages = extractAllMessageIds(this.purgedMessageRecord); - for (let map of [deletedMessages, editedMessages, purgedMessages]) if (map.length > this.settings.savedMessagesCap) deleteMessages(map); - if (changed) this.saveData(); - if (!this.settings.cacheAllImages) return; - if (!this.settings.dontDeleteCachedImages) { - const savedImages = this.nodeModules.fs.readdirSync(this.settings.imageCacheDir); - const msgs = Object.values(this.messageRecord) - .filter(e => e.delete_data) - .map(({ message: { attachments } }) => attachments) - .filter(e => e.length); - for (let img of savedImages) { - const [attId] = img.split('.'); - if (isNaN(attId)) continue; - let found = false; - for (let i = 0, len = msgs.length; i < len; i++) { - if (msgs[i].findIndex(({ id }) => id === attId) !== -1) { - found = true; - break; - } - } - if (found) continue; - this.nodeModules.fs.unlink(`${this.settings.imageCacheDir}/${img}`, e => e && ZeresPluginLibrary.Logger.err(this.getName(), 'Error deleting unreferenced image, what the shit', e.message)); - } - } - // 10 minutes - for (let id in this.editHistoryAntiSpam) if (new Date().getTime() - this.editHistoryAntiSpam[id].times[0] < 10 * 60 * 1000) delete this.editHistoryAntiSpam[id]; - } catch (e) { - ZeresPluginLibrary.Logger.stacktrace(this.getName(), 'Error clearing out data', e); - } - } - /* ==================================================-|| END MESSAGE MANAGMENT ||-================================================== */ - onDispatchEvent(args, callDefault) { - const dispatch = args[0]; - let ret = Promise.resolve(); - - if (!dispatch) return callDefault(...args); - - try { - if (dispatch.type === 'MESSAGE_LOGGER_V2_SELF_TEST') { - clearTimeout(this.selfTestTimeout); - //console.log('Self test OK'); - this.selfTestFailures = 0; - return ret; - } - // if (dispatch.type == 'EXPERIMENT_TRIGGER') return callDefault(...args); - // console.log('INFO: onDispatchEvent -> dispatch', dispatch); - if (dispatch.type === 'CHANNEL_SELECT') { - ret = callDefault(...args); - this.selectedChannel = this.getSelectedTextChannel(); - return ret; - } - - if (dispatch.ML2 && dispatch.type === 'MESSAGE_DELETE') return callDefault(...args); - - if (dispatch.type !== 'MESSAGE_CREATE' && dispatch.type !== 'MESSAGE_DELETE' && dispatch.type !== 'MESSAGE_DELETE_BULK' && dispatch.type !== 'MESSAGE_UPDATE' && dispatch.type !== 'LOAD_MESSAGES_SUCCESS') return callDefault(...args); - - // console.log('INFO: onDispatchEvent -> dispatch', dispatch); - - if (dispatch.message && (dispatch.message.type !== 0 && dispatch.message.type !== 19 && (dispatch.message.type !== 20 || (dispatch.message.flags & 64) === 64))) return callDefault(...args); // anti other shit 1 - - const channel = this.tools.getChannel(dispatch.message ? dispatch.message.channel_id : dispatch.channelId); - if (!channel) return callDefault(...args); - const guild = channel.guild_id ? this.tools.getServer(channel.guild_id) : false; - - let author = dispatch.message && dispatch.message.author ? this.tools.getUser(dispatch.message.author.id) : false; - if (!author) author = ((this.channelMessages[channel.id] || { _map: {} })._map[dispatch.message ? dispatch.message.id : dispatch.id] || {}).author; - if (!author) { - // last ditch attempt - let message = this.getCachedMessage(dispatch.id); - if (message) author = this.tools.getUser(message.author.id); - } - - if (!author && !(dispatch.type == 'LOAD_MESSAGES_SUCCESS' || dispatch.type == 'MESSAGE_DELETE_BULK')) return callDefault(...args); - - const isLocalUser = author && author.id === this.localUser.id; - - if (author && author.bot && this.settings.ignoreBots) return callDefault(...args); - if (author && isLocalUser && this.settings.ignoreSelf) return callDefault(...args); - if (author && this.settings.ignoreBlockedUsers && this.tools.isBlocked(author.id) && !isLocalUser) return callDefault(...args); - if (author && author.avatar === 'clyde') return callDefault(...args); - - if (this.settings.ignoreLocalEdits && dispatch.type === 'MESSAGE_UPDATE' && isLocalUser) return callDefault(...args); - if (this.settings.ignoreLocalDeletes && dispatch.type === 'MESSAGE_DELETE' && isLocalUser && this.localDeletes.findIndex(m => m === dispatch.id) !== -1) return callDefault(...args); - - let guildIsMutedReturn = false; - let channelIgnoreReturn = false; - - const isInWhitelist = id => this.settings.whitelist.findIndex(m => m === id) != -1; - const isInBlacklist = id => this.settings.blacklist.findIndex(m => m === id) != -1; - const guildWhitelisted = guild && isInWhitelist(guild.id); - const channelWhitelisted = isInWhitelist(channel.id); - - const guildBlacklisted = guild && isInBlacklist(guild.id); - const channelBlacklisted = isInBlacklist(channel.id); - - let doReturn = false; - - if (guild) { - guildIsMutedReturn = this.settings.ignoreMutedGuilds && this.muteModule.isMuted(guild.id); - channelIgnoreReturn = (this.settings.ignoreNSFW && channel.nsfw && !channelWhitelisted) || (this.settings.ignoreMutedChannels && (this.muteModule.isChannelMuted(guild.id, channel.id) || (channel.parent_id && this.muteModule.isChannelMuted(guild.id, channel.parent_id)))); - } - - if (!((this.settings.alwaysLogSelected && this.selectedChannel && this.selectedChannel.id == channel.id) || (this.settings.alwaysLogDM && !guild))) { - if (guildBlacklisted) { - if (!channelWhitelisted) doReturn = true; // not whitelisted - } else if (guildWhitelisted) { - if (channelBlacklisted) doReturn = true; // channel blacklisted - if (channelIgnoreReturn && !channelWhitelisted) doReturn = true; - } else { - if (this.settings.onlyLogWhitelist) { - if (!channelWhitelisted) doReturn = true; // guild not in either list, channel not whitelisted - } else { - if (channelBlacklisted) doReturn = true; // channel blacklisted - if (channelIgnoreReturn || guildIsMutedReturn) { - if (!channelWhitelisted) doReturn = true; - } - } - } - } - - if (doReturn && this.settings.alwaysLogGhostPings) { - if (dispatch.type === 'MESSAGE_DELETE') { - const deleted = (this.tempEditedMessageRecord[dispatch.id] && this.tempEditedMessageRecord[dispatch.id].message) || this.getCachedMessage(dispatch.id, dispatch.channelId); - if (!deleted || (deleted.type !== 0 && deleted.type !== 19 && deleted.type !== 20)) return callDefault(...args); // nothing we can do past this point.. - if (!this.tools.isMentioned(deleted, this.localUser.id)) return callDefault(...args); - const record = this.messageRecord[dispatch.id]; - if ((!this.selectedChannel || this.selectedChannel.id != channel.id) && (guild ? this.settings.toastToggles.ghostPings : this.settings.toastTogglesDMs.ghostPings) && (!record || !record.ghost_pinged)) { - XenoLib.Notifications.warning(`You got ghost pinged in ${this.getLiteralName(channel.guild_id, channel.id, true)}`, { timeout: 0, onClick: () => this.openWindow('ghostpings'), onContext: () => this.jumpToMessage(dispatch.channelId, dispatch.id, guild && guild.id), channelId: channel.id }); - if (!this.settings.useNotificationsInstead) { - this.showToast(`You got ghost pinged in ${this.getLiteralName(channel.guild_id, channel.id)}`, { - type: 'warning', - onClick: () => this.openWindow('ghostpings'), - onContext: () => this.jumpToMessage(dispatch.channelId, dispatch.id, guild && guild.id), - timeout: 4500 - }); - } - } - this.saveDeletedMessage(deleted, this.deletedMessageRecord); - this.saveData(); - if (XenoLib.DiscordAPI.channelId.id === dispatch.channelId) this.dispatcher.dispatch({ type: 'MLV2_FORCE_UPDATE_MESSAGE', id: dispatch.id }); - } else if (dispatch.type === 'MESSAGE_UPDATE') { - if (!dispatch.message.edited_timestamp) { - if (dispatch.message.embeds) { - let last = this.getCachedMessage(dispatch.message.id); - if (last) last.embeds = dispatch.message.embeds.map(this.cleanupEmbed); - } - return callDefault(...args); - } - let isSaved = this.getEditedMessage(dispatch.message.id, channel.id); - const last = this.getCachedMessage(dispatch.message.id, channel.id); - const lastEditedSaved = isSaved || this.tempEditedMessageRecord[dispatch.message.id]; - // if we have lastEdited then we can still continue as we have all the data we need to process it. - if (!last && !lastEditedSaved) return callDefault(...args); // nothing we can do past this point.. - - if (isSaved && !lastEditedSaved.local_mentioned) { - lastEditedSaved.message.content = dispatch.message.content; // don't save history, just the value so we don't confuse the user - return callDefault(...args); - } - - let ghostPinged = false; - if (lastEditedSaved) { - // last is not needed, we have all the data already saved - if (lastEditedSaved.message.content === dispatch.message.content) return callDefault(...args); // we don't care about that - lastEditedSaved.edit_history.push({ - content: lastEditedSaved.message.content, - time: new Date().getTime() - }); - lastEditedSaved.message.content = dispatch.message.content; - ghostPinged = !lastEditedSaved.ghost_pinged && lastEditedSaved.local_mentioned && !this.tools.isMentioned(dispatch.message, this.localUser.id); - } else { - if (last.content === dispatch.message.content) return callDefault(...args); // we don't care about that - let data = this.createMiniFormattedData(last); - data.edit_history = [ - { - content: last.content, - time: new Date().getTime() - } - ]; - data.message.content = dispatch.message.content; - this.tempEditedMessageRecord[data.message.id] = data; - ghostPinged = this.tools.isMentioned(last, this.localUser.id) && !this.tools.isMentioned(dispatch.message, this.localUser.id); - } - - if (isSaved) this.saveData(); - - if (!ghostPinged) return callDefault(...args); - - if (!isSaved) { - const data = this.tempEditedMessageRecord[dispatch.message.id]; - data.ghost_pinged = true; - this.messageRecord[dispatch.message.id] = data; - if (!this.editedMessageRecord[channel.id]) this.editedMessageRecord[channel.id] = []; - this.editedMessageRecord[channel.id].push(dispatch.message.id); - this.saveData(); - } else { - const lastEdited = this.getEditedMessage(dispatch.message.id, channel.id); - if (!lastEdited) return callDefault(...args); - lastEdited.ghost_pinged = true; - this.saveData(); - } - - if ((!this.selectedChannel || this.selectedChannel.id != channel.id) && (guild ? this.settings.toastToggles.ghostPings : this.settings.toastTogglesDMs.ghostPings)) { - XenoLib.Notifications.warning(`You got ghost pinged in ${this.getLiteralName(channel.guild_id, channel.id, true)}`, { timeout: 0, onClick: () => this.openWindow('ghostpings'), onContext: () => this.jumpToMessage(dispatch.channelId, dispatch.id, guild && guild.id), channelId: channel.id }); - if (!this.settings.useNotificationsInstead) { - this.showToast(`You got ghost pinged in ${this.getLiteralName(channel.guild_id, channel.id)}`, { - type: 'warning', - onClick: () => this.openWindow('ghostpings'), - onContext: () => this.jumpToMessage(dispatch.channelId, dispatch.id, guild && guild.id), - timeout: 4500 - }); - } - } - } else if (dispatch.type == 'MESSAGE_CREATE' && dispatch.message && (dispatch.message.content.length || (dispatch.attachments && dispatch.attachments.length) || (dispatch.embeds && dispatch.embeds.length)) && dispatch.message.state != 'SENDING' && !dispatch.optimistic && (dispatch.message.type === 0 || dispatch.message.type === 19 || dispatch.message.type === 20) && this.tools.isMentioned(dispatch.message, this.localUser.id)) { - if (this.cachedMessageRecord.findIndex(m => m.id === dispatch.message.id) != -1) return callDefault(...args); - this.cachedMessageRecord.push(dispatch.message); - } - } - if (doReturn) return callDefault(...args); - - if (dispatch.type == 'LOAD_MESSAGES_SUCCESS') { - if (!this.settings.restoreDeletedMessages) return callDefault(...args); - if (dispatch.jump && dispatch.jump.ML2) delete dispatch.jump; - const deletedMessages = this.deletedMessageRecord[channel.id]; - const purgedMessages = this.purgedMessageRecord[channel.id]; - try { - const recordIDs = [...(deletedMessages || []), ...(purgedMessages || [])]; - const fetchUser = id => this.tools.getUser(id) || dispatch.messages.find(e => e.author.id === id) - for (let i = 0, len = recordIDs.length; i < len; i++) { - const id = recordIDs[i]; - if (!this.messageRecord[id]) continue; - const { message } = this.messageRecord[id]; - for (let j = 0, len2 = message.mentions.length; j < len2; j++) { - const user = message.mentions[j]; - const cachedUser = fetchUser(user.id || user); - if (cachedUser) message.mentions[j] = this.cleanupUserObject(cachedUser); - } - const author = fetchUser(message.author.id); - if (!author) continue; - message.author = this.cleanupUserObject(author); - } - } catch { } - if ((!deletedMessages && !purgedMessages) || (!this.settings.showPurgedMessages && !this.settings.showDeletedMessages)) return callDefault(...args); - if (this.settings.showDeletedMessages && deletedMessages) this.reAddDeletedMessages(dispatch.messages, deletedMessages, !dispatch.hasMoreAfter && !dispatch.isBefore, !dispatch.hasMoreBefore && !dispatch.isAfter); - if (this.settings.showPurgedMessages && purgedMessages) this.reAddDeletedMessages(dispatch.messages, purgedMessages, !dispatch.hasMoreAfter && !dispatch.isBefore, !dispatch.hasMoreBefore && !dispatch.isAfter); - return callDefault(...args); - } - - const notificationsBlacklisted = this.settings.notificationBlacklist.indexOf(channel.id) !== -1 || (guild && this.settings.notificationBlacklist.indexOf(guild.id) !== -1); - - if (dispatch.type == 'MESSAGE_DELETE') { - const deleted = this.getCachedMessage(dispatch.id, dispatch.channelId); - - if (this.settings.aggresiveMessageCaching) { - const channelMessages = this.channelMessages[channel.id]; - if (!channelMessages || !channelMessages.ready) this.cacheChannelMessages(channel.id); - } - - if (!deleted) return callDefault(...args); // nothing we can do past this point.. - - if (this.deletedMessageRecord[channel.id] && this.deletedMessageRecord[channel.id].findIndex(m => m === deleted.id) != -1) { - if (!this.settings.showDeletedMessages) ret = callDefault(...args); - return ret; - } - - if (deleted.type !== 0 && deleted.type !== 19 && (deleted.type !== 20 || (deleted.flags & 64) === 64)) return callDefault(...args); - - if (this.settings.showDeletedCount) { - if (!this.deletedChatMessagesCount[channel.id]) this.deletedChatMessagesCount[channel.id] = 0; - if (!this.selectedChannel || this.selectedChannel.id != channel.id) this.deletedChatMessagesCount[channel.id]++; - } - if (!notificationsBlacklisted) { - if (guild ? this.settings.toastToggles.deleted && ((isLocalUser && !this.settings.toastToggles.disableToastsForLocal) || !isLocalUser) : this.settings.toastTogglesDMs.deleted && !isLocalUser) { - if (this.settings.useNotificationsInstead) { - XenoLib.Notifications.danger(`Message deleted from ${this.getLiteralName(channel.guild_id, channel.id, true)}`, { - onClick: () => this.openWindow('deleted'), - onContext: () => this.jumpToMessage(dispatch.channelId, dispatch.id, guild && guild.id), - timeout: 4500 - }); - } else { - this.showToast(`Message deleted from ${this.getLiteralName(channel.guild_id, channel.id)}`, { - type: 'error', - onClick: () => this.openWindow('deleted'), - onContext: () => this.jumpToMessage(dispatch.channelId, dispatch.id, guild && guild.id), - timeout: 4500 - }); - } - } - } - - const record = this.messageRecord[dispatch.id]; - - if ((!this.selectedChannel || this.selectedChannel.id != channel.id) && (guild ? this.settings.toastToggles.ghostPings : this.settings.toastTogglesDMs.ghostPings) && (!record || !record.ghost_pinged) && this.tools.isMentioned(deleted, this.localUser.id)) { - XenoLib.Notifications.warning(`You got ghost pinged in ${this.getLiteralName(channel.guild_id, channel.id, true)}`, { timeout: 0, onClick: () => this.openWindow('ghostpings'), onContext: () => this.jumpToMessage(dispatch.channelId, dispatch.id, guild && guild.id), channelId: dispatch.channelId }); - if (!this.settings.useNotificationsInstead) { - this.showToast(`You got ghost pinged in ${this.getLiteralName(channel.guild_id, channel.id)}`, { - type: 'warning', - onClick: () => this.openWindow('ghostpings'), - onContext: () => this.jumpToMessage(dispatch.channelId, dispatch.id, guild && guild.id), - timeout: 4500 - }); - } - } - - this.saveDeletedMessage(deleted, this.deletedMessageRecord); - // if (this.settings.cacheAllImages) this.cacheImages(deleted); - if (!this.settings.showDeletedMessages) ret = callDefault(...args); - else if (XenoLib.DiscordAPI.channelId === dispatch.channelId) this.dispatcher.dispatch({ type: 'MLV2_FORCE_UPDATE_MESSAGE', id: dispatch.id }); - this.saveData(); - } else if (dispatch.type == 'MESSAGE_DELETE_BULK') { - if (this.settings.showDeletedCount) { - if (!this.deletedChatMessagesCount[channel.id]) this.deletedChatMessagesCount[channel.id] = 0; - if (!this.selectedChannel || this.selectedChannel.id != channel.id) this.deletedChatMessagesCount[channel.id] += dispatch.ids.length; - } - - let failedMessage = false; - - for (let i = 0; i < dispatch.ids.length; i++) { - const purged = this.getCachedMessage(dispatch.ids[i], channel.id); - if (!purged) { - failedMessage = true; - continue; - } - this.saveDeletedMessage(purged, this.purgedMessageRecord); - if (XenoLib.DiscordAPI.channelId === dispatch.channelId) this.dispatcher.dispatch({ type: 'MLV2_FORCE_UPDATE_MESSAGE', id: purged.id }); - } - - if (failedMessage && this.aggresiveMessageCaching) - // forcefully cache the channel in case there are active convos there - this.cacheChannelMessages(channel.id); - else if (this.settings.aggresiveMessageCaching) { - const channelMessages = this.channelMessages[channel.id]; - if (!channelMessages || !channelMessages.ready) this.cacheChannelMessages(channel.id); - } - if (!notificationsBlacklisted) { - if (guild ? this.settings.toastToggles.deleted : this.settings.toastTogglesDMs.deleted) { - if (this.settings.useNotificationsInstead) { - XenoLib.Notifications.danger(`${dispatch.ids.length} messages bulk deleted from ${this.getLiteralName(channel.guild_id, channel.id, true)}`, { - onClick: () => this.openWindow('purged'), - onContext: () => this.jumpToMessage(channel.id, undefined, guild && guild.id), - timeout: 4500 - }); - } else { - this.showToast(`${dispatch.ids.length} messages bulk deleted from ${this.getLiteralName(channel.guild_id, channel.id)}`, { - type: 'error', - onClick: () => this.openWindow('purged'), - onContext: () => this.jumpToMessage(channel.id, undefined, guild && guild.id), - timeout: 4500 - }); - } - } - } - if (!this.settings.showPurgedMessages) ret = callDefault(...args); - this.saveData(); - } else if (dispatch.type == 'MESSAGE_UPDATE') { - if (!dispatch.message.edited_timestamp) { - if (dispatch.message.embeds) { - let last = this.getCachedMessage(dispatch.message.id); - if (last) last.embeds = dispatch.message.embeds.map(this.cleanupEmbed); - } - return callDefault(...args); - } - - if (this.settings.showEditedCount) { - if (!this.editedChatMessagesCount[channel.id]) this.editedChatMessagesCount[channel.id] = 0; - if (!this.selectedChannel || this.selectedChannel.id != channel.id) this.editedChatMessagesCount[channel.id]++; - } - - if (this.settings.aggresiveMessageCaching) { - const channelMessages = this.channelMessages[channel.id]; - if (!channelMessages || !channelMessages.ready) this.cacheChannelMessages(channel.id); - } - - const last = this.getCachedMessage(dispatch.message.id, channel.id); - const lastEditedSaved = this.getEditedMessage(dispatch.message.id, channel.id); - - // if we have lastEdited then we can still continue as we have all the data we need to process it. - if (!last && !lastEditedSaved) return callDefault(...args); // nothing we can do past this point.. - let ghostPinged = false; - if (lastEditedSaved) { - // last is not needed, we have all the data already saved - // console.log(lastEditedSaved.message); - // console.log(dispatch.message); - if (lastEditedSaved.message.content === dispatch.message.content) { - return callDefault(...args); // we don't care about that - } - lastEditedSaved.edit_history.push({ - content: lastEditedSaved.message.content, - time: new Date().getTime() - }); - lastEditedSaved.message.content = dispatch.message.content; - ghostPinged = !lastEditedSaved.ghost_pinged && lastEditedSaved.local_mentioned && !this.tools.isMentioned(dispatch.message, this.localUser.id); - if (ghostPinged) lastEditedSaved.ghost_pinged = true; - } else { - if (last.content === dispatch.message.content) { - return callDefault(...args); // we don't care about that - } - let data = this.createMiniFormattedData(last); - data.edit_history = [ - { - content: last.content, - time: new Date().getTime() - } - ]; - ghostPinged = this.tools.isMentioned(last, this.localUser.id) && !this.tools.isMentioned(dispatch.message, this.localUser.id); - data.message.content = dispatch.message.content; - if (ghostPinged) data.ghost_pinged = true; - this.messageRecord[data.message.id] = data; - if (!this.editedMessageRecord[channel.id]) this.editedMessageRecord[channel.id] = []; - this.editedMessageRecord[channel.id].push(data.message.id); - } - if (!notificationsBlacklisted) { - if (guild ? this.settings.toastToggles.edited && ((isLocalUser && !this.settings.toastToggles.disableToastsForLocal) || !isLocalUser) : this.settings.toastTogglesDMs.edited && !isLocalUser) { - if (!this.settings.blockSpamEdit) { - if (!this.editHistoryAntiSpam[author.id]) { - this.editHistoryAntiSpam[author.id] = { - blocked: false, - times: [new Date().getTime()] - }; - } else { - this.editHistoryAntiSpam[author.id].times.push(new Date().getTime()); - } - if (this.editHistoryAntiSpam[author.id].times.length > 10) this.editHistoryAntiSpam[author.id].times.shift(); - if (this.editHistoryAntiSpam[author.id].times.length === 10 && new Date().getTime() - this.editHistoryAntiSpam[author.id].times[0] < 60 * 1000) { - if (!this.editHistoryAntiSpam[author.id].blocked) { - if (this.settings.useNotificationsInstead) { - XenoLib.Notifications.warning(`Edit notifications from <@${author.id}> have been temporarily blocked for 1 minute.`, { - timeout: 7500, - channelId: channel.id - }); - } else { - this.showToast(`Edit notifications from ${author.username} have been temporarily blocked for 1 minute.`, { - type: 'warning', - timeout: 7500 - }); - } - this.editHistoryAntiSpam[author.id].blocked = true; - } - } else if (this.editHistoryAntiSpam[author.id].blocked) { - this.editHistoryAntiSpam[author.id].blocked = false; - this.editHistoryAntiSpam[author.id].times = []; - } - } - if (this.settings.blockSpamEdit || !this.editHistoryAntiSpam[author.id].blocked) { - if (this.settings.useNotificationsInstead) { - XenoLib.Notifications.info(`Message edited in ${this.getLiteralName(channel.guild_id, channel.id, true)}`, { - onClick: () => this.openWindow('edited'), - onContext: () => this.jumpToMessage(channel.id, dispatch.message.id, guild && guild.id), - timeout: 4500 - }); - } else { - this.showToast(`Message edited in ${this.getLiteralName(channel.guild_id, channel.id)}`, { - type: 'info', - onClick: () => this.openWindow('edited'), - onContext: () => this.jumpToMessage(channel.id, dispatch.message.id, guild && guild.id), - timeout: 4500 - }); - } - } - } - } - if ((!this.selectedChannel || this.selectedChannel.id != channel.id) && (guild ? this.settings.toastToggles.ghostPings : this.settings.toastTogglesDMs.ghostPings) && ghostPinged) { - XenoLib.Notifications.warning(`You got ghost pinged in ${this.getLiteralName(channel.guild_id, channel.id, true)}`, { timeout: 0, onClick: () => this.openWindow('ghostpings'), onContext: () => this.jumpToMessage(dispatch.channelId, dispatch.id, guild && guild.id), channelId: dispatch.channelId }); - if (!this.settings.useNotificationsInstead) { - this.showToast(`You got ghost pinged in ${this.getLiteralName(channel.guild_id, channel.id)}`, { - type: 'warning', - onClick: () => this.openWindow('ghostpings'), - onContext: () => this.jumpToMessage(dispatch.channelId, dispatch.id, guild && guild.id), - timeout: 4500 - }); - } - } - this.saveData(); - return callDefault(...args); - } else if (dispatch.type == 'MESSAGE_CREATE' && dispatch.message && (dispatch.message.content.length || (dispatch.attachments && dispatch.attachments.length) || (dispatch.embeds && dispatch.embeds.length)) && dispatch.message.state != 'SENDING' && !dispatch.optimistic && (dispatch.message.type === 0 || dispatch.message.type === 19 || dispatch.message.type === 20)) { - if (this.cachedMessageRecord.findIndex(m => m.id === dispatch.message.id) != -1) return callDefault(...args); - this.cachedMessageRecord.push(dispatch.message); - - /* if (this.menu.open && this.menu.selectedTab == 'sent') this.refilterMessages(); */ - - if (this.settings.aggresiveMessageCaching) { - const channelMessages = this.channelMessages[channel.id]; - if (!channelMessages || !channelMessages.ready) this.cacheChannelMessages(channel.id); - } - if (!notificationsBlacklisted) { - if ((guild ? this.settings.toastToggles.sent : this.settings.toastTogglesDMs.sent) && (!this.selectedChannel || this.selectedChannel.id != channel.id)) { - if (this.settings.useNotificationsInstead) { - XenoLib.Notifications.info(`Message sent in ${this.getLiteralName(channel.guild_id, channel.id, true)}`, { onClick: () => this.openWindow('sent'), onContext: () => this.jumpToMessage(channel.id, dispatch.message.id, guild && guild.id), timeout: 4500 }); - } else { - this.showToast(`Message sent in ${this.getLiteralName(channel.guild_id, channel.id)}`, { type: 'info', onClick: () => this.openWindow('sent'), onContext: () => this.jumpToMessage(channel.id, dispatch.message.id, guild && guild.id), timeout: 4500 }); - } - } - } - return callDefault(...args); - } else return callDefault(...args); - } catch (err) { - ZeresPluginLibrary.Logger.stacktrace(this.getName(), 'Error in onDispatchEvent', err); - } - return ret; - } - /* ==================================================-|| START MENU ||-================================================== */ - processUserRequestQueue() { - return; - if (!this.processUserRequestQueue.queueIntervalTime) this.processUserRequestQueue.queueIntervalTime = 500; - if (this.menu.queueInterval) return; - const messageDataManager = () => { - return; - if (!this.menu.userRequestQueue.length) { - clearInterval(this.menu.queueInterval); - this.menu.queueInterval = 0; - return; - } - const data = this.menu.userRequestQueue.shift(); - this.tools - .getUserAsync(data.id) - .then(res => { - for (let ss of data.success) ss(res); - }) - .catch(reason => { - if (reason.status == 429 && typeof reason.body.retry_after === 'number') { - clearInterval(this.menu.queueInterval); - this.menu.queueInterval = 0; - this.processUserRequestQueue.queueIntervalTime += 50; - setTimeout(messageDataManager, reason.body.retry_after); - ZeresPluginLibrary.Logger.warn(this.getName(), 'Rate limited, retrying in', reason.body.retry_after, 'ms'); - this.menu.userRequestQueue.push(data); - return; - } - ZeresPluginLibrary.Logger.warn(this.getName(), `Failed to get info for ${data.username}, reason:`, reason); - for (let ff of data.fail) ff(); - }); - }; - this.menu.queueInterval = setInterval(messageDataManager, this.processUserRequestQueue.queueIntervalTime); - } - async patchMessages() { - const Tooltip = ZeresPluginLibrary.WebpackModules.getByString('shouldShowTooltip', 'handleMouseEnter'); - const dateFormat = ZeresPluginLibrary.WebpackModules.getModule(e => typeof e === 'function' && e?.toString()?.includes('sameDay'), { searchExports: true }); - const i18n = ZeresPluginLibrary.WebpackModules.find(e => e.Messages && e.Messages.HOME); - /* suck it you retarded asshole devilfuck */ - const SuffixEdited = ZeresPluginLibrary.DiscordModules.React.memo(e => ZeresPluginLibrary.DiscordModules.React.createElement(Tooltip, { text: e.timestamp ? dateFormat(e.timestamp, 'LLLL') : null }, tt => ZeresPluginLibrary.DiscordModules.React.createElement('time', Object.assign({ dateTime: e.timestamp.toISOString(), className: this.multiClasses.edited, role: 'note' }, tt), `(${i18n.Messages.MESSAGE_EDITED})`))); - SuffixEdited.displayName = 'SuffixEdited'; - const parseContent = (() => { - const parse = ZeresPluginLibrary.WebpackModules.getModule(e => typeof e === 'function' && e?.toString()?.includes('customRenderedContent') && e?.toString()?.includes('renderMediaEmbeds'), { searchExports: true }); - if (parse) { - return function parseContent() { - const ReactDispatcher = ZeresPluginLibrary.DiscordModules.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentDispatcher.current; - const oUseMemo = ReactDispatcher.useMemo; - ReactDispatcher.useMemo = memo => memo(); - try { - return parse(...arguments); - } finally { - ReactDispatcher.useMemo = oUseMemo; - } - return {}; - } - } - return null; - })(); - const MessageContent = ZeresPluginLibrary.WebpackModules.getModule(e => e?.type?.toString()?.includes('Messages.MESSAGE_EDITED')); - const MemoMessage = await (async () => { - var el = document.querySelector('.messageListItem-ZZ7v6g') || (await new Promise(res => { - var sub = ZeresPluginLibrary.DOMTools.observer.subscribeToQuerySelector(() => { - ZeresPluginLibrary.DOMTools.observer.unsubscribe(sub); - res(document.querySelector('.messageListItem-ZZ7v6g')); - }, '.messageListItem-ZZ7v6g', null, true) - })); - return ZeresPluginLibrary.Utilities.findInTree(ZeresPluginLibrary.ReactTools.getReactInstance(el), e => ((typeof e?.memoizedProps?.isHighlight) === 'boolean'), { walkable: ['return'] })?.elementType - })() - if (!MessageContent || !MemoMessage) return XenoLib.Notifications.error('Failed to patch message components, edit history and deleted tint will not show!', { timeout: 0 }); - this.unpatches.push( - this.Patcher.after(MessageContent, 'type', (_, [props], ret) => { - const forceUpdate = ZeresPluginLibrary.DiscordModules.React.useState()[1]; - ZeresPluginLibrary.DiscordModules.React.useEffect( - () => { - function callback(e) { - if (!e || !e.id || e.id === props.message.id) { - forceUpdate({}); - } - } - this.dispatcher.subscribe('MLV2_FORCE_UPDATE_MESSAGE_CONTENT', callback); - return () => { - this.dispatcher.unsubscribe('MLV2_FORCE_UPDATE_MESSAGE_CONTENT', callback); - }; - }, - [props.message.id, forceUpdate] - ); - if (!this.settings.showEditedMessages || (typeof props.className === 'string' && ~props.className.indexOf('repliedTextContent'))) return; - if (!this.editedMessageRecord[props.message.channel_id] || this.editedMessageRecord[props.message.channel_id].indexOf(props.message.id) === -1) return; - const record = this.messageRecord[props.message.id]; - if (!record || record.edits_hidden || !Array.isArray(ret.props.children)) return; - const createEditedMessage = (edit, editNum, isSingular, noSuffix) => - ZeresPluginLibrary.DiscordModules.React.createElement( - XenoLib.ReactComponents.ErrorBoundary, - { label: 'Edit history' }, - ZeresPluginLibrary.DiscordModules.React.createElement( - Tooltip, - { - text: !!record.delete_data ? null : 'Edited: ' + this.createTimeStamp(edit.time), - position: 'left', - hideOnClick: true - }, - _ => - ZeresPluginLibrary.DiscordModules.React.createElement( - 'div', - { - ..._, - className: XenoLib.joinClassNames({ [this.style.editedCompact]: props.compact && !isSingular, [this.style.edited]: !isSingular }), - editNum - }, - parseContent({ channel_id: props.message.channel_id, mentionChannels: props.message.mentionChannels, content: edit.content, embeds: [], isCommandType: () => false, hasFlag: () => false }, {}).content, - noSuffix - ? null - : ZeresPluginLibrary.DiscordModules.React.createElement(SuffixEdited, { - timestamp: this.tools.createMomentObject(edit.time) - }) - ) - ) - ); - ret.props.className = XenoLib.joinClassNames(ret.props.className, this.style.edited); - const modifier = this.editModifiers[props.message.id]; - if (modifier) { - ret.props.children = [createEditedMessage(record.edit_history[modifier.editNum], modifier.editNum, true, modifier.noSuffix)]; - return; - } - const oContent = Array.isArray(ret.props.children[0]) ? ret.props.children[0] : ret.props.children[1]; - const edits = []; - let i = 0; - let max = record.edit_history.length; - if (this.settings.maxShownEdits) { - if (record.edit_history.length > this.settings.maxShownEdits) { - if (this.settings.hideNewerEditsFirst) { - max = this.settings.maxShownEdits; - } else { - i = record.edit_history.length - this.settings.maxShownEdits; - } - } - } - for (; i < max; i++) { - const edit = record.edit_history[i]; - if (!edit) continue; - let editNum = i; - edits.push(createEditedMessage(edit, editNum)); - } - ret.props.children = [edits, oContent]; - }) - ); - - const messageClass = XenoLib.getSingleClass('ephemeral message'); - const _self = this; - function Message(props, ...whatever) { - try { - const ret = props.__MLV2_type(props, ...whatever); - if (!props.__MLV2_deleteTime) return ret; - const oRef = ret.props.children.ref; - ret.props.children.ref = e => { - if (e && !e.__tooltip) { - // later - new ZeresPluginLibrary.Tooltip(e, 'Deleted: ' + _self.tools.createMomentObject(props.__MLV2_deleteTime).format('LLLL'), { side: 'left' }); - e.__tooltip = true; - } - if (typeof oRef === 'function') return oRef(e); - else if (XenoLib._.isObject(oRef)) oRef.current = e; - }; - return ret; - } catch (err) {} - return null; - } - this.unpatches.push( - this.Patcher.after(MemoMessage, 'type', (_, [props], ret) => { - const forceUpdate = ZeresPluginLibrary.DiscordModules.React.useState()[1]; - ZeresPluginLibrary.DiscordModules.React.useEffect( - () => { - function callback(e) { - if (!e || !e.id || e.id === props.message.id) forceUpdate({}); - } - this.dispatcher.subscribe('MLV2_FORCE_UPDATE_MESSAGE', callback); - return () => { - this.dispatcher.unsubscribe('MLV2_FORCE_UPDATE_MESSAGE', callback); - }; - }, - [props.message.id, forceUpdate] - ); - const record = this.messageRecord[props.message.id]; - if (!record || !record.delete_data) return; - if (this.noTintIds.indexOf(props.message.id) !== -1) return; - const message = ZeresPluginLibrary.Utilities.findInReactTree(ret, e => e && typeof e?.props?.className === 'string' && ~e?.props?.className?.indexOf(messageClass)); - if (!message) return; - message.props.className += ' ' + (this.settings.useAlternativeDeletedStyle ? this.style.deletedAlt : this.style.deleted); - message.props.__MLV2_deleteTime = record.delete_data.time; - message.props.__MLV2_type = message.type; - message.type = Message; - }) - ); - this.forceReloadMessages(); - } - forceReloadMessages() { - const instance = ZeresPluginLibrary.Utilities.findInTree(ZeresPluginLibrary.ReactTools.getReactInstance(document.querySelector('.chatContent-3KubbW')), e => ((typeof e?.memoizedProps?.showQuarantinedUserBanner) === 'boolean'), { walkable: ['return'] })?.stateNode; - if (!instance) return; - const unpatch = this.Patcher.after(instance, 'render', (_this, _, ret) => { - unpatch(); - if (!ret) return; - ret.key = Math.random().toString(36).substring(2, 10).toUpperCase(); - ret.ref = () => _this.forceUpdate(); - }); - instance.forceUpdate(); - } - patchModal() { - return; - // REQUIRED not anymore I guess lol - try { - const confirmModal = ZeresPluginLibrary.WebpackModules.getByDisplayName('ConfirmModal'); - this.createModal.confirmationModal = props => { - try { - const ret = confirmModal(props); - if (props.size) ret.props.size = props.size; - - if (props.onCancel) { - const cancelButton = ZeresPluginLibrary.Utilities.findInReactTree(ret, e => e && e.type === XenoLib.ReactComponents.Button && e.props && e.props.look); - if (cancelButton) cancelButton.props.onClick = props.onCancel; - } - return ret; - } catch (err) { - if (props.onCancel) props.onCancel(); - else props.onClose(); - return null; - } - }; - this.createModal.confirmationModal.Sizes = ZeresPluginLibrary.WebpackModules.getByProps('ModalSize').ModalSize; - } catch { } - this.ModalStack = ZeresPluginLibrary.WebpackModules.getByProps('openModal', 'hasModalOpen'); - this._modalsApiUnsubcribe = (this.ModalStack.modalsApi || this.ModalStack.useModalsStore).subscribe(_ => { - if (this.menu.open && !this.ModalStack.hasModalOpen(this.style.menu)) { - this.menu.filter = ''; - this.menu.open = false; - this.menu.shownMessages = -1; - if (this.menu.messages) this.menu.messages.length = 0; - } - }); - /* - this.createModal.confirmationModal = class ConfirmationModal extends ZeresPluginLibrary.DiscordModules.ConfirmationModal { - constructor(props) { - super(props); - this._handleSubmit = this.handleSubmit.bind(this); - this._handleClose = this.handleClose.bind(this); - this.handleSubmit = this.handleSubmitEx.bind(this); - this.handleClose = this.handleCloseEx.bind(this); - } - handleSubmitEx(e) { - if (this.props.ml2Data) onClearLog(e); - else return this._handleSubmit(e); - } - handleCloseEx(e) { - if (this.props.ml2Data) onChangeOrder(e); - else return this._handleClose(e); - } - render() { - const ret = super.render(); - if (!ret) return ret; - delete ret.props['aria-label']; - return ret; - } - }; - this.unpatches.push( - ZeresPluginLibrary.Patcher.instead(this.getName(), ZeresPluginLibrary.DiscordModules.ConfirmationModal.prototype, 'componentDidMount', (thisObj, args, original) => { - if (thisObj.props.ml2Data) { - if (this.menu.refilterOnMount) { - this.refilterMessages(); - this.menu.refilterOnMount = false; - } - document.getElementById(this.style.menuMessages).parentElement.parentElement.parentElement.scrollTop = this.scrollPosition; - } - return original(...args); - }) - ); -*/ - } - buildMenu(setup) { - const ret = ZeresPluginLibrary.DCM.buildMenu(setup); - return props => ret({ ...props, onClose: _ => { } }); - } - // >>-|| POPULATION ||-<< - createMessageGroup(message, isStart) { - let deleted = false; - let edited = false; - let details = 'Sent in'; - let channel = this.tools.getChannel(message.channel_id); - let timestamp = message.timestamp; - let author = this.tools.getUser(message.author.id); - let noUserInfo = false; - let userInfoBeingRequested = true; - const isBot = message.author.bot; - const record = this.messageRecord[message.id]; - if (record) { - deleted = !!record.delete_data; - edited = !!record.edit_history; - - if (deleted && edited) { - details = 'Edited and deleted from'; - timestamp = record.delete_data.time; - } else if (deleted) { - details = 'Deleted from'; - timestamp = record.delete_data.time; - } else if (edited) { - details = 'Last edit in'; // todo: purged? - if (typeof record.edit_history[record.edit_history.length - 1].time !== 'string') timestamp = record.edit_history[record.edit_history.length - 1].time; - } - } - - details += ` ${this.getLiteralName(message.guild_id || (channel && channel.guild_id), message.channel_id)} `; - - details += `at ${this.createTimeStamp(timestamp, true)}`; - - details = details.replace(/[<>"&]/g, c => ({ "<": "<", ">": ">", "\"": """, "&": "&" })[c]); - const classes = this.createMessageGroup.classes; - const getAvatarOf = user => { - if (!user.avatar) return '/assets/322c936a8c8be1b803cd94861bdfa868.png'; - return `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png?size=128`; - }; - if (!classes.extra) - classes.extra = [ - /* 0 */ XenoLib.joinClassNames(XenoLib.getClass('groupStart message'), XenoLib.getClass('groupStart cozyMessage'), XenoLib.getClass('systemMessage groupStart'), XenoLib.getClass('zalgo wrapper'), XenoLib.getClass('zalgo cozy'), XenoLib.getClass('cozy zalgo')), - /* 1 */ XenoLib.joinClassNames(XenoLib.getClass('groupStart message'), XenoLib.getClass('groupStart cozyMessage'), XenoLib.getClass('zalgo wrapper'), XenoLib.getClass('zalgo cozy'), XenoLib.getClass('cozy zalgo')), - /* 2 */ XenoLib.getClass('username header'), - /* 3 */ XenoLib.joinClassNames(XenoLib.getClass('edited avatar'), XenoLib.getClass('edited avatar clickable')), - /* 4 */ XenoLib.joinClassNames(XenoLib.getClass('timestampTooltip username'), XenoLib.getClass('edited avatar clickable')), - /* 5 */ XenoLib.joinClassNames(XenoLib.getClass('separator timestamp'), XenoLib.getClass('separator timestampInline')), - /* 6 */ XenoLib.joinClassNames(this.multiClasses.markup, XenoLib.getClass('buttonContainer markupRtl')), - /* 7 */ XenoLib.getClass('embedWrapper container'), - /* 8 */ XenoLib.joinClassNames(XenoLib.getClass('zalgo latin24CompactTimeStamp'), XenoLib.getClass('separator timestamp'), XenoLib.getClass('alt timestampVisibleOnHover'), XenoLib.getClass('timestampVisibleOnHover alt')), - /* 9 */ XenoLib.getClass('latin24CompactTimeStamp separator'), - /* 10 */ XenoLib.getSingleClass('timestampTooltip username'), - /* 11 */ XenoLib.getSingleClass('separator timestamp'), - /* 12 */ XenoLib.getClass('zalgo contents') - ]; - - const element = isStart - ? this.parseHTML(`
-
-

${message.author.username.replace(/[<>"]/g, c => ({ "<": "<", ">": ">", "\"": """ })[c])}${(isBot && `BOT`) || ''}${details}

-
-
-
-
`) - : this.parseHTML(`
-
- - - [ - ${this.createTimeStamp(timestamp, -1)} - ] - - -
-
-
-
`); - element.messageId = message.id; - const profImg = element.getElementsByClassName(classes.avatarImgSingle)[0]; - if (profImg) { - profImg.onerror = () => { - profImg.src = '/assets/322c936a8c8be1b803cd94861bdfa868.png'; - }; - const verifyProfilePicture = () => { - if (message.author.avatar != author.avatar && author.avatar) { - profImg.src = getAvatarOf(author); - if (record) { - record.message.author.avatar = author.avatar; - } - } else { - if (record) record.message.author.avatar = null; - } - }; - if (!isBot || true) { - if (!author) { - author = message.author; - if (this.menu.userRequestQueue.findIndex(m => m.id === author.id) == -1) { - this.menu.userRequestQueue.push({ - id: author.id, - username: author.username, - success: [ - res => { - author = $.extend(true, {}, res); - verifyProfilePicture(); - userInfoBeingRequested = false; - } - ], - fail: [ - () => { - noUserInfo = true; - userInfoBeingRequested = false; - } - ] - }); - } else { - const dt = this.menu.userRequestQueue.find(m => m.id === author.id); - dt.success.push(res => { - author = $.extend(true, {}, res); - verifyProfilePicture(); - userInfoBeingRequested = false; - }); - dt.fail.push(() => { - noUserInfo = true; - userInfoBeingRequested = false; - }); - } - } else { - userInfoBeingRequested = false; - verifyProfilePicture(); - } - } - const profIcon = element.getElementsByClassName(classes.avatarImgSingle)[0]; - profIcon.addEventListener('click', () => { - //if (isBot) return this.showToast('User is a bot, this action is not possible on a bot.', { type: 'error', timeout: 5000 }); - if (userInfoBeingRequested) return this.showToast('Internal error', { type: 'info', timeout: 5000 }); - if (noUserInfo) return this.showToast('Could not get user info!', { type: 'error' }); - ZeresPluginLibrary.Popouts.showUserPopout(profIcon, author); - }); - profIcon.addEventListener('contextmenu', e => { - //if (isBot) return this.showToast('User is a bot, this action is not possible on a bot.', { type: 'error', timeout: 5000 }); - if (userInfoBeingRequested) return this.showToast('Internal error', { type: 'info', timeout: 5000 }); - if (noUserInfo) return this.showToast('Could not get user info! You can only delete or copy to clipboard!', { timeout: 5000 }); - ZeresPluginLibrary.WebpackModules.getByProps('openUserContextMenu').openUserContextMenu(e, author, channel || this.menu.randomValidChannel); - }); - const nameLink = element.getElementsByClassName(classes.extra[10])[0]; - nameLink.addEventListener('click', () => { - //if (isBot) return this.showToast('User is a bot, this action is not possible on a bot.', { type: 'error', timeout: 5000 }); - if (userInfoBeingRequested) return this.showToast('Internal error', { type: 'info', timeout: 5000 }); - if (noUserInfo) return this.showToast('Could not get user info!', { type: 'error' }); - ZeresPluginLibrary.Popouts.showUserPopout(nameLink, author); - }); - nameLink.addEventListener('contextmenu', e => { - //if (isBot) return this.showToast('User is a bot, this action is not possible on a bot.', { type: 'error', timeout: 5000 }); - if (userInfoBeingRequested) return this.showToast('Internal error', { type: 'info', timeout: 5000 }); - if (noUserInfo) return this.showToast('Could not get user info! You can only delete or copy to clipboard!', { type: 'error', timeout: 5000 }); - ZeresPluginLibrary.WebpackModules.getByProps('openUserContextMenu').openUserContextMenu(e, author, channel || this.menu.randomValidChannel); - }); - const timestampEl = element.getElementsByClassName(classes.extra[11])[0]; - timestampEl.addEventListener('contextmenu', e => { - const messages = [element]; - let target = element.nextElementSibling; - while (target && target.classList && !target.classList.contains(XenoLib.getSingleClass('systemMessage groupStart'))) { - messages.push(target); - target = target.nextElementSibling; - } - if (!messages.length) return; - const messageIds = []; - for (let i = 0; i < messages.length; i++) if (messages[i] && messages[i].messageId) messageIds.push(messages[i].messageId); - if (!messageIds.length) return; - ZeresPluginLibrary.DCM.openContextMenu( - e, - this.buildMenu([ - { - type: 'group', - items: [ - { - label: 'Copy Formatted Message', - action: () => { - ZeresPluginLibrary.DiscordModules.ContextMenuActions.closeContextMenu(); - let result = ''; - for (let msgid of messageIds) { - const record = this.messageRecord[msgid]; - if (!record) continue; - if (!result.length) result += `> **${record.message.author.username}** | ${this.createTimeStamp(record.message.timestamp, true)}\n`; - result += `> ${record.message.content.replace(/\n/g, '\n> ')}\n`; - } - this.nodeModules.electron.clipboard.writeText(result); - this.showToast('Copied!', { type: 'success' }); - } - }, - { - type: 'item', - label: 'Remove Group From Log', - action: () => { - ZeresPluginLibrary.DiscordModules.ContextMenuActions.closeContextMenu(); - let invalidatedChannelCache = false; - for (let msgid of messageIds) { - const record = this.messageRecord[msgid]; - if (!record) continue; // the hell - if ((record.edit_history && !record.edits_hidden) || (record.delete_data && !record.delete_data.hidden)) this.invalidateChannelCache((invalidatedChannelCache = record.message.channel_id)); - this.deleteMessageFromRecords(msgid); - } - if (invalidatedChannelCache) this.cacheChannelMessages(invalidatedChannelCache); - this.refilterMessages(); // I don't like calling that, maybe figure out a way to animate it collapsing on itself smoothly - this.saveData(); - } - } - ] - } - ]) - ); - }); - timestampEl.addEventListener('click', e => { - if (!this.menu.deleteKeyDown) return; - const messages = [element]; - let target = element.nextElementSibling; - while (target && target.classList && !target.classList.contains(XenoLib.getSingleClass('systemMessage groupStart'))) { - messages.push(target); - target = target.nextElementSibling; - } - if (!messages.length) return; - const messageIds = []; - for (let i = 0; i < messages.length; i++) if (messages[i] && messages[i].messageId) messageIds.push(messages[i].messageId); - if (!messageIds.length) return; - let invalidatedChannelCache = false; - for (let msgid of messageIds) { - const record = this.messageRecord[msgid]; - if (!record) continue; // the hell - if ((record.edit_history && !record.edits_hidden) || (record.delete_data && !record.delete_data.hidden)) this.invalidateChannelCache((invalidatedChannelCache = record.message.channel_id)); - this.deleteMessageFromRecords(msgid); - } - if (invalidatedChannelCache) this.cacheChannelMessages(invalidatedChannelCache); - this.refilterMessages(); // I don't like calling that, maybe figure out a way to animate it collapsing on itself smoothly - this.saveData(); - }); - new ZeresPluginLibrary.Tooltip(timestampEl, 'Sent at ' + this.tools.createMomentObject(message.timestamp).format('LLLL'), { side: 'top' }); - } - const messageContext = e => { - let target = e.target; - if (!target.classList.contains('mention') || (target.tagName == 'DIV' && target.classList.contains(ZeresPluginLibrary.WebpackModules.getByProps('imageError').imageError.split(/ /g)[0]))) { - let isMarkup = false; - let isEdited = false; - let isBadImage = target.tagName == 'DIV' && target.classList == ZeresPluginLibrary.WebpackModules.getByProps('imageError').imageError; - if (!isBadImage) { - while (target && (!target.classList || !(isMarkup = target.classList.contains(this.classes.markup)))) { - if (target.classList && target.classList.contains(this.style.edited)) isEdited = target; - target = target.parentElement; - } - } - - if (isMarkup || isBadImage) { - const messageId = message.id; - const record = this.getSavedMessage(messageId); - if (!record) return; - let editNum = -1; - if (isEdited) editNum = isEdited.edit; - const menuItems = []; - if (channel) { - menuItems.push({ - type: 'item', - label: 'Jump to Message', - action: () => { - ZeresPluginLibrary.DiscordModules.ContextMenuActions.closeContextMenu(); - this.jumpToMessage(message.channel_id, messageId, message.guild_id); - } - }); - } - if (!isBadImage || record.message.content.length) { - menuItems.push( - { - type: 'item', - label: 'Copy Text', - action: () => { - ZeresPluginLibrary.DiscordModules.ContextMenuActions.closeContextMenu(); - this.nodeModules.electron.clipboard.writeText(editNum != -1 ? record.edit_history[editNum].content : record.message.content); - this.showToast('Copied!', { type: 'success' }); - } - }, - { - type: 'item', - label: 'Copy Formatted Message', - action: () => { - ZeresPluginLibrary.DiscordModules.ContextMenuActions.closeContextMenu(); - const content = editNum != -1 ? record.edit_history[editNum].content : record.message.content; - const result = `> **${record.message.author.username}** | ${this.createTimeStamp(record.message.timestamp, true)}\n> ${content.replace(/\n/g, '\n> ')}`; - this.nodeModules.electron.clipboard.writeText(result); - this.showToast('Copied!', { type: 'success' }); - } - } - ); - } - if (record.delete_data && record.delete_data.hidden) { - menuItems.push({ - type: 'item', - label: 'Unhide Deleted Message', - action: () => { - ZeresPluginLibrary.DiscordModules.ContextMenuActions.closeContextMenu(); - record.delete_data.hidden = false; - this.invalidateChannelCache(record.message.channel_id); // good idea? - this.cacheChannelMessages(record.message.channel_id); - this.saveData(); - this.showToast('Unhidden!', { type: 'success' }); - } - }); - } - if (record.edit_history) { - if (editNum != -1) { - menuItems.push({ - type: 'item', - label: 'Delete Edit', - action: () => { - ZeresPluginLibrary.DiscordModules.ContextMenuActions.closeContextMenu(); - this.deleteEditedMessageFromRecord(messageId, editNum); - this.refilterMessages(); // I don't like calling that, maybe figure out a way to animate it collapsing on itself smoothly - this.showToast('Deleted!', { type: 'success' }); - } - }); - } - if (record.edits_hidden) { - menuItems.push({ - type: 'item', - label: 'Unhide Edits', - action: () => { - ZeresPluginLibrary.DiscordModules.ContextMenuActions.closeContextMenu(); - record.edits_hidden = false; - this.saveData(); - this.showToast('Unhidden!', { type: 'success' }); - } - }); - } - } - menuItems.push( - { - type: 'item', - label: 'Remove From Log', - action: () => { - ZeresPluginLibrary.DiscordModules.ContextMenuActions.closeContextMenu(); - let invalidatedChannelCache = false; - if ((record.edit_history && !record.edits_hidden) || (record.delete_data && !record.delete_data.hidden)) this.invalidateChannelCache((invalidatedChannelCache = record.message.channel_id)); - this.deleteMessageFromRecords(messageId); - this.refilterMessages(); // I don't like calling that, maybe figure out a way to animate it collapsing on itself smoothly - if (invalidatedChannelCache) this.cacheChannelMessages(invalidatedChannelCache); - this.saveData(); - if (record.message.channel_id !== this.selectedChannel.id) return; - if (record.delete_data) { - this.dispatcher.dispatch({ - type: 'MESSAGE_DELETE', - id: messageId, - channelId: record.message.channel_id, - ML2: true // ignore ourselves lol, it's already deleted - // on a side note, probably does nothing if we don't ignore - }); - } else { - this.dispatcher.dispatch({ type: 'MLV2_FORCE_UPDATE_MESSAGE_CONTENT', id: messageId }); - } - } - }, - { - type: 'item', - label: 'Copy Message ID', - action: () => { - ZeresPluginLibrary.DiscordModules.ContextMenuActions.closeContextMenu(); - this.nodeModules.electron.clipboard.writeText(messageId); // todo: store electron or writeText somewhere? - this.showToast('Copied!', { type: 'success' }); - } - }, - { - type: 'item', - label: 'Copy Author ID', - action: () => { - ZeresPluginLibrary.DiscordModules.ContextMenuActions.closeContextMenu(); - this.nodeModules.electron.clipboard.writeText(message.author.id); - this.showToast('Copied!', { type: 'success' }); - } - } - ); - ZeresPluginLibrary.DCM.openContextMenu( - e, - this.buildMenu([ - { - type: 'group', - items: menuItems - } - ]) - ); - return; - } - } - }; - element.addEventListener('contextmenu', e => messageContext(e)); - element.addEventListener('click', e => { - if (!this.menu.deleteKeyDown) return; - let target = e.target; - let isMarkup = false; - let isEdited = false; - let isBadImage = target.tagName == 'DIV' && target.classList == ZeresPluginLibrary.WebpackModules.getByProps('imageError').imageError; - if (!isBadImage) { - while (!target.classList.contains('message-2qnXI6') && !(isMarkup = target.classList.contains(this.classes.markup))) { - if (target.classList.contains(this.style.edited)) isEdited = target; - target = target.parentElement; - } - } - if (!isMarkup && !isBadImage) return; - const messageId = message.id; - const record = this.messageRecord[messageId]; - if (!record) return; - this.invalidateChannelCache(record.message.channel_id); // good idea? - this.cacheChannelMessages(record.message.channel_id); - if (isEdited) { - this.deleteEditedMessageFromRecord(messageId, isEdited.edit); - } else { - this.deleteMessageFromRecords(messageId); - } - this.refilterMessages(); // I don't like calling that, maybe figure out a way to animate it collapsing on itself smoothly - this.saveData(); - }); - return element; - } - populateParent(parent, messages) { - let lastMessage; - let lastType; /* unused */ - let messageGroup; - const populate = i => { - try { - // todo: maybe make the text red if it's deleted? - const messageId = messages[i]; - const record = this.getSavedMessage(messageId); - const message = record ? record.message : this.getMessageAny(messageId); - if (!message) return; - // todo: get type and use it - if (!messageGroup /* || !lastType */ || !lastMessage || lastMessage.channel_id != message.channel_id || lastMessage.author.id != message.author.id || new Date(message.timestamp).getDate() !== new Date(lastMessage.timestamp).getDate() || (message.attachments.length && message.content.length)) { - messageGroup = this.createMessageGroup(message, true); - } else { - messageGroup = this.createMessageGroup(message); - } - lastMessage = message; - const markup = messageGroup.getElementsByClassName(this.classes.markup)[0]; - const contentDiv = messageGroup.getElementsByClassName(XenoLib.getSingleClass('embedWrapper container'))[0]; - if (record && record.edit_history) { - markup.classList.add(this.style.edited); - for (let ii = 0; ii < record.edit_history.length; ii++) { - const hist = record.edit_history[ii]; - const editedMarkup = this.formatMarkup(hist.content, message.channel_id); - editedMarkup.insertAdjacentHTML('beforeend', ``); // TODO, change this - new ZeresPluginLibrary.Tooltip(editedMarkup, 'Edited at ' + (typeof hist.time === 'string' ? hist.time : this.createTimeStamp(hist.time)), { side: 'left' }); - editedMarkup.classList.add(this.style.edited); - editedMarkup.edit = ii; - markup.appendChild(editedMarkup); - } - } - markup.append(this.formatMarkup(message.content, message.channel_id)); - if (!record) { - const channel = this.tools.getChannel(message.channel_id); - const guild = this.tools.getServer(channel && channel.guild_id); - markup.addEventListener('click', () => this.jumpToMessage(message.channel_id, message.id, guild && guild.id)); - } - // todo, embeds - // how do I do embeds? - - // why don't attachments show for sent messages? what's up with that? - if (message.attachments.length) { - // const attachmentsContent = this.parseHTML(`
`); - const attemptToUseCachedImage = (attachmentId, attachmentIdx, hidden, filename, width, height) => { - const img = document.createElement('img'); - img.classList = ZeresPluginLibrary.WebpackModules.getByProps('clickable').clickable; - img.messageId = messageId; - img.idx = attachmentIdx; - img.id = attachmentId; // USED FOR FINDING THE IMAGE THRU CONTEXT MENUS - if (hidden) { - img.src = `https://i.clouds.tf/q2vy/r8q6.png#${record.message.channel_id},${img.id}`; - img.width = 200; - } else { - img.src = 'http://localhost:7474/' + attachmentId + filename.match(/\.[0-9a-z]+$/i)[0] + `#${record.message.channel_id},${img.id}`; - img.width = 256; - } - img.addEventListener('click', e => { - if (this.menu.deleteKeyDown) { - this.deleteMessageFromRecords(messageId); - this.refilterMessages(); // I don't like calling that, maybe figure out a way to animate it collapsing on itself smoothly - this.saveData(); - return; - } - this.createModal( - { - src: img.src + '?ML2=true', // self identify - placeholder: img.src, // cute image here - original: img.src, - width: width, - height: height, - onClickUntrusted: e => e.openHref(), - className: this.style.imageRoot - }, - true - ); - }); - img.onerror = () => { - const imageErrorDiv = document.createElement('div'); - imageErrorDiv.classList = ZeresPluginLibrary.WebpackModules.getByProps('imageError').imageError; - imageErrorDiv.messageId = messageId; - contentDiv.replaceChild(imageErrorDiv, img); - }; - contentDiv.appendChild(img); - return true; - }; - const handleCreateImage = (attachment, idx) => { - if (attachment.url == 'ERROR') { - attemptToUseCachedImage(attachment.id, idx, attachment.hidden, attachment.filename, attachment.width, attachment.height); - } else { - if (!this.isImage(attachment.url)) return; // bruh - const img = document.createElement('img'); - img.classList = ZeresPluginLibrary.WebpackModules.getByProps('clickable').clickable; - img.messageId = messageId; - img.id = attachment.id; // USED FOR FINDING THE IMAGE THRU CONTEXT MENUS - img.idx = idx; - // img.style.minHeight = '104px'; // bruh? - if (record) { - img.addEventListener('click', () => { - if (this.menu.deleteKeyDown) { - this.deleteMessageFromRecords(messageId); - this.refilterMessages(); // I don't like calling that, maybe figure out a way to animate it collapsing on itself smoothly - this.saveData(); - return; - } - this.createModal( - { - src: attachment.url + '?ML2=true', // self identify - placeholder: attachment.url, // cute image here - original: attachment.url, - width: attachment.width, - height: attachment.height, - onClickUntrusted: e => e.openHref(), - className: this.style.imageRoot - }, - true - ); - }); - } - img.onerror = () => { - if (img.triedCache) { - const imageErrorDiv = document.createElement('div'); - imageErrorDiv.classList = ZeresPluginLibrary.WebpackModules.getByProps('imageError').imageError; - imageErrorDiv.messageId = messageId; - contentDiv.replaceChild(imageErrorDiv, img); - } - if (record) { - this.nodeModules.request.head(attachment.url, (err, res) => { - try { - if (err || res.statusCode != 404) return; - record.message.attachments[idx].url = 'ERROR'; - img.src = 'http://localhost:7474/' + attachment.id + attachment.filename.match(/\.[0-9a-z]+$/)[0]; - img.triedCache = true; - } catch (err) { - console.error('Failed loading cached image', err.message); - } - }); - } - }; - if (attachment.hidden) { - img.src = `https://i.clouds.tf/q2vy/r8q6.png#${record.message.channel_id},${img.id}`; - img.width = 200; - } else { - img.src = attachment.url; - img.width = this.clamp(attachment.width, 200, 650); - } - contentDiv.appendChild(img); - } - }; - for (let ii = 0; ii < message.attachments.length; ii++) handleCreateImage(message.attachments[ii], ii); - } - if (message.embeds && message.embeds.length && false) { - const ddiv = document.createElement('div'); - // TODO: optimize - if (!this.populateParent.__embedcontainer) this.populateParent.__embedcontainer = this.safeGetClass(() => ZeresPluginLibrary.WebpackModules.getByProps('containerCozy', 'gifFavoriteButton').containerCozy, 'containerCozy'); - ddiv.className = this.populateParent.__embedcontainer; - const fuckme = new (ZeresPluginLibrary.WebpackModules.getByDisplayName('MessageAccessories'))({ channel: this.tools.getChannel(message.channel_id) || this.menu.randomValidChannel }); - for (const embed of message.embeds) { - const embedBase = { - GIFVComponent: ZeresPluginLibrary.WebpackModules.getByDisplayName('LazyGIFV'), - ImageComponent: ZeresPluginLibrary.WebpackModules.getByDisplayName('LazyImageZoomable'), - LinkComponent: ZeresPluginLibrary.WebpackModules.getByDisplayName('MaskedLink'), - VideoComponent: ZeresPluginLibrary.WebpackModules.getByDisplayName('LazyVideo'), - allowFullScreen: true, - autoPlayGif: true, - backgroundOpacity: '', - className: ZeresPluginLibrary.WebpackModules.getByProps('embedWrapper', 'gifFavoriteButton').embedWrapper, - embed: ZeresPluginLibrary.WebpackModules.getByProps('sanitizeEmbed').sanitizeEmbed(message.channel_id, message.id, embed), - hideMedia: false, - inlineGIFV: true, - maxMediaHeight: 300, - maxMediaWidth: 400, - maxThumbnailHeight: 80, - maxThumbnailWidth: 80, - suppressEmbed: false, - renderTitle: fuckme.renderEmbedTitle.bind(fuckme), - renderDescription: fuckme.renderEmbedDescription.bind(fuckme), - renderLinkComponent: ZeresPluginLibrary.WebpackModules.getByProps('defaultRenderLinkComponent').defaultRenderLinkComponent, - renderImageComponent: ZeresPluginLibrary.WebpackModules.getByProps('renderImageComponent').renderImageComponent, - renderVideoComponent: ZeresPluginLibrary.WebpackModules.getByProps('renderVideoComponent').renderVideoComponent, - renderAudioComponent: ZeresPluginLibrary.WebpackModules.getByProps('renderAudioComponent').renderAudioComponent, - renderMaskedLinkComponent: ZeresPluginLibrary.WebpackModules.getByProps('renderMaskedLinkComponent').renderMaskedLinkComponent - }; - ZeresPluginLibrary.DiscordModules.ReactDOM.render(ZeresPluginLibrary.DiscordModules.React.createElement(ZeresPluginLibrary.WebpackModules.getByDisplayName('Embed'), embedBase), ddiv); - } - contentDiv.appendChild(ddiv); - } - if (!contentDiv.childElementCount && !message.content.length) return; // don't bother - //messageContent.appendChild(divParent); - parent.appendChild(messageGroup); - } catch (err) { - ZeresPluginLibrary.Logger.stacktrace(this.getName(), 'Error in populateParent', err); - } - }; - let i = 0; - const addMore = () => { - for (let added = 0; i < messages.length && (added < this.settings.renderCap || (this.menu.shownMessages != -1 && i < this.menu.shownMessages)); i++, added++) populate(i); - handleMoreMessages(); - this.menu.shownMessages = i; - }; - const handleMoreMessages = () => { - if (i < messages.length) { - const div = document.createElement('div'); - const moreButton = this.createButton('LOAD MORE', function () { - this.parentElement.remove(); - addMore(); - }); - moreButton.style.width = '100%'; - moreButton.style.marginBottom = '20px'; - div.appendChild(moreButton); - parent.appendChild(div); - } - }; - - if (this.settings.renderCap) addMore(); - else for (; i < messages.length; i++) populate(i); - this.processUserRequestQueue(); - if (!messages.length) { - const strong = document.createElement('strong'); - strong.className = this.multiClasses.defaultColor; - strong.innerText = "Not to worry, the logger is not broken! There simply wasn't anything logged in the selected tab."; - parent.appendChild(strong); - } - } - // >>-|| FILTERING ||-<< - sortMessagesByAge(map) { - // sort direction: new - old - map.sort((a, b) => { - const recordA = this.messageRecord[a]; - const recordB = this.messageRecord[b]; - if (!recordA || !recordB) return 0; - let timeA = new Date(recordA.message.timestamp).getTime(); - let timeB = new Date(recordB.message.timestamp).getTime(); - if (recordA.edit_history && typeof recordA.edit_history[recordA.edit_history.length - 1].time !== 'string') timeA = recordA.edit_history[recordA.edit_history.length - 1].time; - if (recordB.edit_history && typeof recordB.edit_history[recordB.edit_history.length - 1].time !== 'string') timeB = recordB.edit_history[recordB.edit_history.length - 1].time; - if (recordA.delete_data && recordA.delete_data.time) timeA = recordA.delete_data.time; - if (recordB.delete_data && recordB.delete_data.time) timeB = recordB.delete_data.time; - return parseInt(timeB) - parseInt(timeA); - }); - } - getFilteredMessages() { - let messages = []; - - const pushIdsIntoMessages = map => { - for (let channel in map) { - for (let messageIdIDX in map[channel]) { - messages.push(map[channel][messageIdIDX]); - } - } - }; - const checkIsMentioned = map => { - for (let channel in map) { - for (let messageIdIDX in map[channel]) { - const messageId = map[channel][messageIdIDX]; - const record = this.getSavedMessage(messageId); - if (!record) continue; - if (record.ghost_pinged) { - messages.push(messageId); - } - } - } - }; - - if (this.menu.selectedTab == 'sent') { - for (let i of this.cachedMessageRecord) { - messages.push(i.id); - } - } - if (this.menu.selectedTab == 'edited') pushIdsIntoMessages(this.editedMessageRecord); - if (this.menu.selectedTab == 'deleted') pushIdsIntoMessages(this.deletedMessageRecord); - if (this.menu.selectedTab == 'purged') pushIdsIntoMessages(this.purgedMessageRecord); - if (this.menu.selectedTab == 'ghostpings') { - checkIsMentioned(this.deletedMessageRecord); - checkIsMentioned(this.editedMessageRecord); - checkIsMentioned(this.purgedMessageRecord); - } - - const filters = this.menu.filter.split(','); - - for (let i = 0; i < filters.length; i++) { - const split = filters[i].split(':'); - if (split.length < 2) continue; - - const filterType = split[0].trim().toLowerCase(); - const filter = split[1].trim().toLowerCase(); - - if (filterType == 'server' || filterType == 'guild') - messages = messages.filter(x => { - const message = this.getMessageAny(x); - if (!message) return false; - const channel = this.tools.getChannel(message.channel_id); - const guild = this.tools.getServer(message.guild_id || (channel && channel.guild_id)); - return (message.guild_id || (channel && channel.guild_id)) == filter || (guild && guild.name.toLowerCase().includes(filter.toLowerCase())); - }); - - if (filterType == 'channel') - messages = messages.filter(x => { - const message = this.getMessageAny(x); - if (!message) return false; - const channel = this.tools.getChannel(message.channel_id); - return message.channel_id == filter || (channel && channel.name.toLowerCase().includes(filter.replace('#', '').toLowerCase())); - }); - - if (filterType == 'message' || filterType == 'content') - messages = messages.filter(x => { - const message = this.getMessageAny(x); - return x == filter || (message && message.content.toLowerCase().includes(filter.toLowerCase())); - }); - - if (filterType == 'user') - messages = messages.filter(x => { - const message = this.getMessageAny(x); - if (!message) return false; - const channel = this.tools.getChannel(message.channel_id); - const member = ZeresPluginLibrary.DiscordModules.GuildMemberStore.getMember(message.guild_id || (channel && channel.guild_id), message.author.id); - return message.author.id == filter || message.author.username.toLowerCase().includes(filter.toLowerCase()) || (member && member.nick && member.nick.toLowerCase().includes(filter.toLowerCase())); - }); - - if (filterType == 'has') { - switch (filter) { - case 'image': - messages = messages.filter(x => { - const message = this.getMessageAny(x); - if (!message) return false; - if (Array.isArray(message.attachments)) if (message.attachments.some(({ filename }) => ZeresPluginLibrary.DiscordModules.DiscordConstants.IMAGE_RE.test(filename))) return true; - if (Array.isArray(message.embeds)) return message.embeds.some(({ image }) => !!image); - return false; - }); - break; - case 'link': - messages = messages.filter(x => { - const message = this.getMessageAny(x); - if (!message) return false; - return message.content.search(/https?:\/\/[\w\W]{2,}/) !== -1; - }); - break; - } - } - } - - if (this.menu.selectedTab != 'sent') { - this.sortMessagesByAge(messages); - if (this.settings.reverseOrder) messages.reverse(); // this gave me a virtual headache - } else if (!this.settings.reverseOrder) messages.reverse(); // this gave me a virtual headache - - return messages; - } - // >>-|| REPOPULATE ||-<< - refilterMessages() { - const messagesDIV = document.getElementById(this.style.menuMessages); - const original = messagesDIV.style.display; - messagesDIV.style.display = 'none'; - while (messagesDIV.firstChild) messagesDIV.removeChild(messagesDIV.firstChild); - this.menu.messages = this.getFilteredMessages(); - this.populateParent(messagesDIV, this.menu.messages); - messagesDIV.style.display = original; - } - // >>-|| HEADER ||-<< - openTab(tab) { - const tabBar = document.getElementById(this.style.menuTabBar); - if (!tabBar) return this.showToast(`Error switching to tab ${tab}!`, { type: 'error', timeout: 3000 }); - tabBar.querySelector(`.${this.style.tabSelected}`).classList.remove(this.style.tabSelected); - tabBar.querySelector('#' + tab).classList.add(this.style.tabSelected); - this.menu.selectedTab = tab; - setTimeout(() => this.refilterMessages(), 0); - } - createHeader() { - if (!this.createHeader.classes || this.createHeader.classes.__errored) { - try { - const TabBarStuffs = ZeresPluginLibrary.WebpackModules.getByProps('body', 'tabBar'); - this.createHeader.classes = { - itemTabBarItem: this.style.tabBarItem, - tabBarContainer: this.style.tabBarContainer, - tabBar: this.style.tabBar, - tabBarSingle: this.style.tabBar - }; - } catch { - this.createHeader.classes = { - itemTabBarItem: 'tabBarItem' + ' ' + 'item', - tabBarContainer: 'tabBarContainer', - tabBar: 'tabBar', - tabBarSingle: 'tabBar', - __errored: true - }; - } - } - const classes = this.createHeader.classes; - const createTab = (title, id) => { - const tab = this.parseHTML(`
${title}
`); - tab.addEventListener('mousedown', () => this.openTab(id)); - return tab; - }; - const tabBar = this.parseHTML(`
`); - const tabs = tabBar.getElementsByClassName(classes.tabBarSingle)[0]; - tabs.appendChild(createTab('Sent', 'sent')); - tabs.appendChild(createTab('Deleted', 'deleted')); - tabs.appendChild(createTab('Edited', 'edited')); - tabs.appendChild(createTab('Purged', 'purged')); - tabs.appendChild(createTab('Ghost pings', 'ghostpings')); - tabBar.style.marginRight = '20px'; - return tabBar; - } - createTextBox() { - if (!this.createTextBox.classes || this.createTextBox.classes.__errored) { - try { - this.createTextBox.classes = { - inputWrapper: this.style.inputWrapper, - inputMultiInput: this.style.multiInput, - multiInputFirst: this.style.multiInputFirst, - inputDefaultMultiInputField: this.style.input, - questionMark: this.style.questionMark, - icon: this.style.questionMark, - focused: ZeresPluginLibrary.WebpackModules.getByProps('focused').focused.split(/ /g), - questionMarkSingle: this.style.questionMark - } - } catch { - this.createTextBox.classes = { - inputWrapper: 'inputMini inputWrapper', - inputMultiInput: 'inputPrefix input' + ' ' + 'multiInput', - multiInputFirst: 'multiInputFirst', - inputDefaultMultiInputField: 'inputPrefix inputDefault' + ' ' + 'multiInputField', - questionMark: 'questionMark', - icon: 'questionMark', - focused: 'focused', - questionMarkSingle: 'questionMark', - __errored: true - } - } - } - const classes = this.createTextBox.classes; - let textBox = this.parseHTML( - `
` - ); - const inputEl = textBox.getElementsByTagName('input')[0]; - inputEl.addEventListener('focusout', e => { - DOMTokenList.prototype.remove.apply(e.target.parentElement.parentElement.classList, classes.focused); - }); - inputEl.addEventListener('focusin', e => { - DOMTokenList.prototype.add.apply(e.target.parentElement.parentElement.classList, classes.focused); - }); - const onUpdate = e => { - if (this.menu.filterSetTimeout) clearTimeout(this.menu.filterSetTimeout); - this.menu.filter = inputEl.value; - const filters = this.menu.filter.split(','); - // console.log(filters); - if (!filters[0].length) return this.refilterMessages(); - this.menu.filterSetTimeout = setTimeout(() => { - if (filters[0].length) { - for (let i = 0; i < filters.length; i++) { - const split = filters[i].split(':'); - if (split.length < 2) return; - } - } - this.refilterMessages(); - }, 200); - }; - inputEl.addEventListener('keyup', onUpdate); // maybe I can actually use keydown but it didn't work for me - inputEl.addEventListener('paste', onUpdate); - const helpButton = textBox.getElementsByClassName(classes.questionMarkSingle)[0]; - helpButton.addEventListener('click', () => { - const extraHelp = this.createButton('Logger help', () => this.showLoggerHelpModal()); - this.createModal({ - confirmText: 'OK', - header: 'Filter help', - size: this.createModal.confirmationModal.Sizes.LARGE, - children: [ - ZeresPluginLibrary.ReactTools.createWrappedElement([ - this.parseHTML( - `
"server: " - Filter results with the specified server name or id. - "channel: " - Filter results with the specified channel name or id. - "user: " - Filter results with the specified username, nickname or userid. - "message: " or "content: " - Filter results with the specified message content. - "has: - Filter results to only images or links - - Separate the search tags with commas. - Example: server: tom's bd stuff, message: heck - - - Shortcut help: - - "Ctrl + M" (default) - Open message log. - "Ctrl + N" (default) - Open message log with selected channel filtered.\n\n
`.replace(/\n/g, '
') - ), - extraHelp - ]) - ], - red: false - }); - }); - new ZeresPluginLibrary.Tooltip(helpButton, 'Help!', { side: 'top' }); - return textBox; - } - // >>-|| MENU MODAL CREATION ||-<< - openWindow(type) { - return; - if (this.menu.open) { - this.menu.scrollPosition = 0; - if (type) this.openTab(type); - return; - } - this.menu.open = true; - if (type) this.menu.selectedTab = type; - if (!this.menu.selectedTab) this.menu.selectedTab = 'deleted'; - const messagesDIV = this.parseHTML(`
`); - const viewportHeight = document.getElementById('app-mount').getBoundingClientRect().height; - messagesDIV.style.minHeight = viewportHeight * 0.514090909 + 'px'; // hack but ok - //messagesDIV.style.display = 'none'; - const onChangeOrder = el => { - this.settings.reverseOrder = !this.settings.reverseOrder; - el.target.innerText = 'Sort direction: ' + (!this.settings.reverseOrder ? 'new - old' : 'old - new'); // maybe a func? - this.saveSettings(); - this.refilterMessages(); - }; - - const Text = ZeresPluginLibrary.WebpackModules.getByDisplayName('Text') || ZeresPluginLibrary.WebpackModules.getByDisplayName('LegacyText'); - const onClearLog = e => { - if (!Text) return; - if (document.getElementById(this.style.filter).parentElement.parentElement.className.indexOf(this.createTextBox.classes.focused[0]) != -1) return; - let type = this.menu.selectedTab; - if (type === 'ghostpings') type = 'ghost pings'; - else { - type += ' messages'; - } - this.createModal({ - header: 'Clear log', - children: ZeresPluginLibrary.DiscordModules.React.createElement(Text, { size: Text.Sizes.SIZE_16, children: [`Are you sure you want to delete all ${type}${this.menu.filter.length ? ' that also match filter' : ''}?`] }), - confirmText: 'Confirm', - cancelText: 'Cancel', - onConfirm: () => { - if (this.menu.selectedTab == 'sent') { - if (!this.menu.filter.length) - for (let id of this.menu.messages) - this.cachedMessageRecord.splice( - this.cachedMessageRecord.findIndex(m => m.id === id), - 1 - ); - else this.cachedMessageRecord.length = 0; // hack, does it cause a memory leak? - } else { - for (let id of this.menu.messages) { - const record = this.messageRecord[id]; - let isSelected = false; - if (record) { - this.invalidateChannelCache(record.message.channel_id); - if (this.selectedChannel) isSelected = record.message.channel_id === this.selectedChannel.id; - } - this.deleteMessageFromRecords(id); - if (this.selectedChannel && isSelected) this.cacheChannelMessages(this.selectedChannel.id); - } - this.saveData(); - } - setImmediate(_ => this.refilterMessages()); - // this.menu.refilterOnMount = true; - } - }); - }; - this.createModal( - { - confirmText: 'Clear log', - cancelText: 'Sort direction: ' + (!this.settings.reverseOrder ? 'new - old' : 'old - new'), - header: ZeresPluginLibrary.ReactTools.createWrappedElement([this.createTextBox(), this.createHeader()]), - size: this.createModal.confirmationModal.Sizes.LARGE, - children: [ZeresPluginLibrary.ReactTools.createWrappedElement([messagesDIV])], - onCancel: onChangeOrder, - onConfirm: onClearLog, - onClose: _ => { }, - ml2Data: true, - className: this.style.menuRoot, - ref: e => { - if (!e) return; - /* advanced tech! */ - const stateNode = ZeresPluginLibrary.Utilities.getNestedProp(e, '_reactInternalFiber.return.return.stateNode.firstChild.childNodes.1.firstChild'); - if (!stateNode) return; - stateNode.addEventListener( - 'scroll', - this.tools.DiscordUtils.debounce(() => { - this.scrollPosition = document.getElementById(this.style.menuMessages).parentElement.parentElement.parentElement.scrollTop; - }, 100) - ); - } - }, - false, - this.style.menu - ); - let loadAttempts = 0; - const loadMessages = () => { - loadAttempts++; - try { - this.refilterMessages(); - } catch (e) { - if (loadAttempts > 4) { - XenoLib.Notifications.error(`Couldn't load menu messages! Report this issue to Lighty, error info is in console`, { timeout: 0 }); - ZeresPluginLibrary.Logger.stacktrace(this.getName(), 'Failed loading menu', e); - return; - } - setTimeout(loadMessages, 100); - } - }; - setTimeout(loadMessages, 100); - } - /* ==================================================-|| END MENU ||-================================================== */ - /* ==================================================-|| START CONTEXT MENU ||-================================================== */ - patchContextMenus() { - const _this = this; - - this.unpatches.push(BdApi.ContextMenu.patch('message', (ret, props) => { - const menu = ZeresPluginLibrary.Utilities.getNestedProp( - ZeresPluginLibrary.Utilities.findInReactTree(ret, e => e && e.navId === 'message'), - 'children' - ); - if (!Array.isArray(menu)) return; - - const newItems = []; - const addElement = (label, action, options = {}) => newItems.push({ label, action, ...options }); - - addElement('Open Logs', () => this.openWindow()); - - const messageId = props.message.id; - const channelId = props.channel.id; - const record = this.messageRecord[messageId]; - if (record) { - /* - addElement('Show in menu', () => { - this.menu.filter = `message:${messageId}`; - this.openWindow(); - }); */ - if (record.delete_data) { - const options = menu.find(m => m.props.children && m.props.children.length > 10); - options.props.children.splice(0, options.props.children.length); - addElement( - 'Hide Deleted Message', - () => { - this.dispatcher.dispatch({ - type: 'MESSAGE_DELETE', - id: messageId, - channelId: channelId, - ML2: true // ignore ourselves lol, it's already deleted - // on a side note, probably does nothing if we don't ignore - }); - this.showToast('Hidden!', { type: 'success' }); - record.delete_data.hidden = true; - this.saveData(); - } - ); - const idx = this.noTintIds.indexOf(messageId); - addElement( - `${idx !== -1 ? 'Add' : 'Remove'} Deleted Tint`, - () => { - if (idx !== -1) this.noTintIds.splice(idx, 1); - else this.noTintIds.push(messageId); - this.showToast(idx !== -1 ? 'Added!' : 'Removed!', { type: 'success' }); - } - ); - } - if (record.edit_history) { - if (record.edits_hidden) { - addElement( - 'Unhide Edits', - () => { - record.edits_hidden = false; - this.saveData(); - this.dispatcher.dispatch({ type: 'MLV2_FORCE_UPDATE_MESSAGE_CONTENT', id: messageId }); - } - ); - } else { - let target = props.target; - if (target) { - while (target && target.className && target.className.indexOf(this.style.edited) === -1) { - target = target.parentElement; - } - if (target) { - if (!this.editModifiers[messageId]) { - addElement( - 'Hide Edits', - () => { - record.edits_hidden = true; - this.saveData(); - this.dispatcher.dispatch({ type: 'MLV2_FORCE_UPDATE_MESSAGE_CONTENT', id: messageId }); - } - ); - } - const editNum = target.getAttribute('editNum'); - if (this.editModifiers[messageId]) { - addElement( - `${this.editModifiers[messageId].noSuffix ? 'Show' : 'Hide'} (edited) Tag`, - () => { - this.editModifiers[messageId].noSuffix = true; - this.dispatcher.dispatch({ type: 'MLV2_FORCE_UPDATE_MESSAGE_CONTENT', id: messageId }); - } - ); - addElement( - `Undo Show As Message`, - () => { - delete this.editModifiers[messageId]; - this.dispatcher.dispatch({ type: 'MLV2_FORCE_UPDATE_MESSAGE_CONTENT', id: messageId }); - }, - this.obfuscatedClass('undo-show-as-message') - ); - } else if (typeof editNum !== 'undefined' && editNum !== null) { - addElement( - 'Show Edit As Message', - () => { - this.editModifiers[messageId] = { editNum }; - this.dispatcher.dispatch({ type: 'MLV2_FORCE_UPDATE_MESSAGE_CONTENT', id: messageId }); - } - ); - addElement( - 'Delete Edit', - () => { - this.deleteEditedMessageFromRecord(messageId, parseInt(editNum)); - this.dispatcher.dispatch({ type: 'MLV2_FORCE_UPDATE_MESSAGE_CONTENT', id: messageId }); - }, - { color: 'colorDanger' } - ); - } - } - } - } - } - if (record) { - addElement( - 'Remove From Log', - () => { - this.deleteMessageFromRecords(messageId); - this.saveData(); - if (record.delete_data) { - this.dispatcher.dispatch({ - type: 'MESSAGE_DELETE', - id: messageId, - channelId: channelId, - ML2: true // ignore ourselves lol, it's already deleted - // on a side note, probably does nothing if we don't ignore - }); - } else { - this.dispatcher.dispatch({ type: 'MLV2_FORCE_UPDATE_MESSAGE_CONTENT', id: messageId }); - } - }, - { color: 'colorDanger' } - ); - } - } - - menu.push(BdApi.ContextMenu.buildMenuChildren([{ - type: 'group', - items: [{ - type: 'submenu', - label: this.settings.contextmenuSubmenuName, - items: newItems - }] - }])); - })); - - const handleWhiteBlackList = (newItems, id) => { - const addElement = (label, action, options = {}) => newItems.push({ label, action, ...options }); - const whitelistIdx = this.settings.whitelist.findIndex(m => m === id); - const blacklistIdx = this.settings.blacklist.findIndex(m => m === id); - if (whitelistIdx == -1 && blacklistIdx == -1) { - addElement( - `Add to Whitelist`, - () => { - this.settings.whitelist.push(id); - this.saveSettings(); - this.showToast('Added!', { type: 'success' }); - } - ); - addElement( - `Add to Blacklist`, - () => { - this.settings.blacklist.push(id); - this.saveSettings(); - this.showToast('Added!', { type: 'success' }); - } - ); - } else if (whitelistIdx != -1) { - addElement( - `Remove From Whitelist`, - () => { - this.settings.whitelist.splice(whitelistIdx, 1); - this.saveSettings(); - this.showToast('Removed!', { type: 'success' }); - } - ); - addElement( - `Move to Blacklist`, - () => { - this.settings.whitelist.splice(whitelistIdx, 1); - this.settings.blacklist.push(id); - this.saveSettings(); - this.showToast('Moved!', { type: 'success' }); - } - ); - } else { - addElement( - `Remove From Blacklist`, - () => { - this.settings.blacklist.splice(blacklistIdx, 1); - this.saveSettings(); - this.showToast('Removed!', { type: 'success' }); - } - ); - addElement( - `Move to Whitelist`, - () => { - this.settings.blacklist.splice(blacklistIdx, 1); - this.settings.whitelist.push(id); - this.saveSettings(); - this.showToast('Moved!', { type: 'success' }); - } - ); - } - const notifIdx = this.settings.notificationBlacklist.indexOf(id); - addElement( - `${notifIdx === -1 ? 'Add To' : 'Remove From'} Notification Blacklist`, - () => { - if (notifIdx === -1) this.settings.notificationBlacklist.push(id); - else this.settings.notificationBlacklist.splice(notifIdx, 1); - this.saveSettings(); - this.showToast(notifIdx === -1 ? 'Added!' : 'Removed!', { type: 'success' }); - } - ); - };/* - - this.unpatches.push(BdApi.ContextMenu.patch('channel-context', (ret, props) => { - console.log(ret, props); - const menu = ZeresPluginLibrary.Utilities.getNestedProp( - ZeresPluginLibrary.Utilities.findInReactTree(ret, e => e && e.navId === 'channel-context'), - 'children' - ); - if (!Array.isArray(menu)) return; - - const newItems = []; - const addElement = (label, action, options = {}) => newItems.push({ label, action, ...options }); - - addElement('Open Logs', () => this.openWindow()); - addElement( - `Open Log For Channel`, - () => { - _this.menu.filter = `channel:${props.channel.id}`; - _this.openWindow(); - } - ); - handleWhiteBlackList(newItems, props.channel.id); - - menu.push(BdApi.ContextMenu.buildMenuChildren([{ - type: 'group', - items: [{ - type: 'submenu', - label: this.settings.contextmenuSubmenuName, - items: newItems - }] - }])); - })); */ - - this.unpatches.push(BdApi.ContextMenu.patch('guild-context', (ret, props) => { - const menu = ZeresPluginLibrary.Utilities.getNestedProp( - ZeresPluginLibrary.Utilities.findInReactTree(ret, e => e && e.navId === 'guild-context'), - 'children' - ); - if (!Array.isArray(menu)) return; - - const newItems = []; - const addElement = (label, action, options = {}) => newItems.push({ label, action, ...options }); - - addElement('Open Logs', () => this.openWindow()); - - addElement( - `Open Log For Guild`, - () => { - _this.menu.filter = `guild:${props.guild.id}`; - _this.openWindow(); - } - ); - handleWhiteBlackList(newItems, props.guild.id); - - menu.push(BdApi.ContextMenu.buildMenuChildren([{ - type: 'group', - items: [{ - type: 'submenu', - label: this.settings.contextmenuSubmenuName, - items: newItems - }] - }])); - })); - - this.unpatches.push(BdApi.ContextMenu.patch('user-context', (ret, props) => { - const menu = ZeresPluginLibrary.Utilities.getNestedProp( - ZeresPluginLibrary.Utilities.findInReactTree(ret, e => e && e.navId === 'user-context'), - 'children' - ); - if (!Array.isArray(menu)) return; - - const newItems = []; - const addElement = (label, action, options = {}) => newItems.push({ label, action, ...options }); - - addElement('Open Logs', () => this.openWindow()); - addElement( - `Open Log For User`, - () => { - _this.menu.filter = `user:${props.user.id}`; - _this.openWindow(); - } - ); - - if (props.channel?.isDM()) { - addElement( - `Open Log For DM`, - () => { - _this.menu.filter = `channel:${props.channel.id}`; - _this.openWindow(); - } - ); - - handleWhiteBlackList(newItems, props.channel.id); - } - - menu.push(BdApi.ContextMenu.buildMenuChildren([{ - type: 'group', - items: [{ - type: 'submenu', - label: this.settings.contextmenuSubmenuName, - items: newItems - }] - }])); - })); - - this.unpatches.push(BdApi.ContextMenu.patch('gdm-context', (ret, props) => { - const menu = ZeresPluginLibrary.Utilities.getNestedProp( - ZeresPluginLibrary.Utilities.findInReactTree(ret, e => e && e.navId === 'gdm-context'), - 'children' - ); - if (!Array.isArray(menu)) return; - - const newItems = []; - const addElement = (label, action, options = {}) => newItems.push({ label, action, ...options }); - - addElement('Open Logs', () => this.openWindow()); - addElement( - `Open Log For Channel`, - () => { - _this.menu.filter = `channel:${props.channel.id}`; - _this.openWindow(); - } - ); - handleWhiteBlackList(newItems, props.channel.id); - - menu.push(BdApi.ContextMenu.buildMenuChildren([{ - type: 'group', - items: [{ - type: 'submenu', - label: this.settings.contextmenuSubmenuName, - items: newItems - }] - }])); - })); - - return; - const Patcher = XenoLib.createSmartPatcher({ before: (moduleToPatch, functionName, callback, options = {}) => ZeresPluginLibrary.Patcher.before(this.getName(), moduleToPatch, functionName, callback, options), instead: (moduleToPatch, functionName, callback, options = {}) => ZeresPluginLibrary.Patcher.instead(this.getName(), moduleToPatch, functionName, callback, options), after: (moduleToPatch, functionName, callback, options = {}) => ZeresPluginLibrary.Patcher.after(this.getName(), moduleToPatch, functionName, callback, options), unpatchAll: () => ZeresPluginLibrary.Patcher.unpatchAll(this.getName()) }); - const WebpackModules = ZeresPluginLibrary.WebpackModules; - const nativeImageContextMenuPatch = () => { - const mod = WebpackModules.find(e => e.default && (e.__powercordOriginal_default || e.default).displayName === 'NativeImageContextMenu'); - if (!mod) return console.error('Failed to patch NativeImageContextMenu'); - this.unpatches.push( - this.Patcher.after( - mod, - 'default', - (_, [props], ret) => { - const newItems = []; - if (!this.menu.open) return; - const menu = ZeresPluginLibrary.Utilities.getNestedProp( - ZeresPluginLibrary.Utilities.findInReactTree(ret, e => e && e.type && e.type.displayName === 'Menu'), - 'props.children' - ); - if (!Array.isArray(menu)) return; - const addElement = (label, callback, id, options = {}) => newItems.push(XenoLib.createContextMenuItem(label, callback, id, options)); - let matched; - let isCached = false; - if (!props.src) return; - if (props.src.startsWith('data:image/png')) { - const cut = props.src.substr(0, 100); - matched = cut.match(/;(\d+);(\d+);/); - isCached = true; - } else { - matched = props.src.match(/.*ments\/(\d+)\/(\d+)\//); - if (!matched) matched = props.src.match(/r8q6.png#(\d+),(\d+)/); - if (!matched) { - matched = props.src.match(/localhost:7474.*#(\d+),(\d+)/); - isCached = true; - } - } - if (!matched) return; - const channelId = matched[1]; - const attachmentId = matched[2]; - const element = document.getElementById(attachmentId); - if (!element) return; - const attachmentIdx = element.idx; - const record = this.getSavedMessage(element.messageId); - if (!record) return; - addElement( - 'Save to Folder', - () => { - const { dialog } = this.nodeModules.electron.remote; - dialog - .showSaveDialog({ - defaultPath: record.message.attachments[attachmentIdx].filename - }) - .then(({ filePath: dir }) => { - try { - if (!dir) return; - const attemptToUseCached = () => { - const srcFile = `${this.settings.imageCacheDir}/${attachmentId}${record.message.attachments[attachmentIdx].filename.match(/\.[0-9a-z]+$/)[0]}`; - if (!this.nodeModules.fs.existsSync(srcFile)) return this.showToast('Image does not exist locally!', { type: 'error', timeout: 5000 }); - this.nodeModules.fs.copyFileSync(srcFile, dir); - this.showToast('Saved!', { type: 'success' }); - }; - if (isCached) { - attemptToUseCached(); - } else { - const req = this.nodeModules.request(record.message.attachments[attachmentIdx].url); - req.on('response', res => { - if (res.statusCode == 200) { - req - .pipe(this.nodeModules.fs.createWriteStream(dir)) - .on('finish', () => this.showToast('Saved!', { type: 'success' })) - .on('error', () => this.showToast('Failed to save! No permissions.', { type: 'error', timeout: 5000 })); - } else if (res.statusCode == 404) { - attemptToUseCached(); - } else { - attemptToUseCached(); - } - }); - } - } catch (err) { - console.error('Failed saving', err.message); - } - }); - }, - this.obfuscatedClass('save-to') - ); - addElement( - 'Copy to Clipboard', - () => { - const { clipboard, nativeImage } = this.nodeModules.electron; - const attemptToUseCached = () => { - const srcFile = `${this.settings.imageCacheDir}/${attachmentId}${record.message.attachments[attachmentIdx].filename.match(/\.[0-9a-z]+$/)[0]}`; - if (!this.nodeModules.fs.existsSync(srcFile)) return this.showToast('Image does not exist locally!', { type: 'error', timeout: 5000 }); - clipboard.write({ image: srcFile }); - this.showToast('Copied!', { type: 'success' }); - }; - if (isCached) { - attemptToUseCached(); - } else { - const path = this.nodeModules.path; - const process = require('process'); - // ImageToClipboard by Zerebos - this.nodeModules.request({ url: record.message.attachments[attachmentIdx].url, encoding: null }, (error, response, buffer) => { - try { - if (error || response.statusCode != 200) { - this.showToast('Failed to copy. Image may not exist. Attempting to use local image cache.', { type: 'error' }); - attemptToUseCached(); - return; - } - if (process.platform === 'win32' || process.platform === 'darwin') { - clipboard.write({ image: nativeImage.createFromBuffer(buffer) }); - } else { - const file = path.join(process.env.HOME, 'ml2temp.png'); - this.nodeModules.fs.writeFileSync(file, buffer, { encoding: null }); - clipboard.write({ image: file }); - this.nodeModules.fs.unlinkSync(file); - } - this.showToast('Copied!', { type: 'success' }); - } catch (err) { - console.error('Failed to cached', err.message); - } - }); - } - }, - this.obfuscatedClass('copy-to') - ); - addElement( - 'Jump to Message', - () => { - this.jumpToMessage(channelId, element.messageId, record.message.guild_id); - }, - this.obfuscatedClass('jump-to') - ); - if (record.delete_data && record.delete_data.hidden) { - addElement( - 'Unhide Deleted Message', - () => { - record.delete_data.hidden = false; - this.invalidateChannelCache(record.message.channel_id); // good idea? - this.cacheChannelMessages(record.message.channel_id); - this.saveData(); - this.showToast('Unhidden!', { type: 'success' }); - }, - this.obfuscatedClass('unhide-deleted') - ); - } - if (record.edit_history && record.edits_hidden) { - addElement( - 'Unhide Message History', - () => { - record.edits_hidden = false; - this.invalidateChannelCache(record.message.channel_id); // good idea? - this.cacheChannelMessages(record.message.channel_id); - this.saveData(); - this.showToast('Unhidden!', { type: 'success' }); - }, - this.obfuscatedClass('unhide-edited') - ); - } - addElement( - 'Remove From Log', - () => { - this.deleteMessageFromRecords(element.messageId); - this.refilterMessages(); // I don't like calling that, maybe figure out a way to animate it collapsing on itself smoothly - this.saveData(); - if (record.delete_data) this.dispatcher.dispatch({ type: 'MESSAGE_DELETE', id: messageId, channelId: channelId, ML2: true }); - else this.dispatcher.dispatch({ type: 'MLV2_FORCE_UPDATE_MESSAGE_CONTENT', id: messageId }); - }, - this.obfuscatedClass('remove') - ); - if (!props.src.startsWith('https://i.clouds.tf/q2vy/r8q6.png')) { - addElement( - 'Hide Image From Log', - () => { - record.message.attachments[attachmentIdx].hidden = true; - element.src = `https://i.clouds.tf/q2vy/r8q6.png#${channelId},${attachmentId}`; - element.width = 200; - }, - this.obfuscatedClass('hide-image') - ); - } else { - addElement( - 'Unhide Image From Log', - () => { - record.message.attachments[attachmentIdx].hidden = false; - const srcFile = `http://localhost:7474/${attachmentId}${record.message.attachments[attachmentIdx].filename.match(/\.[0-9a-z]+$/)[0]}#${channelId},${attachmentId}`; - element.src = record.message.attachments[attachmentIdx].url === 'ERROR' ? srcFile : record.message.attachments[attachmentIdx].url; - element.width = record.message.attachments[attachmentIdx].url === 'ERROR' ? 256 : this.clamp(record.message.attachments[attachmentIdx].width, 200, 650); - }, - this.obfuscatedClass('unhide-image') - ); - } - if (!newItems.length) return; - menu.push(XenoLib.createContextMenuGroup([XenoLib.createContextMenuSubMenu(this.settings.contextmenuSubmenuName, newItems, this.obfuscatedClass('mlv2'))])); - } - ) - ); - } - this.unpatches.push(XenoLib.listenLazyContextMenu('NativeImageContextMenu', nativeImageContextMenuPatch)); - - const messageContextPatch = () => { - const mod = WebpackModules.find(e => e.default && (e.__powercordOriginal_default || e.default).displayName === 'MessageContextMenu'); - if (!mod) return console.error('[MessageLoggerV2] Failed to find MessageContextMenu'); - this.unpatches.push( - this.Patcher.after( - mod, - 'default', - (_, [props], ret) => { - const newItems = []; - const menu = ZeresPluginLibrary.Utilities.getNestedProp( - ZeresPluginLibrary.Utilities.findInReactTree(ret, e => e && e.type && e.type.displayName === 'Menu'), - 'props.children' - ); - if (!Array.isArray(menu)) return; - const addElement = (label, callback, id, options = {}) => newItems.push(XenoLib.createContextMenuItem(label, callback, id, options)); - addElement('Open Logs', () => this.openWindow(), this.obfuscatedClass('open')); - const messageId = props.message.id; - const channelId = props.channel.id; - const record = this.messageRecord[messageId]; - if (record) { - /* - addElement('Show in menu', () => { - this.menu.filter = `message:${messageId}`; - this.openWindow(); - }); */ - if (record.delete_data) { - const options = menu.find(m => m.props.children && m.props.children.length > 10); - options.props.children.splice(0, options.props.children.length); - addElement( - 'Hide Deleted Message', - () => { - this.dispatcher.dispatch({ - type: 'MESSAGE_DELETE', - id: messageId, - channelId: channelId, - ML2: true // ignore ourselves lol, it's already deleted - // on a side note, probably does nothing if we don't ignore - }); - this.showToast('Hidden!', { type: 'success' }); - record.delete_data.hidden = true; - this.saveData(); - }, - this.obfuscatedClass('hide-deleted') - ); - const idx = this.noTintIds.indexOf(messageId); - addElement( - `${idx !== -1 ? 'Add' : 'Remove'} Deleted Tint`, - () => { - if (idx !== -1) this.noTintIds.splice(idx, 1); - else this.noTintIds.push(messageId); - this.showToast(idx !== -1 ? 'Added!' : 'Removed!', { type: 'success' }); - }, - this.obfuscatedClass('change-tint') - ); - } - if (record.edit_history) { - if (record.edits_hidden) { - addElement( - 'Unhide Edits', - () => { - record.edits_hidden = false; - this.saveData(); - this.dispatcher.dispatch({ type: 'MLV2_FORCE_UPDATE_MESSAGE_CONTENT', id: messageId }); - }, - this.obfuscatedClass('unhide-edits') - ); - } else { - let target = props.target; - if (target) { - while (target && target.className && target.className.indexOf(this.style.edited) === -1) { - target = target.parentElement; - } - if (target) { - if (!this.editModifiers[messageId]) { - addElement( - 'Hide Edits', - () => { - record.edits_hidden = true; - this.saveData(); - this.dispatcher.dispatch({ type: 'MLV2_FORCE_UPDATE_MESSAGE_CONTENT', id: messageId }); - }, - this.obfuscatedClass('hide-edits') - ); - } - const editNum = target.getAttribute('editNum'); - if (this.editModifiers[messageId]) { - addElement( - `${this.editModifiers[messageId].noSuffix ? 'Show' : 'Hide'} (edited) Tag`, - () => { - this.editModifiers[messageId].noSuffix = true; - this.dispatcher.dispatch({ type: 'MLV2_FORCE_UPDATE_MESSAGE_CONTENT', id: messageId }); - }, - this.obfuscatedClass('change-edit-tag') - ); - addElement( - `Undo Show As Message`, - () => { - delete this.editModifiers[messageId]; - this.dispatcher.dispatch({ type: 'MLV2_FORCE_UPDATE_MESSAGE_CONTENT', id: messageId }); - }, - this.obfuscatedClass('undo-show-as-message') - ); - } else if (typeof editNum !== 'undefined' && editNum !== null) { - addElement( - 'Show Edit As Message', - () => { - this.editModifiers[messageId] = { editNum }; - this.dispatcher.dispatch({ type: 'MLV2_FORCE_UPDATE_MESSAGE_CONTENT', id: messageId }); - }, - this.obfuscatedClass('show-as-message') - ); - addElement( - 'Delete Edit', - () => { - this.deleteEditedMessageFromRecord(messageId, parseInt(editNum)); - this.dispatcher.dispatch({ type: 'MLV2_FORCE_UPDATE_MESSAGE_CONTENT', id: messageId }); - }, - this.obfuscatedClass('delete-edit'), - { color: 'colorDanger' } - ); - } - } - } - } - } - if (record) { - addElement( - 'Remove From Log', - () => { - this.deleteMessageFromRecords(messageId); - this.saveData(); - if (record.delete_data) { - this.dispatcher.dispatch({ - type: 'MESSAGE_DELETE', - id: messageId, - channelId: channelId, - ML2: true // ignore ourselves lol, it's already deleted - // on a side note, probably does nothing if we don't ignore - }); - } else { - this.dispatcher.dispatch({ type: 'MLV2_FORCE_UPDATE_MESSAGE_CONTENT', id: messageId }); - } - }, - this.obfuscatedClass('remove-from-log'), - { color: 'colorDanger' } - ); - } - } - if (!newItems.length) return; - menu.push(XenoLib.createContextMenuGroup([XenoLib.createContextMenuSubMenu(this.settings.contextmenuSubmenuName, newItems, this.obfuscatedClass('mlv2'))])); - } - ) - ); - return true; - } - this.unpatches.push(XenoLib.listenLazyContextMenu('MessageContextMenu', messageContextPatch)); - - const handleWhiteBlackList_ = (newItems, id) => { - const addElement = (label, callback, id, options = {}) => newItems.push(XenoLib.createContextMenuItem(label, callback, id, options)); - const whitelistIdx = this.settings.whitelist.findIndex(m => m === id); - const blacklistIdx = this.settings.blacklist.findIndex(m => m === id); - if (whitelistIdx == -1 && blacklistIdx == -1) { - addElement( - `Add to Whitelist`, - () => { - this.settings.whitelist.push(id); - this.saveSettings(); - this.showToast('Added!', { type: 'success' }); - }, - this.obfuscatedClass('add-whitelist') - ); - addElement( - `Add to Blacklist`, - () => { - this.settings.blacklist.push(id); - this.saveSettings(); - this.showToast('Added!', { type: 'success' }); - }, - this.obfuscatedClass('add-blacklist') - ); - } else if (whitelistIdx != -1) { - addElement( - `Remove From Whitelist`, - () => { - this.settings.whitelist.splice(whitelistIdx, 1); - this.saveSettings(); - this.showToast('Removed!', { type: 'success' }); - }, - this.obfuscatedClass('remove-whitelist') - ); - addElement( - `Move to Blacklist`, - () => { - this.settings.whitelist.splice(whitelistIdx, 1); - this.settings.blacklist.push(id); - this.saveSettings(); - this.showToast('Moved!', { type: 'success' }); - }, - this.obfuscatedClass('move-blacklist') - ); - } else { - addElement( - `Remove From Blacklist`, - () => { - this.settings.blacklist.splice(blacklistIdx, 1); - this.saveSettings(); - this.showToast('Removed!', { type: 'success' }); - }, - this.obfuscatedClass('remove-blacklist') - ); - addElement( - `Move to Whitelist`, - () => { - this.settings.blacklist.splice(blacklistIdx, 1); - this.settings.whitelist.push(id); - this.saveSettings(); - this.showToast('Moved!', { type: 'success' }); - }, - this.obfuscatedClass('move-whitelist') - ); - } - const notifIdx = this.settings.notificationBlacklist.indexOf(id); - addElement( - `${notifIdx === -1 ? 'Add To' : 'Remove From'} Notification Blacklist`, - () => { - if (notifIdx === -1) this.settings.notificationBlacklist.push(id); - else this.settings.notificationBlacklist.splice(notifIdx, 1); - this.saveSettings(); - this.showToast(notifIdx === -1 ? 'Added!' : 'Removed!', { type: 'success' }); - }, - this.obfuscatedClass('change-notif-blacklist') - ); - }; - - const loggerIdentifier = this.randomString(); - const channelListTextChannelContextMenuPatch = (fmod) => { - const mods = WebpackModules.findAll(e => (e.default === fmod || (e.default && e.default.__originalFunction === fmod)) && (e[loggerIdentifier] === undefined && (e[loggerIdentifier] = true))); - if (!mods) return; - const _this = this; - function ChannelListTextChannelContextMenu(props) { - const ret = props[MLV2_TYPE_L3](props); - try { - if (props.channel && props.channel.type === 4) return ret; // no lol, categories are unsupported - const newItems = []; - const menu = ZeresPluginLibrary.Utilities.getNestedProp( - ZeresPluginLibrary.Utilities.findInReactTree(ret, e => e && e.type && e.type.displayName === 'Menu'), - 'props.children' - ); - if (!Array.isArray(menu)) return ret; - const addElement = (label, callback, id, options = {}) => newItems.push(XenoLib.createContextMenuItem(label, callback, id, options)); - addElement('Open Logs', () => _this.openWindow(), _this.obfuscatedClass('open')); - addElement( - `Open Log For Channel`, - () => { - _this.menu.filter = `channel:${props.channel.id}`; - _this.openWindow(); - }, - _this.obfuscatedClass('open-channel') - ); - handleWhiteBlackList(newItems, props.channel.id); - if (!newItems.length) return ret; - menu.push(XenoLib.createContextMenuGroup([XenoLib.createContextMenuSubMenu(_this.settings.contextmenuSubmenuName, newItems, _this.obfuscatedClass('mlv2'))])); - } catch (err) { - console.error('[MessageLoggerV2] Failed to patch Channel Context Menu', err); - } - return ret; - } - function NormalMenu(props) { - const ret = props[MLV2_TYPE_L2](props); - try { - if (ret.type.displayName !== 'NormalMenu') return ret; - if (!ChannelListTextChannelContextMenu.displayName) Object.assign(ChannelListTextChannelContextMenu, ret.type); - ret.props[MLV2_TYPE_L3] = ret.type; - ChannelListTextChannelContextMenu.__originalFunction = ret.type; - ret.type = ChannelListTextChannelContextMenu; - } catch (err) { - console.error('[MessageLoggerV2] Failed to patch Normal Menu', err); - } - return ret; - } - function ChannelListTextChannelContextMenuWrapper(props) { - const ret = props[MLV2_TYPE_L1](props); - try { - if (!NormalMenu.displayName) Object.assign(NormalMenu, ret.props.children.type); - ret.props.children.props[MLV2_TYPE_L2] = ret.props.children.type; - NormalMenu.__originalFunction = ret.props.children.type; - ret.props.children.type = NormalMenu; - } catch (err) { - console.error('[MessageLoggerV2] Failed to patch ChannelListTextChannelContextMenuWrapper', err); - } - return ret; - } - mods.forEach(mod => { - this.unpatches.push( - this.Patcher.after( - mod, - 'default', - (_, __, ret) => { - const damnedmenu = ret.props.children; - if (damnedmenu.props[MLV2_TYPE_L1]) return; - if (!ChannelListTextChannelContextMenuWrapper.displayName) Object.assign(ChannelListTextChannelContextMenuWrapper, damnedmenu.type); - damnedmenu.props[MLV2_TYPE_L1] = damnedmenu.type; - ChannelListTextChannelContextMenuWrapper.__originalFunction = damnedmenu.type; - damnedmenu.type = ChannelListTextChannelContextMenuWrapper; - } - ) - ) - }); - return true; - } - this.unpatches.push(XenoLib.listenLazyContextMenu('ChannelListTextChannelContextMenu', channelListTextChannelContextMenuPatch, true)); - - const guildContextMenu = () => { - const mod = WebpackModules.find(e => e.default && (e.__powercordOriginal_default || e.default).displayName === 'GuildContextMenuWrapper'); - if (!mod) return console.error('[MessageLoggerV2] GuildContextMenu not found'); - - const _this = this; - function GuildContextMenu(props) { - try { - const ret = props[MLV2_TYPE_L1](props); - - const newItems = []; - const menu = ZeresPluginLibrary.Utilities.getNestedProp( - ZeresPluginLibrary.Utilities.findInReactTree(ret, e => e && e.type && e.type.displayName === 'Menu'), - 'props.children' - ); - if (!Array.isArray(menu)) return; - const addElement = (label, callback, id, options = {}) => newItems.push(XenoLib.createContextMenuItem(label, callback, id, options)); - addElement( - 'Open Logs', - () => { - _this.openWindow(); - }, - _this.obfuscatedClass('open') - ); - addElement( - `Open Log For Guild`, - () => { - _this.menu.filter = `guild:${props.guild.id}`; - _this.openWindow(); - }, - _this.obfuscatedClass('open-guild') - ); - handleWhiteBlackList(newItems, props.guild.id); - if (!newItems.length) return; - menu.push(XenoLib.createContextMenuGroup([XenoLib.createContextMenuSubMenu(_this.settings.contextmenuSubmenuName, newItems, _this.obfuscatedClass('mlv2'))])); - return ret; - } catch (err) { - ZeresPluginLibrary.Logger.warn(_this.getName(), 'Failed to run patch GuildContextMenu', err); - try { - const ret = props[MLV2_TYPE_L1](props); - return ret; - } catch (err) { - ZeresPluginLibrary.Logger.error(_this.getName(), 'Failed to original only GuildContextMenu', err); - return null; - } - } - } - GuildContextMenu.displayName = 'GuildContextMenu'; - this.unpatches.push( - this.Patcher.after( - mod, - 'default', - (_, __, { props: { children } }) => { - if (children.props[MLV2_TYPE_L1]) return; - if (!GuildContextMenu.displayName) Object.assign(GuildContextMenu, children.type); - children.props[MLV2_TYPE_L1] = children.type; - GuildContextMenu.__originalFunction = children.type; - children.type = GuildContextMenu; - } - ) - ); - return true; - } - this.unpatches.push(XenoLib.listenLazyContextMenu('GuildContextMenuWrapper', guildContextMenu)); - - const guildChannelUserContextMenuPatch = (fmod) => { - const mod = WebpackModules.find(e => (e.default === fmod || (e.default && e.default.__originalFunction === fmod))); - if (!mod) return console.error('[MessageLoggerV2] GuildChannelUserContextMenu not found'); - const _this = this; - function GuildChannelUserContextMenu(props) { - const ret = props[MLV2_TYPE_L2](props); - try { - const newItems = []; - const menu = ZeresPluginLibrary.Utilities.getNestedProp( - ZeresPluginLibrary.Utilities.findInReactTree(ret, e => e && e.type && e.type.displayName === 'Menu'), - 'props.children' - ); - if (!Array.isArray(menu)) return ret; - const addElement = (label, callback, id, options = {}) => newItems.push(XenoLib.createContextMenuItem(label, callback, id, options)); - addElement( - 'Open Logs', - () => { - _this.openWindow(); - }, - _this.obfuscatedClass('open') - ); - addElement( - `Open Log For User`, - () => { - _this.menu.filter = `user:${props.user.id}`; - _this.openWindow(); - }, - _this.obfuscatedClass('open-user') - ); - if (!newItems.length) return ret; - menu.push(XenoLib.createContextMenuGroup([XenoLib.createContextMenuSubMenu(_this.settings.contextmenuSubmenuName, newItems, _this.obfuscatedClass('mlv2'))])); - } catch (err) { - console.error(err); - } - return ret; - } - function GuildChannelUserContextMenuWrapper(props) { - const ret = props[MLV2_TYPE_L1](props); - try { - if (ret.props.children.props[MLV2_TYPE_L2]) return ret; - if (!GuildChannelUserContextMenu.displayName) Object.assign(GuildChannelUserContextMenu, ret.props.children.type); - ret.props.children.props[MLV2_TYPE_L2] = ret.props.children.type; - GuildChannelUserContextMenu.__originalFunction = ret.props.children.type; - ret.props.children.type = GuildChannelUserContextMenu; - } catch (err) { - console.error('[MessageLoggerV2] Failed to patch GuildChannelUserContextMenuWrapper', err); - } - return ret; - } - this.unpatches.push( - this.Patcher.after( - mod, - 'default', - (_, __, ret) => { - const damnedmenu = ret.props.children; - if (damnedmenu.props[MLV2_TYPE_L1]) return; - if (!GuildChannelUserContextMenuWrapper.displayName) Object.assign(GuildChannelUserContextMenuWrapper, damnedmenu.type); - damnedmenu.props[MLV2_TYPE_L1] = damnedmenu.type; - GuildChannelUserContextMenuWrapper.__originalFunction = damnedmenu.type; - damnedmenu.type = GuildChannelUserContextMenuWrapper; - } - ) - ); - return true; - } - this.unpatches.push(XenoLib.listenLazyContextMenu('GuildChannelUserContextMenu', guildChannelUserContextMenuPatch)); - - const dmUserContextMenuPatch = (fmod) => { - const mod = WebpackModules.find(e => (e.default === fmod || (e.default && e.default.__originalFunction === fmod))); - if (!mod) return console.error('[MessageLoggerV2] DMUserContextMenu not found'); - const _this = this; - function DMUserContextMenu(props) { - const ret = props[MLV2_TYPE_L2](props); - try { - const newItems = []; - const menu = ZeresPluginLibrary.Utilities.getNestedProp( - ZeresPluginLibrary.Utilities.findInReactTree(ret, e => e && e.type && e.type.displayName === 'Menu'), - 'props.children' - ); - if (!Array.isArray(menu)) return ret; - const addElement = (label, callback, id, options = {}) => newItems.push(XenoLib.createContextMenuItem(label, callback, id, options)); - addElement( - 'Open Logs', - () => { - _this.openWindow(); - }, - _this.obfuscatedClass('open') - ); - addElement( - `Open Log For User`, - () => { - _this.menu.filter = `user:${props.user.id}`; - _this.openWindow(); - }, - _this.obfuscatedClass('open-user') - ); - addElement( - `Open Log For DM`, - () => { - _this.menu.filter = `channel:${props.channel.id}`; - _this.openWindow(); - }, - _this.obfuscatedClass('open-dm') - ); - handleWhiteBlackList(newItems, props.channel.id); - if (!newItems.length) return; - menu.push(XenoLib.createContextMenuGroup([XenoLib.createContextMenuSubMenu(_this.settings.contextmenuSubmenuName, newItems, _this.obfuscatedClass('mlv2'))])); - } catch (err) { - console.error('[MessageLoggerV2] Error in DMUserContextMenu patch', err); - } - return ret; - } - function DMUserContextMenuWrapper(props) { - const ret = props[MLV2_TYPE_L1](props); - try { - if (!DMUserContextMenu.displayName) Object.assign(DMUserContextMenu, ret.props.children.type); - ret.props.children.props[MLV2_TYPE_L2] = ret.props.children.type; - DMUserContextMenu.__originalFunction = ret.props.children.type; - ret.props.children.type = DMUserContextMenu; - } catch (err) { - console.error('[MessageLoggerV2] Failed to patch DMUserContextMenuWrapper', err); - } - return ret; - } - this.unpatches.push( - this.Patcher.after( - mod, - 'default', - (_, __, ret) => { - const damnedmenu = ret.props.children; - if (damnedmenu.props[MLV2_TYPE_L1]) return; - if (!DMUserContextMenuWrapper.displayName) Object.assign(DMUserContextMenuWrapper, damnedmenu.type); - damnedmenu.props[MLV2_TYPE_L1] = damnedmenu.type; - DMUserContextMenuWrapper.__originalFunction = damnedmenu.type; - damnedmenu.type = DMUserContextMenuWrapper; - } - ) - ); - return true; - } - this.unpatches.push(XenoLib.listenLazyContextMenu('DMUserContextMenu', dmUserContextMenuPatch)); - - const groupDMUserContextMenuPatch = (fmod) => { - const mod = WebpackModules.find(e => (e.default === fmod || (e.default && e.default.__originalFunction === fmod))); - if (!mod) return console.error('[MessageLoggerV2] GroupDMUserContextMenu not found'); - const _this = this; - function GroupDMUserContextMenu(props) { - const ret = props[MLV2_TYPE_L2](props); - try { - const newItems = []; - const menu = ZeresPluginLibrary.Utilities.getNestedProp( - ZeresPluginLibrary.Utilities.findInReactTree(ret, e => e && e.type && e.type.displayName === 'Menu'), - 'props.children' - ); - if (!Array.isArray(menu)) return ret; - const addElement = (label, callback, id, options = {}) => newItems.push(XenoLib.createContextMenuItem(label, callback, id, options)); - addElement('Open Logs', () => _this.openWindow(), _this.obfuscatedClass('open')); - addElement( - `Open Log For Channel`, - () => { - _this.menu.filter = `channel:${props.channel.id}`; - _this.openWindow(); - }, - _this.obfuscatedClass('open-channel') - ); - handleWhiteBlackList(newItems, props.channel.id); - if (!newItems.length) return ret; - menu.push(XenoLib.createContextMenuGroup([XenoLib.createContextMenuSubMenu(_this.settings.contextmenuSubmenuName, newItems, _this.obfuscatedClass('mlv2'))])); - } catch (err) { - console.error('[MessageLoggerV2] Error in GroupDMUserContextMenu patch', err); - } - return ret; - } - function GroupDMUserContextMenuWrapper(props) { - const ret = props[MLV2_TYPE_L1](props); - try { - if (!GroupDMUserContextMenu.displayName) Object.assign(GroupDMUserContextMenu, ret.props.children.type); - ret.props.children.props[MLV2_TYPE_L2] = ret.props.children.type; - GroupDMUserContextMenu.__originalFunction = ret.props.children.type; - ret.props.children.type = GroupDMUserContextMenu; - } catch (err) { - console.error('[MessageLoggerV2] Failed to patch GroupDMUserContextMenuWrapper', err); - } - return ret; - } - this.unpatches.push( - this.Patcher.after( - mod, - 'default', - (_, __, ret) => { - const damnedmenu = ret.props.children; - if (damnedmenu.props[MLV2_TYPE_L1]) return; - if (!GroupDMUserContextMenuWrapper.displayName) Object.assign(GroupDMUserContextMenuWrapper, damnedmenu.type); - damnedmenu.props[MLV2_TYPE_L1] = damnedmenu.type; - GroupDMUserContextMenuWrapper.__originalFunction = damnedmenu.type; - damnedmenu.type = GroupDMUserContextMenuWrapper; - } - ) - ); - return true; - }; - this.unpatches.push(XenoLib.listenLazyContextMenu('GroupDMUserContextMenu', groupDMUserContextMenuPatch)); - - } - /* ==================================================-|| END CONTEXT MENU ||-================================================== */ -}; -/*@end @*/ diff --git a/discord/plugins/XenoLib.plugin.js b/discord/plugins/XenoLib.plugin.js deleted file mode 100644 index 56a2923..0000000 --- a/discord/plugins/XenoLib.plugin.js +++ /dev/null @@ -1,2731 +0,0 @@ -/** - * @name XenoLib - * @description Simple library to complement plugins with shared code without lowering performance. Also adds needed buttons to some plugins. - * @author 1Lighty - * @authorId 239513071272329217 - * @version 1.4.11 - * @invite NYvWdN5 - * @donate https://paypal.me/lighty13 - * @source https://github.com/1Lighty/BetterDiscordPlugins/blob/master/Plugins/1XenoLib.plugin.js - * @updateUrl https://raw.githubusercontent.com/1Lighty/BetterDiscordPlugins/master/Plugins/1XenoLib.plugin.js - */ -/*@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.', 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, '1XenoLib.plugin.js'), true); - // Show the user where to put plugins in the future - shell.Exec('explorer ' + pathPlugins); - shell.Popup('I\'m installed!', 0, 'Successfully installed', 0x40); - } - WScript.Quit(); - -@else@*/ -/* - * Copyright © 2019-2022, _Lighty_ - * All rights reserved. - * Code may not be redistributed, modified or otherwise taken without explicit permission. - */ - -// eslint-disable-next-line no-undef -if (window.__XL_waitingForWatcherTimeout && !window.__XL_assumingZLibLoaded) clearTimeout(window.__XL_waitingForWatcherTimeout); - -function _extractMeta(code/* : string */)/* : BDPluginManifest */ { - const [firstLine] = code.split('\n'); - if (firstLine.indexOf('//META') !== -1) return _parseOldMeta(code); - if (firstLine.indexOf('/**') !== -1) return _parseNewMeta(code); - throw new /* ErrorNoStack */Error('No or invalid plugin META header'); -} - -function _parseOldMeta(code/* : string */)/* : BDPluginManifest */ { - const [meta] = code.split('\n'); - const rawMeta = meta.substring(meta.indexOf('//META') + 6, meta.indexOf('*//')); - try { - const parsed = JSON.parse(rawMeta); - if (!parsed.name) throw 'ENONAME'; - parsed.format = 'json'; - return parsed; - } catch (err) { - if (err === 'ENONAME') throw new /* ErrorNoStack */Error('Plugin META header missing name property'); - throw new /* ErrorNoStack */Error('Plugin META header could not be parsed'); - } -} - -function _parseNewMeta(code/* : string */)/* : BDPluginManifest */ { - const ret = {}; - let key = ''; - let value = ''; - try { - const jsdoc = code.substr(code.indexOf('/**') + 3, code.indexOf('*/') - code.indexOf('/**') - 3); - for (let i = 0, lines = jsdoc.split(/[^\S\r\n]*?(?:\r\n|\n)[^\S\r\n]*?\*[^\S\r\n]?/); i < lines.length; i++) { - const line = lines[i]; - if (!line.length) continue; - if (line[0] !== '@' || line[1] === ' ') { - value += ` ${line.replace('\\n', '\n').replace(/^\\@/, '@')}`; - continue; - } - if (key && value) ret[key] = value.trim(); - const spaceSeperator = line.indexOf(' '); - key = line.substr(1, spaceSeperator - 1); - value = line.substr(spaceSeperator + 1); - } - ret[key] = value.trim(); - ret.format = 'jsdoc'; - } catch (err) { - throw new /* ErrorNoStack */Error(`Plugin META header could not be parsed ${err}`); - } - if (!ret.name) throw new /* ErrorNoStack */Error('Plugin META header missing name property'); - return ret; -} - -module.exports = (() => { - const canUseAstraNotifAPI = !!(global.Astra && Astra.n11s && Astra.n11s.n11sApi); - // 1 day interval in milliseconds - const USER_COUNTER_INTERVAL = 1000 * 60 * 60 * 24 * 1; - /* Setup */ - const config = { - main: 'index.js', - info: { - name: 'XenoLib', - authors: [ - { - name: 'Lighty', - discord_id: '239513071272329217', - github_username: '1Lighty', - twitter_username: '' - } - ], - version: '1.4.11', - description: 'Simple library to complement plugins with shared code without lowering performance. Also adds needed buttons to some plugins.', - github: 'https://github.com/1Lighty', - github_raw: 'https://raw.githubusercontent.com/1Lighty/BetterDiscordPlugins/master/Plugins/1XenoLib.plugin.js' - }, - changelog: [ - { - title: 'Fixed', - type: 'fixed', - items: [] - } - ], - defaultConfig: [ - canUseAstraNotifAPI ? {} : { - type: 'category', - id: 'notifications', - name: 'Notification settings', - collapsible: true, - shown: true, - settings: [ - { - name: 'Notification position', - id: 'position', - type: 'position', - value: 'topRight' - }, - { - name: 'Notifications use backdrop-filter', - id: 'backdrop', - type: 'switch', - value: true - }, - { - name: 'Background color', - id: 'backdropColor', - type: 'color', - value: '#3e4346', - options: { - defaultColor: '#3e4346' - } - }, - { - name: 'Timeout resets to 0 when hovered', - id: 'timeoutReset', - type: 'switch', - value: true - } - ] - }, - { - type: 'category', - id: 'addons', - name: 'AddonCard settings', - collapsible: true, - shown: false, - settings: [ - { - name: 'Add extra buttons to specific plugins', - note: 'Disabling this will move the buttons to the bottom of plugin settings (if available)', - id: 'extra', - type: 'switch', - value: true - } - ] - }, - { - type: 'category', - id: 'userCounter', - name: 'User counter settings', - collapsible: true, - shown: false, - settings: [ - { - name: 'Enable user counter', - id: 'enabled', - note: 'Only active after 3 days of enabling this setting', - type: 'switch', - value: true - }, - { - id: 'enableTime', - type: 'timeStatus', - value: 0, - after: 'User counter will be active ', - active: 'User counter is currently active', - inactive: 'User counter is currently inactive', - time: USER_COUNTER_INTERVAL - }, - { - id: 'lastSubmission', - type: 'timeStatus', - value: 0, - after: 'Next user counter submission will be ', - active: 'User counter submission will be submitted on next load', - inactive: 'User counter submissions are inactive', - time: USER_COUNTER_INTERVAL - } - ] - } - ] - }; - - /* Build */ - const buildPlugin = ([Plugin, Api]) => { - const start = performance.now(); - const { Settings, Modals, Utilities, WebpackModules, DiscordModules, ColorConverter, DiscordClasses, ReactTools, ReactComponents, Logger, PluginUpdater, PluginUtilities, Structs } = Api; - const { React, ModalStack, ContextMenuActions, ReactDOM, ChannelStore, GuildStore, UserStore, DiscordConstants, PrivateChannelActions, LayerManager, InviteActions, FlexChild, Titles, Changelog: ChangelogModal, SelectedChannelStore, SelectedGuildStore, Moment } = DiscordModules; - - if (window.__XL_waitingForWatcherTimeout) clearTimeout(window.__XL_waitingForWatcherTimeout); - - try { - //PluginUpdater.checkForUpdate(config.info.name, config.info.version, config.info.github_raw); - } catch (err) { - } - - let CancelledAsync = false; - const DefaultLibrarySettings = {}; - - for (let s = 0; s < config.defaultConfig.length; s++) { - const current = config.defaultConfig[s]; - if (current.type === 'category') { - DefaultLibrarySettings[current.id] = {}; - for (let s = 0; s < current.settings.length; s++) { - const subCurrent = current.settings[s]; - DefaultLibrarySettings[current.id][subCurrent.id] = subCurrent.value; - } - } else DefaultLibrarySettings[current.id] = current.value; - } - const XenoLib = {}; - - if (global.XenoLib) try { - global.XenoLib.shutdown(); - XenoLib._lazyContextMenuListeners = global.XenoLib._lazyContextMenuListeners || []; - } catch (e) { } - if (!XenoLib._lazyContextMenuListeners) XenoLib._lazyContextMenuListeners = []; - XenoLib.shutdown = () => { - try { - Logger.log('Unpatching all'); - Patcher.unpatchAll(); - } catch (e) { - Logger.stacktrace('Failed to unpatch all', e); - } - CancelledAsync = true; - PluginUtilities.removeStyle('XenoLib-CSS'); - try { - const notifWrapper = document.querySelector('.xenoLib-notifications'); - if (notifWrapper) { - ReactDOM.unmountComponentAtNode(notifWrapper); - notifWrapper.remove(); - } - } catch (e) { - Logger.stacktrace('Failed to unmount Notifications component', e); - } - }; - - XenoLib._ = XenoLib.DiscordUtils = window._ || WebpackModules.getByProps('bindAll', 'debounce'); - - XenoLib.loadData = (name, key, defaultData, returnNull) => { - try { - return XenoLib._.mergeWith(defaultData ? Utilities.deepclone(defaultData) : {}, BdApi.getData(name, key), (_, b) => { - if (XenoLib._.isArray(b)) return b; - }); - } catch (err) { - Logger.err(name, 'Unable to load data: ', err); - if (returnNull) return null; - return Utilities.deepclone(defaultData); - } - }; - - // replica of zeres deprecated DiscordAPI - XenoLib.DiscordAPI = { - get userId() { - const user = UserStore.getCurrentUser(); - return user && user.id; - }, - get channelId() { - return SelectedChannelStore.getChannelId(); - }, - get guildId() { - return SelectedGuildStore.getGuildId(); - }, - get user() { - return UserStore.getCurrentUser(); - }, - get channel() { - return ChannelStore.getChannel(this.channelId); - }, - get guild() { - return GuildStore.getGuild(this.guildId); - } - }; - - XenoLib.getClass = (arg, thrw) => { - try { - const args = arg.split(' '); - return WebpackModules.getByProps(...args)[args[args.length - 1]]; - } catch (e) { - if (thrw) throw e; - if (XenoLib.DiscordAPI.userId === '239513071272329217' && !XenoLib.getClass.__warns[arg] || Date.now() - XenoLib.getClass.__warns[arg] > 1000 * 60) { - Logger.warn(`Failed to get class with props ${arg}`, e); - XenoLib.getClass.__warns[arg] = Date.now(); - } - return ''; - } - }; - XenoLib.getSingleClass = (arg, thrw) => { - try { - return XenoLib.getClass(arg, thrw).split(' ')[0]; - } catch (e) { - if (thrw) throw e; - if (XenoLib.DiscordAPI.userId === '239513071272329217' && !XenoLib.getSingleClass.__warns[arg] || Date.now() - XenoLib.getSingleClass.__warns[arg] > 1000 * 60) { - Logger.warn(`Failed to get class with props ${arg}`, e); - XenoLib.getSingleClass.__warns[arg] = Date.now(); - } - return ''; - } - }; - XenoLib.getClass.__warns = {}; - XenoLib.getSingleClass.__warns = {}; - - const NOOP = () => {}; - const NOOP_NULL = () => null; - - const originalFunctionClass = Function; - XenoLib.createSmartPatcher = patcher => { - const createPatcher = patcher => (moduleToPatch, functionName, callback, options = {}) => { - try { - var origDef = moduleToPatch[functionName]; - } catch (err) { - return Logger.error(`Failed to patch ${functionName}`, err); - } - if (origDef && typeof origDef === 'function' && origDef.constructor !== originalFunctionClass) window.Function = origDef.constructor; - - const unpatches = []; - try { - unpatches.push(patcher(moduleToPatch, functionName, callback, options) || NOOP); - } catch (err) { - throw err; - } finally { - 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 { - ...patcher, before: createPatcher(patcher.before), - instead: createPatcher(patcher.instead), - after: createPatcher(patcher.after) - }; - }; - - const Patcher = XenoLib.createSmartPatcher(Api.Patcher); - - const LibrarySettings = XenoLib.loadData(config.info.name, 'settings', DefaultLibrarySettings); - - try { - // 1 week before the API will be enabled. - if (LibrarySettings.userCounter.enabled) { - const { enableTime } = LibrarySettings.userCounter; - let changed = false; - if (enableTime) { - if ((Date.now() - enableTime > USER_COUNTER_INTERVAL) && (Date.now() - LibrarySettings.userCounter.lastSubmission > USER_COUNTER_INTERVAL)) { - LibrarySettings.userCounter.lastSubmission = Date.now(); - changed = true; - require('https').get('https://astranika.com/api/analytics/submit', res => { - res.on('error', () => {}); - }); - } - } else { - LibrarySettings.userCounter.enableTime = Date.now(); - changed = true; - } - if (changed) PluginUtilities.saveSettings(config.info.name, LibrarySettings); - } - } catch (err) { - Logger.stacktrace('Failed to load user counter', err); - } - - PluginUtilities.addStyle( - 'XenoLib-CSS', - ` - .xenoLib-color-picker .xenoLib-button { - min-height: 38px; - width: 34px; - white-space: nowrap; - position: relative; - transition: background-color .2s ease-in-out,color .2s ease-in-out,width .2s ease-in-out; - overflow: hidden; - margin: 4px 4px 4px 0; - padding: 2px 20px; - border-radius: 2px; - } - - .xenoLib-color-picker .xenoLib-button:hover { - width: 128px; - } - - .xenoLib-color-picker .xenoLib-button .xl-text-1SHFy0 { - opacity: 0; - transform: translate3d(200%,0,0); - - } - .xenoLib-color-picker .xenoLib-button:hover .xl-text-1SHFy0 { - opacity: 1; - transform: translateZ(0); - } - .xenoLib-button-icon { - left: 50%; - top: 50%; - position: absolute; - margin-left: -12px; - margin-top: -8px; - width: 24px; - height: 24px; - opacity: 1; - transform: translateZ(0); - transition: opacity .2s ease-in-out,transform .2s ease-in-out,-webkit-transform .2s ease-in-out; - } - .xenoLib-button-icon.xenoLib-revert > svg { - width: 24px; - height: 24px; - } - .xenoLib-button-icon.xenoLib-revert { - margin-top: -12px; - } - .xenoLib-button:hover .xenoLib-button-icon { - opacity: 0; - transform: translate3d(-200%,0,0); - } - .xenoLib-notifications { - position: absolute; - color: white; - width: 100%; - min-height: 100%; - display: flex; - flex-direction: column; - z-index: 1000; - pointer-events: none; - font-size: 14px; - } - .xenoLib-notification { - min-width: 200px; - overflow: hidden; - } - .xenoLib-notification-content-wrapper { - padding: 22px 20px 0 20px; - } - .xenoLib-centering-bottomLeft .xenoLib-notification-content-wrapper:first-of-type, .xenoLib-centering-bottomMiddle .xenoLib-notification-content-wrapper:first-of-type, .xenoLib-centering-bottomRight .xenoLib-notification-content-wrapper:first-of-type { - padding: 0 20px 20px 20px; - } - .xenoLib-notification-content { - padding: 12px; - overflow: hidden; - background: #474747; - pointer-events: all; - position: relative; - width: 20vw; - white-space: break-spaces; - min-width: 330px; - } - .xenoLib-notification-loadbar { - position: absolute; - bottom: 0; - left: 0px; - width: auto; - background-image: linear-gradient(130deg,var(--grad-one),var(--grad-two)); - height: 5px; - } - .xenoLib-notification-loadbar-user { - animation: fade-loadbar-animation 1.5s ease-in-out infinite; - } - @keyframes fade-loadbar-animation { - 0% { - filter: brightness(75%) - } - 50% { - filter: brightness(100%) - } - to { - filter: brightness(75%) - } - } - .xenoLib-notification-loadbar-striped:before { - content: ""; - position: absolute; - width: 100%; - height: 100%; - border-radius: 5px; - background: linear-gradient( - -20deg, - transparent 35%, - var(--bar-color) 35%, - var(--bar-color) 70%, - transparent 70% - ); - animation: shift 1s linear infinite; - background-size: 60px 100%; - box-shadow: inset 0 0px 1px rgba(0, 0, 0, 0.2), - inset 0 -2px 1px rgba(0, 0, 0, 0.2); - } - @keyframes shift { - to { - background-position: 60px 100%; - } - } - .xenoLib-notification-close { - float: right; - padding: 0; - height: unset; - opacity: .7; - } - .xenLib-notification-counter { - float: right; - margin-top: 2px; - } - .option-xenoLib { - position: absolute; - width: 24%; - height: 24%; - margin: 6px; - border-radius: 3px; - opacity: .6; - background-color: #72767d; - cursor: pointer; - overflow: hidden; - text-indent: -999em; - font-size: 0; - line-height: 0; - } - .selected-xenoLib.option-xenoLib { - background-color: var(--brand-experiment); - border-color: var(--brand-experiment); - box-shadow: 0 2px 0 rgba(0,0,0,.3); - opacity: 1; - } - .topLeft-xenoLib { - top: 0; - left: 0; - } - .topRight-xenoLib { - top: 0; - right: 0; - } - .bottomLeft-xenoLib { - bottom: 0; - left: 0; - } - .bottomRight-xenoLib { - bottom: 0; - right: 0; - } - .topMiddle-xenoLib { - top: 0; - left: 0; - right: 0; - margin-left: auto; - margin-right: auto; - } - .bottomMiddle-xenoLib { - bottom: 0; - left: 0; - right: 0; - margin-left: auto; - margin-right: auto; - } - .xenoLib-centering-topLeft, .xenoLib-centering-bottomLeft { - align-items: flex-start; - } - .xenoLib-centering-topMiddle, .xenoLib-centering-bottomMiddle { - align-items: center; - } - .xenoLib-centering-topRight, .xenoLib-centering-bottomRight { - align-items: flex-end; - } - .xenoLib-centering-bottomLeft, .xenoLib-centering-bottomMiddle, .xenoLib-centering-bottomRight { - flex-direction: column-reverse; - bottom: 0; - } - .xenoLib-position-wrapper { - box-sizing: border-box; - position: relative; - background-color: rgba(0,0,0,.1); - padding-bottom: 56.25%; - border-radius: 8px; - border: 2px solid var(--brand-experiment); - } - .xenoLib-position-hidden-input { - opacity: 0; - position: absolute; - top: 0; - cursor: pointer; - } - .XL-chl-p img{ - width: unset !important; - } - .xenoLib-error-text { - padding-top: 5px; - } - - .xenoLib-multiInput { - display: -webkit-box; - display: -ms-flexbox; - display: flex; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - font-size: 16px; - -webkit-box-sizing: border-box; - box-sizing: border-box; - width: 100%; - border-radius: 3px; - color: var(--text-normal); - background-color: var(--deprecated-text-input-bg); - border: 1px solid var(--deprecated-text-input-border); - -webkit-transition: border-color .2s ease-in-out; - transition: border-color .2s ease-in-out; - } - .xenoLib-multiInput.xenoLib-multiInput-focused { - border-color: var(--text-link); - } - .xenoLib-multiInput.xenoLib-multiInput-error { - border-color: hsl(359,calc(var(--saturation-factor, 1)*82.6%),59.4%); - } - .xenoLib-multiInputFirst { - -webkit-box-flex: 1; - -ms-flex-positive: 1; - flex-grow: 1 - } - .xenoLib-multiInputField { - border: none; - background-color: transparent - } - ` - ); - - { - const hasOwn = {}.hasOwnProperty; - - XenoLib.joinClassNames = function classNames(...args/* : VariableClassNamesArgs */)/* : string */ { - const classes = []; - for (let i = 0, len = args.length; i < len; i++) { - const arg = args[i]; - if (!arg) continue; - const argType = typeof arg; - if (argType === 'string' || argType === 'number') classes.push(arg); - else if (Array.isArray(arg)) { - if (arg.length) { - const inner = classNames(...arg); - if (inner) classes.push(inner); - } - // eslint-disable-next-line curly - } else if (argType === 'object') { - if (arg.toString === Object.prototype.toString) for (const key in arg/* as any */) { - if (hasOwn.call(arg, key) && arg[key]) classes.push(key); - } - else classes.push(arg.toString()); - } - } - - return classes.join(' '); - }; - } - - XenoLib.authorId = '239513071272329217'; - XenoLib.supportServerId = '389049952732446731'; - -/* try { - const getUserAsync = WebpackModules.getByProps('getUser', 'acceptAgreements').getUser; - const requestUser = () => - getUserAsync(XenoLib.authorId) - .then(user => (XenoLib.author = user)) - .catch(() => setTimeout(requestUser, 1 * 60 * 1000)); - if (UserStore.getUser(XenoLib.authorId)) XenoLib.author = UserStore.getUser(XenoLib.authorId); - else requestUser(); - } catch (e) { - Logger.stacktrace('Failed to grab author object', e); - } */ - - XenoLib.ReactComponents = {}; - - XenoLib.ReactComponents.ErrorBoundary = class XLErrorBoundary extends React.PureComponent { - constructor(props) { - super(props); - this.state = { hasError: false }; - } - componentDidCatch(err, inf) { - Logger.err(`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; - } - }; - - /* —————————————— Copyright (c) 2022 1Lighty, All rights reserved —————————————— - * - * A utility from Astra - * - * ————————————————————————————————————————————————————————————————————————————— */ - function fakeRenderHook(executor/* : () => void */, options/* : { - preExecutor?(): void - postExecutor?(): void - useCallback?(...args: any[]): any - useContext?(...args: any[]): any - useDebugValue?(...args: any[]): any - useDeferredValue?(...args: any[]): any - useEffect?(...args: any[]): any - useImperativeHandle?(...args: any[]): any - useLayoutEffect?(...args: any[]): any - useMemo?(...args: any[]): any - useMutableSource?(...args: any[]): any - useOpaqueIdentifier?(...args: any[]): any - useReducer?(...args: any[]): any - useRef?(...args: any[]): any - useState?(...args: any[]): any - useTransition?(...args: any[]): any - } */ = {})/* : void */ { - // @ts-ignore - const ReactDispatcher = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentDispatcher.current; - const oUseCallback = ReactDispatcher.useCallback; - const oUseContext = ReactDispatcher.useContext; - const oUseDebugValue = ReactDispatcher.useDebugValue; - const oUseDeferredValue = ReactDispatcher.useDeferredValue; - const oUseEffect = ReactDispatcher.useEffect; - const oUseImperativeHandle = ReactDispatcher.useImperativeHandle; - const oUseLayoutEffect = ReactDispatcher.useLayoutEffect; - const oUseMemo = ReactDispatcher.useMemo; - const oUseMutableSource = ReactDispatcher.useMutableSource; - const oUseOpaqueIdentifier = ReactDispatcher.useOpaqueIdentifier; - const oUseReducer = ReactDispatcher.useReducer; - const oUseRef = ReactDispatcher.useRef; - const oUseState = ReactDispatcher.useState; - const oUseTransition = ReactDispatcher.useTransition; - - ReactDispatcher.useCallback = options.useCallback || (() => () => {}); - ReactDispatcher.useContext = options.useContext || (context => context._currentValue); - ReactDispatcher.useDebugValue = options.useDebugValue || (() => {}); - ReactDispatcher.useDeferredValue = options.useDeferredValue || (val => val); - ReactDispatcher.useEffect = options.useEffect || (() => {}); - ReactDispatcher.useImperativeHandle = options.useImperativeHandle || (() => {}); - ReactDispatcher.useLayoutEffect = options.useLayoutEffect || (() => {}); - ReactDispatcher.useMemo = options.useMemo || (memo => memo()); - ReactDispatcher.useMutableSource = options.useMutableSource || (() => {}); - ReactDispatcher.useOpaqueIdentifier = options.useOpaqueIdentifier || (() => rand()); - ReactDispatcher.useReducer = options.useReducer || ((_, val) => [val, () => {}]); - ReactDispatcher.useRef = options.useRef || (() => ({ current: null })); - ReactDispatcher.useState = options.useState || (() => [null, () => {}]); - ReactDispatcher.useTransition = options.useTransition || (() => [() => {}, true]); - - if (typeof options.preExecutor === 'function') options.preExecutor(); - - let ret/* : any */ = null; - try { - ret = executor(); - } catch (err) { - Logger.error('Error rendering functional component', err); - } - - if (typeof options.postExecutor === 'function') options.postExecutor(); - ReactDispatcher.useCallback = oUseCallback; - ReactDispatcher.useContext = oUseContext; - ReactDispatcher.useDebugValue = oUseDebugValue; - ReactDispatcher.useDeferredValue = oUseDeferredValue; - ReactDispatcher.useEffect = oUseEffect; - ReactDispatcher.useImperativeHandle = oUseImperativeHandle; - ReactDispatcher.useLayoutEffect = oUseLayoutEffect; - ReactDispatcher.useMemo = oUseMemo; - ReactDispatcher.useMutableSource = oUseMutableSource; - ReactDispatcher.useOpaqueIdentifier = oUseOpaqueIdentifier; - ReactDispatcher.useReducer = oUseReducer; - ReactDispatcher.useRef = oUseRef; - ReactDispatcher.useState = oUseState; - ReactDispatcher.useTransition = oUseTransition; - - return ret; - } - - XenoLib.fakeRenderHook = fakeRenderHook; - - const deprecateFunction = (name, advice, ret = undefined) => () => (Logger.warn(`XenoLib.${name} is deprecated! ${advice}`), ret); - - XenoLib.patchContext = deprecateFunction('patchContext', 'Do manual patching of context menus instead.'); - - const CTXMenu = WebpackModules.getByProps('default', 'MenuStyle'); - - class ContextMenuWrapper extends React.PureComponent { - constructor(props) { - super(props); - this.handleOnClose = this.handleOnClose.bind(this); - } - handleOnClose() { - ContextMenuActions.closeContextMenu(); - if (this.props.target instanceof HTMLElement) this.props.target.focus(); - } - render() { - return React.createElement(CTXMenu.default, { onClose: this.handleOnClose, id: 'xenolib-context' }, this.props.menu); - } - } - XenoLib.createSharedContext = (element, menuCreation) => { - if (element.__XenoLib_ContextMenus) element.__XenoLib_ContextMenus.push(menuCreation); - else { - element.__XenoLib_ContextMenus = [menuCreation]; - const oOnContextMenu = element.props.onContextMenu; - element.props.onContextMenu = e => (typeof oOnContextMenu === 'function' && oOnContextMenu(e), ContextMenuActions.openContextMenu(e, _ => React.createElement(XenoLib.ReactComponents.ErrorBoundary, { label: 'CTX Menu' }, React.createElement(ContextMenuWrapper, { menu: element.__XenoLib_ContextMenus.map(m => m()), ..._ })))); - } - }; - - const contextMenuItems = WebpackModules.find(m => m.MenuRadioItem && !m.default); - XenoLib.unpatchContext = deprecateFunction('unpatchContext', 'Manual patching needs manual unpatching'); - XenoLib.createContextMenuItem = (label, action, id, options = {}) => (!contextMenuItems ? null : React.createElement(contextMenuItems.MenuItem, { label, id, action: () => (!options.noClose && ContextMenuActions.closeContextMenu(), action()), ...options })); - XenoLib.createContextMenuSubMenu = (label, children, id, options = {}) => (!contextMenuItems ? null : React.createElement(contextMenuItems.MenuItem, { label, children, id, ...options })); - XenoLib.createContextMenuGroup = (children, options) => (!contextMenuItems ? null : React.createElement(contextMenuItems.MenuGroup, { children, ...options })); - - - const lazyContextMenu = WebpackModules.getByProps('openContextMenuLazy'); - const ConnectedContextMenus = WebpackModules.getByDisplayName('ConnectedContextMenus'); - if (lazyContextMenu && ConnectedContextMenus) { - try { - const ContextMenus = fakeRenderHook(() => ConnectedContextMenus({}).type); - Patcher.instead(ContextMenus.prototype, 'componentDidMount', (_this, _, orig) => { - if (!_this.props.isOpen || !_this.props.renderLazy) return orig(); - const olRenderLazy = _this.props.renderLazy; - _this.props.renderLazy = async () => { - _this.props.renderLazy = olRenderLazy; - const ret = await olRenderLazy(); - if (typeof ret === 'function') try { - const ctxEl = ret(); - let { type } = ctxEl; - let typeOverriden = false; - const deepAnalyticsWrapper = type.toString().search(/\)\(\w\)\.AnalyticsLocationProvider,/) !== -1; - const analyticsWrapper = deepAnalyticsWrapper || type.toString().includes('.CONTEXT_MENU).AnalyticsLocationProvider'); - if (type.toString().includes('objectType') || analyticsWrapper) fakeRenderHook(() => { - const ret = type(ctxEl.props); - if (ret.type.displayName !== 'AnalyticsContext' && !analyticsWrapper) return; - ({ type } = ret.props.children); - typeOverriden = true; - if (deepAnalyticsWrapper) { - const deeperRet = type(ret.props.children.props); - if (deeperRet?.props?.children?.type) ({ type } = deeperRet.props.children); - } - }, { - useState: () => [[], () => {}], - useCallback: e => e - }); - - let changed = false; - for (const { menuNameOrFilter, callback, multi, patchedModules } of [...XenoLib._lazyContextMenuListeners]) { - if (typeof menuNameOrFilter === 'string' && menuNameOrFilter !== type.displayName && (!typeOverriden || menuNameOrFilter !== ctxEl.type.displayName)) continue; - if (typeof menuNameOrFilter === 'function' && !menuNameOrFilter(type) && (!typeOverriden || !menuNameOrFilter(ctxEl.type))) continue; - if (multi && patchedModules.indexOf(type) !== -1) continue; - changed = callback(ctxEl.type) || changed; - if (multi) { - patchedModules.push(type); - continue; - } - XenoLib._lazyContextMenuListeners = XenoLib._lazyContextMenuListeners.filter(l => l.callback !== callback); - } - if (changed) requestAnimationFrame(() => { - olRenderLazy().then(r => _this.setState({ render: r })); - }); - - } catch (err) { - Logger.error('Error rendering lazy context menu', err); - } - return ret; - }; - orig(); - _this.props.renderLazy = olRenderLazy; - }); - } catch (err) { - Logger.err('Error patching ContextMenus', err); - } - XenoLib.listenLazyContextMenu = (menuNameOrFilter, callback, multi) => { - XenoLib._lazyContextMenuListeners = XenoLib._lazyContextMenuListeners || []; - if (!Array.isArray(XenoLib._lazyContextMenuListeners)) XenoLib._lazyContextMenuListeners = []; - XenoLib._lazyContextMenuListeners.push({ menuNameOrFilter, callback, multi, patchedModules: [] }); - return () => { - XenoLib._lazyContextMenuListeners = XenoLib._lazyContextMenuListeners.filter(l => l.callback !== callback); - }; - }; - } else XenoLib.listenLazyContextMenu = (menuNameOrFilter, callback, multi) => { - callback(); - }; - - - try { - XenoLib.ReactComponents.ButtonOptions = WebpackModules.getByProps('BorderColors'); - XenoLib.ReactComponents.Button = XenoLib.ReactComponents.ButtonOptions; - } catch (e) { - Logger.stacktrace('Error getting Button component', e); - } - - const path = require('path'); - const isBBDBeta = typeof window.BDModules !== 'undefined' && !window.require && typeof window.BetterDiscordConfig !== 'undefined' && path.normalize(__dirname).replace(/[\\\/]/g, '/').toLowerCase().indexOf('rd_bd/plugins') !== -1; - // why zere? - if (isBBDBeta) Object.assign(window, require('timers')); - - function patchAddonCardAnyway(manualPatch) { - try { - if (patchAddonCardAnyway.patched) return; - patchAddonCardAnyway.patched = true; - const LinkClassname = XenoLib.joinClassNames(XenoLib.getClass('anchorUnderlineOnHover anchor'), XenoLib.getClass('anchor anchorUnderlineOnHover'), 'bda-author'); - const handlePatch = (_this, _, ret) => { - if (!_this.props.addon || !_this.props.addon.plugin || typeof _this.props.addon.plugin.getAuthor().indexOf('Lighty') === -1) return; - const settingsProps = Utilities.findInReactTree(ret, e => e && e.className === 'plugin-settings'); - if (settingsProps) delete settingsProps.id; - const author = Utilities.findInReactTree(ret, e => e && e.props && typeof e.props.className === 'string' && e.props.className.indexOf('bda-author') !== -1); - if (!author || typeof author.props.children !== 'string' || author.props.children.indexOf('Lighty') === -1) return; - const onClick = () => { - if (XenoLib.DiscordAPI.userId === XenoLib.authorId) return; - PrivateChannelActions.ensurePrivateChannel(XenoLib.DiscordAPI.userId, XenoLib.authorId).then(() => { - PrivateChannelActions.openPrivateChannel(XenoLib.DiscordAPI.userId, XenoLib.authorId); - LayerManager.popLayer(); - }); - }; - if (author.props.children === 'Lighty') { - author.type = 'a'; - author.props.className = LinkClassname; - author.props.onClick = onClick; - } else { - const idx = author.props.children.indexOf('Lighty'); - const pre = author.props.children.slice(0, idx); - const post = author.props.children.slice(idx + 6); - author.props.children = [ - pre, - React.createElement( - 'a', - { - className: LinkClassname, - onClick - }, - 'Lighty' - ), - post - ]; - delete author.props.onClick; - author.props.className = 'bda-author'; - author.type = 'span'; - } - let footerProps = Utilities.findInReactTree(ret, e => e && e.props && typeof e.props.className === 'string' && e.props.className.indexOf('bda-links') !== -1); - if (!footerProps) return; - footerProps = footerProps.props; - if (!Array.isArray(footerProps.children)) footerProps.children = [footerProps.children]; - const findLink = name => Utilities.findInReactTree(footerProps.children, e => e && e.props && e.props.children === name); - const websiteLink = findLink('Website'); - const sourceLink = findLink('Source'); - const supportServerLink = findLink('Support Server'); - footerProps.children = []; - if (websiteLink) { - const { href } = websiteLink.props; - delete websiteLink.props.href; - delete websiteLink.props.target; - websiteLink.props.onClick = () => window.open(href); - footerProps.children.push(websiteLink); - } - if (sourceLink) { - const { href } = sourceLink.props; - delete sourceLink.props.href; - delete sourceLink.props.target; - sourceLink.props.onClick = () => window.open(href); - footerProps.children.push(websiteLink ? ' | ' : null, sourceLink); - } - footerProps.children.push(websiteLink || sourceLink ? ' | ' : null, React.createElement('a', { className: 'bda-link bda-link-website', onClick: e => ContextMenuActions.openContextMenu(e, e => React.createElement(XenoLib.ReactComponents.ErrorBoundary, { label: 'Donate button CTX menu' }, React.createElement(ContextMenuWrapper, { menu: XenoLib.createContextMenuGroup([XenoLib.createContextMenuItem('Paypal', () => window.open('https://paypal.me/lighty13'), 'paypal'), XenoLib.createContextMenuItem('Ko-fi', () => window.open('https://ko-fi.com/lighty_'), 'kofi'), XenoLib.createContextMenuItem('Patreon', () => window.open('https://www.patreon.com/lightyp'), 'patreon')]), ...e }))) }, 'Donate')); - footerProps.children.push(' | ', supportServerLink || React.createElement('a', { className: 'bda-link bda-link-website', onClick: () => (LayerManager.popLayer(), InviteActions.acceptInviteAndTransitionToInviteChannel('NYvWdN5')) }, 'Support Server')); - footerProps.children.push(' | ', React.createElement('a', { className: 'bda-link bda-link-website', onClick: () => (_this.props.addon.plugin.showChangelog ? _this.props.addon.plugin.showChangelog() : Modals.showChangelogModal(`${_this.props.addon.plugin.getName()} Changelog`, _this.props.addon.plugin.getVersion(), _this.props.addon.plugin.getChanges())) }, 'Changelog')); - footerProps = null; - }; - async function patchRewriteCard() { - const component = [...ReactComponents.components.entries()].find(([_, e]) => e.component && e.component.prototype && e.component.prototype.reload && e.component.prototype.showSettings); - const AddonCard = component ? component[1] : await ReactComponents.getComponent('AddonCard', '.bda-slist > .ui-switch-item', e => e.prototype && e.prototype.reload && e.prototype.showSettings); - if (CancelledAsync) return; - const ContentColumn = await ReactComponents.getComponent('ContentColumn', '.content-column'); - class PatchedAddonCard extends AddonCard.component { - render() { - const ret = super.render(); - try { - /* did I mention I am Lighty? */ - handlePatch(this, undefined, ret); - } catch (err) { - Logger.stacktrace('AddonCard patch', err); - } - return ret; - } - } - let firstRender = true; - Patcher.after(ContentColumn.component.prototype, 'render', (_, __, ret) => { - if (!LibrarySettings.addons.extra) return; - const list = Utilities.findInReactTree(ret, e => e && typeof e.className === 'string' && e.className.indexOf('bd-addon-list') !== -1); - if (Utilities.getNestedProp(list, 'children.0.props.children.type') !== AddonCard.component) return; - for (const item of list.children) { - const card = Utilities.getNestedProp(item, 'props.children'); - if (!card) continue; - card.type = PatchedAddonCard; - } - if (!firstRender) return; - ret.key = DiscordModules.KeyGenerator(); - firstRender = false; - }); - if (manualPatch) return; - ContentColumn.forceUpdateAll(); - AddonCard.forceUpdateAll(); - } - patchRewriteCard(); - } catch (e) { - Logger.stacktrace('Failed to patch V2C_*Card or AddonCard (BBD rewrite)', e); - } - } - if (LibrarySettings.addons.extra) patchAddonCardAnyway(); - - try { - XenoLib.ReactComponents.PluginFooter = class XLPluginFooter extends React.PureComponent { - render() { - if (LibrarySettings.addons.extra) return null; - return React.createElement( - 'div', - { - style: { - display: 'flex' - } - }, - React.createElement( - XenoLib.ReactComponents.Button, - { - style: { - flex: '2 1 auto' - }, - onClick: this.props.showChangelog - }, - 'Changelog' - ), - React.createElement( - XenoLib.ReactComponents.Button, - { - style: { - flex: '2 1 auto' - }, - onClick: e => ContextMenuActions.openContextMenu(e, e => React.createElement(XenoLib.ReactComponents.ErrorBoundary, { label: 'Donate button CTX menu' }, React.createElement(ContextMenuWrapper, { menu: XenoLib.createContextMenuGroup([XenoLib.createContextMenuItem('Paypal', () => window.open('https://paypal.me/lighty13'), 'paypal'), XenoLib.createContextMenuItem('Ko-fi', () => window.open('https://ko-fi.com/lighty_'), 'kofi'), XenoLib.createContextMenuItem('Patreon', () => window.open('https://www.patreon.com/lightyp'), 'patreon')]), ...e }))) - }, - 'Donate' - ), - React.createElement( - XenoLib.ReactComponents.Button, - { - style: { - flex: '2 1 auto' - }, - onClick: () => (LayerManager.popLayer(), InviteActions.acceptInviteAndTransitionToInviteChannel('NYvWdN5')) - }, - 'Support server' - ) - ); - } - }; - } catch (err) { - Logger.stacktrace('Error creating plugin footer'); - XenoLib.ReactComponents.PluginFooter = NOOP_NULL; - } - - const TextElement = WebpackModules.getByDisplayName('Text') || WebpackModules.find(e => e.Text?.displayName === 'Text')?.Text; - - /* shared between FilePicker and ColorPicker */ - const MultiInputClassname = 'xenoLib-multiInput'; - const MultiInputFirstClassname = 'xenoLib-multiInputFirst'; - const MultiInputFieldClassname = 'xenoLib-multiInputField'; - const ErrorMessageClassname = XenoLib.joinClassNames('xenoLib-error-text', XenoLib.getClass('errorMessage'), Utilities.getNestedProp(TextElement, 'Colors.ERROR')); - const ErrorClassname = XenoLib.joinClassNames('xenoLib-multiInput-error', XenoLib.getClass('input error')); - - try { - class DelayedCall { - constructor(delay, callback) { - this.delay = delay; - this.callback = callback; - this.timeout = null; - } - - delay() { - clearTimeout(this.timeout); - this.timeout = setTimeout(this.callback, this.delay); - } - } - const FsModule = require('fs'); - /** - * @interface - * @name module:FilePicker - * @property {string} path - * @property {string} placeholder - * @property {Function} onChange - * @property {object} properties - * @property {bool} nullOnInvalid - * @property {bool} saveOnEnter - */ - XenoLib.ReactComponents.FilePicker = class FilePicker extends React.PureComponent { - constructor(props) { - super(props); - this.state = { - multiInputFocused: false, - path: props.path, - error: null - }; - XenoLib._.bindAll(this, ['handleOnBrowse', 'handleChange', 'checkInvalidDir']); - this.handleKeyDown = XenoLib._.throttle(this.handleKeyDown.bind(this), 500); - this.delayedCallVerifyPath = new DelayedCall(500, () => this.checkInvalidDir()); - } - checkInvalidDir(doSave) { - FsModule.access(this.state.path, FsModule.constants.W_OK, error => { - const invalid = (error && error.message.match(/.*: (.*), access '/)[1]) || null; - this.setState({ error: invalid }); - if (this.props.saveOnEnter && !doSave) return; - if (invalid) this.props.onChange(this.props.nullOnInvalid ? null : ''); - else this.props.onChange(this.state.path); - }); - } - handleOnBrowse() { - DiscordNative.fileManager.showOpenDialog({ title: this.props.title, properties: this.props.properties }).then(({ filePaths: [path] }) => { - if (path) this.handleChange(path); - }); - } - handleChange(path) { - this.setState({ path }); - this.delayedCallVerifyPath.delay(); - } - handleKeyDown(e) { - if (!this.props.saveOnEnter || e.which !== DiscordConstants.KeyboardKeys.ENTER) return; - this.checkInvalidDir(true); - } - render() { - const n = {}; - n['xenoLib-multiInput-focused'] = this.state.multiInputFocused; - n[ErrorClassname] = !!this.state.error; - return React.createElement( - 'div', - { className: DiscordClasses.BasicInputs.inputWrapper, style: { width: '100%' } }, - React.createElement( - 'div', - { className: XenoLib.joinClassNames(MultiInputClassname, n) }, - React.createElement(DiscordModules.Textbox, { - value: this.state.path, - placeholder: this.props.placeholder, - onChange: this.handleChange, - onFocus: () => this.setState({ multiInputFocused: true }), - onBlur: () => this.setState({ multiInputFocused: false }), - onKeyDown: this.handleKeyDown, - autoFocus: false, - className: MultiInputFirstClassname, - inputClassName: MultiInputFieldClassname - }), - React.createElement(XenoLib.ReactComponents.Button, { onClick: this.handleOnBrowse, color: (!!this.state.error && XenoLib.ReactComponents.ButtonOptions.ButtonColors.RED) || XenoLib.ReactComponents.ButtonOptions.ButtonColors.GREY, look: XenoLib.ReactComponents.ButtonOptions.ButtonLooks.GHOST, size: XenoLib.ReactComponents.Button.Sizes.MEDIUM }, 'Browse') - ), - !!this.state.error && React.createElement('div', { className: ErrorMessageClassname, style: { color: 'hsl(359,calc(var(--saturation-factor, 1)*82.6%),59.4%)' } }, 'Error: ', this.state.error) - ); - } - }; - } catch (e) { - Logger.stacktrace('Failed to create FilePicker component', e); - } - - /** - * @param {string} name - name label of the setting - * @param {string} note - help/note to show underneath or above the setting - * @param {string} value - current hex color - * @param {callable} onChange - callback to perform on setting change, callback receives hex string - * @param {object} [options] - object of options to give to the setting - * @param {boolean} [options.disabled=false] - should the setting be disabled - * @param {Array} [options.colors=presetColors] - preset list of colors - * @author Zerebos, from his library ZLibrary - */ - const FormItem = WebpackModules.getByDisplayName('FormItem'); - - const ColorPickerComponent = (_ => { - try { - return null; - return fakeRenderHook(() => { - const GSRED = WebpackModules.getByDisplayName('GuildSettingsRolesEditDisplay'); - const ret = GSRED({ role: { id: '' }, guild: { id: '' } }); - const cpfi = Utilities.findInReactTree(ret, e => e && e.type && e.type.displayName === 'ColorPickerFormItem').type; - const ret2 = cpfi({ role: { color: '' } }); - const ColorPicker = Utilities.findInReactTree(ret2, e => e && e.props && e.props.colors).type; - return ColorPicker; - }); - } catch (err) { - Logger.stacktrace('Failed to get lazy colorpicker, unsurprisingly', err); - return _ => null; - } - })(); - - const ModalStuff = WebpackModules.getByProps('ModalRoot'); - - class ColorPickerModal extends React.PureComponent { - constructor(props) { - super(props); - this.state = { value: props.value }; - XenoLib._.bindAll(this, ['handleChange']); - } - handleChange(value) { - this.setState({ value }); - this.props.onChange(ColorConverter.int2hex(value)); - } - render() { - return React.createElement( - ModalStuff.ModalRoot, - { tag: 'form', onSubmit: this.handleSubmit, size: '', transitionState: this.props.transitionState }, - React.createElement( - ModalStuff.ModalContent, - {}, - React.createElement( - FormItem, - { className: XenoLib.joinClassNames(DiscordClasses.Margins.marginTop20.value, DiscordClasses.Margins.marginBottom20.value) }, - React.createElement(ColorPickerComponent, { - defaultColor: this.props.defaultColor, - colors: [16711680, 16746496, 16763904, 13434624, 65314, 65484, 61183, 43775, 26367, 8913151, 16711918, 16711782, 11730944, 11755264, 11767552, 9417472, 45848, 45967, 42931, 30643, 18355, 6226099, 11731111, 11731015], - value: this.state.value, - onChange: this.handleChange - }) - ) - ) - ); - } - } - const NewModalStack = WebpackModules.getByProps('openModal', 'hasModalOpen'); - - const ExtraButtonClassname = 'xenoLib-button'; - const TextClassname = 'xl-text-1SHFy0'; - const DropperIcon = React.createElement('svg', { width: 16, height: 16, viewBox: '0 0 16 16' }, React.createElement('path', { d: 'M14.994 1.006C13.858-.257 11.904-.3 10.72.89L8.637 2.975l-.696-.697-1.387 1.388 5.557 5.557 1.387-1.388-.697-.697 1.964-1.964c1.13-1.13 1.3-2.985.23-4.168zm-13.25 10.25c-.225.224-.408.48-.55.764L.02 14.37l1.39 1.39 2.35-1.174c.283-.14.54-.33.765-.55l4.808-4.808-2.776-2.776-4.813 4.803z', fill: 'currentColor' })); - const ClockReverseIcon = React.createElement('svg', { width: 16, height: 16, viewBox: '0 0 24 24' }, React.createElement('path', { d: 'M13,3 C8.03,3 4,7.03 4,12 L1,12 L4.89,15.89 L4.96,16.03 L9,12 L6,12 C6,8.13 9.13,5 13,5 C16.87,5 20,8.13 20,12 C20,15.87 16.87,19 13,19 C11.07,19 9.32,18.21 8.06,16.94 L6.64,18.36 C8.27,19.99 10.51,21 13,21 C17.97,21 22,16.97 22,12 C22,7.03 17.97,3 13,3 L13,3 Z M12,8 L12,13 L16.28,15.54 L17,14.33 L13.5,12.25 L13.5,8 L12,8 L12,8 Z', fill: 'currentColor' })); - class ColorPicker extends React.PureComponent { - constructor(props) { - super(props); - this.state = { - error: null, - value: props.value, - multiInputFocused: false - }; - XenoLib._.bindAll(this, ['handleChange', 'handleColorPicker', 'handleReset']); - } - handleChange(value) { - if (!value.length) this.state.error = 'You must input a hex string'; - else if (!ColorConverter.isValidHex(value)) this.state.error = 'Invalid hex string'; - else this.state.error = null; - - this.setState({ value }); - this.props.onChange(!value.length || !ColorConverter.isValidHex(value) ? this.props.defaultColor : value); - } - handleColorPicker() { - const modalId = NewModalStack.openModal(e => React.createElement(XenoLib.ReactComponents.ErrorBoundary, { label: 'color picker modal', onError: () => NewModalStack.closeModal(modalId) }, React.createElement(ColorPickerModal, { ...e, defaultColor: ColorConverter.hex2int(this.props.defaultColor), value: ColorConverter.hex2int(this.props.value), onChange: this.handleChange }))); - } - handleReset() { - this.handleChange(this.props.defaultColor); - } - render() { - const n = {}; - n['xenoLib-multiInput-focused'] = this.state.multiInputFocused; - n[ErrorClassname] = !!this.state.error; - return React.createElement( - 'div', - { className: XenoLib.joinClassNames(DiscordClasses.BasicInputs.inputWrapper.value, 'xenoLib-color-picker'), style: { width: '100%' } }, - React.createElement( - 'div', - { className: XenoLib.joinClassNames(MultiInputClassname, n) }, - React.createElement('div', { - className: XenoLib.ReactComponents.Button.Sizes.SMALL, - style: { - backgroundColor: this.state.value, - height: 38 - } - }), - React.createElement(DiscordModules.Textbox, { - value: this.state.value, - placeholder: 'Hex color', - onChange: this.handleChange, - onFocus: () => this.setState({ multiInputFocused: true }), - onBlur: () => this.setState({ multiInputFocused: false }), - autoFocus: false, - className: MultiInputFirstClassname, - inputClassName: MultiInputFieldClassname - }), - React.createElement( - XenoLib.ReactComponents.Button, - { - onClick: this.handleColorPicker, - color: (!!this.state.error && XenoLib.ReactComponents.ButtonOptions.ButtonColors.RED) || XenoLib.ReactComponents.ButtonOptions.ButtonColors.GREY, - look: XenoLib.ReactComponents.ButtonOptions.ButtonLooks.GHOST, - size: XenoLib.ReactComponents.Button.Sizes.MIN, - className: ExtraButtonClassname - }, - React.createElement('span', { className: TextClassname }, 'Color picker'), - React.createElement( - 'span', - { - className: 'xenoLib-button-icon' - }, - DropperIcon - ) - ), - React.createElement( - XenoLib.ReactComponents.Button, - { - onClick: this.handleReset, - color: (!!this.state.error && XenoLib.ReactComponents.ButtonOptions.ButtonColors.RED) || XenoLib.ReactComponents.ButtonOptions.ButtonColors.GREY, - look: XenoLib.ReactComponents.ButtonOptions.ButtonLooks.GHOST, - size: XenoLib.ReactComponents.Button.Sizes.MIN, - className: ExtraButtonClassname - }, - React.createElement('span', { className: TextClassname }, 'Reset'), - React.createElement( - 'span', - { - className: 'xenoLib-button-icon xenoLib-revert' - }, - ClockReverseIcon - ) - ) - ), - !!this.state.error && React.createElement('div', { className: ErrorMessageClassname, style: { color: 'hsl(359,calc(var(--saturation-factor, 1)*82.6%),59.4%)' } }, 'Error: ', this.state.error) - ); - } - } - XenoLib.Settings = {}; - XenoLib.Settings.FilePicker = class FilePickerSettingField extends Settings.SettingField { - constructor(name, note, value, onChange, options = { properties: ['openDirectory', 'createDirectory'], placeholder: 'Path to folder', defaultPath: '' }) { - super(name, note, onChange, XenoLib.ReactComponents.FilePicker || class b { }, { - onChange: reactElement => path => { - this.onChange(path ? path : options.defaultPath); - }, - path: value, - nullOnInvalid: true, - ...options - }); - } - }; - XenoLib.Settings.ColorPicker = class ColorPickerSettingField extends Settings.SettingField { - constructor(name, note, value, onChange, options = {}) { - super(name, note, onChange, ColorPicker, { - disabled: !!options.disabled, - onChange: reactElement => color => { - this.onChange(color); - }, - defaultColor: typeof options.defaultColor !== 'undefined' ? options.defaultColor : ColorConverter.int2hex(DiscordConstants.DEFAULT_ROLE_COLOR), - value - }); - } - }; - - XenoLib.Settings.PluginFooter = class PluginFooterField extends Settings.SettingField { - constructor(showChangelog) { - super('', '', NOOP, XenoLib.ReactComponents.PluginFooter, { - showChangelog - }); - } - }; - - XenoLib.changeName = (currentName, newName) => { - try { - const path = require('path'); - const fs = require('fs'); - const pluginsFolder = path.dirname(currentName); - const pluginName = path.basename(currentName).match(/^[^\.]+/)[0]; - if (pluginName === newName) return true; - const wasEnabled = BdApi.Plugins && BdApi.Plugins.isEnabled ? BdApi.Plugins.isEnabled(pluginName) : global.pluginCookie && pluginCookie[pluginName]; - fs.accessSync(currentName, fs.constants.W_OK | fs.constants.R_OK); - const files = fs.readdirSync(pluginsFolder); - files.forEach(file => { - if (!file.startsWith(pluginName) || file.startsWith(newName) || file.indexOf('.plugin.js') !== -1) return; - fs.renameSync(path.resolve(pluginsFolder, file), path.resolve(pluginsFolder, `${newName}${file.match(new RegExp(`^${pluginName}(.*)`))[1]}`)); - }); - fs.renameSync(currentName, path.resolve(pluginsFolder, `${newName}.plugin.js`)); - XenoLib.Notifications.success(`[**XenoLib**] \`${pluginName}\` file has been renamed to \`${newName}\``); - if ((!BdApi.Plugins || !BdApi.Plugins.isEnabled || !BdApi.Plugins.enable) && (!global.pluginCookie || !global.pluginModule)) BdApi.showConfirmationModal('Plugin has been renamed', 'Plugin has been renamed, but your client mod has a missing feature, as such, the plugin could not be enabled (if it even was enabled).'); - else { - if (!wasEnabled) return; - setTimeout(() => (BdApi.Plugins && BdApi.Plugins.enable ? BdApi.Plugins.enable(newName) : pluginModule.enablePlugin(newName)), 1000); /* /shrug */ - } - } catch (e) { - Logger.stacktrace('There has been an issue renaming a plugin', e); - } - }; - - const FancyParser = (() => { - const Markdown = WebpackModules.getByProps('astParserFor', 'parse'); - try { - const { default: DeepClone } = WebpackModules.find(m => { - if (!m.default || m.useVirtualizedAnchor || typeof m.default !== 'function') return false; - const toString = m.default.toString(); - return toString.indexOf('/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(') !== -1 && toString.search(/\w\({},\w\[\w\],{},\w\[\w\]\)/) !== -1; - }); - // SOOO much more extra code with zeres lib compared to Astra, maybe I just can't figure out how to use it effectively - const ReactParserRules = WebpackModules.find(m => typeof m === 'function' && (m = m.toString()) && (m.toString().replace(/\n/g, '').search(/^function\(\w\){return \w\({},\w,{link:\(0,\w.default\)\(\w\),emoji:\(\w=\w,\w=\w\.emojiTooltipPosition,\w=void 0===\w?\w/) !== -1)); - const FANCY_PANTS_PARSER_RULES = DeepClone([WebpackModules.getByProps('RULES').RULES, ReactParserRules({}), { mention: WebpackModules.find(e => e.Z && e.Z.react).Z }]); - const { defaultRules } = WebpackModules.getByProps('defaultParse'); - FANCY_PANTS_PARSER_RULES.image = defaultRules.image; - FANCY_PANTS_PARSER_RULES.link = defaultRules.link; - return Markdown.reactParserFor(FANCY_PANTS_PARSER_RULES); - } catch (e) { - //Logger.stacktrace('Failed to create special parser', e); - try { - return Markdown.parse; - } catch (e) { - Logger.stacktrace('Failed to get even basic parser', e); - return e => e; - } - } - })(); - - const AnchorClasses = WebpackModules.getByProps('anchor', 'anchorUnderlineOnHover') || {}; - const EmbedVideo = (() => { - return NOOP_NULL; - try { - return WebpackModules.getByProps('EmbedVideo').EmbedVideo; - } catch (e) { - Logger.stacktrace('Failed to get EmbedVideo!', e); - return NOOP_NULL; - } - })(); - const VideoComponent = (() => { - return NOOP_NULL; - try { - const ret = new (WebpackModules.getByDisplayName('MediaPlayer'))({}).render(); - const vc = Utilities.findInReactTree(ret, e => e && e.props && typeof e.props.className === 'string' && e.props.className.indexOf('video-2HW4jD') !== -1); - return vc.type; - } catch (e) { - Logger.stacktrace('Failed to get the video component', e); - return NOOP_NULL; - } - })(); - const ComponentRenderers = WebpackModules.getByProps('renderVideoComponent') || {}; - /* MY CHANGELOG >:C */ - XenoLib.showChangelog = (title, version, changelog, footer, showDisclaimer) => { - return; - const ChangelogClasses = DiscordClasses.Changelog; - const items = []; - let isFistType = true; - for (let i = 0; i < changelog.length; i++) { - const item = changelog[i]; - switch (item.type) { - case 'image': - items.push(React.createElement('img', { alt: '', src: item.src, width: item.width || 451, height: item.height || 254 })); - continue; - case 'video': - items.push(React.createElement(VideoComponent, { src: item.src, poster: item.thumbnail, width: item.width || 451, height: item.height || 254, loop: item.loop || !0, muted: item.muted || !0, autoPlay: item.autoplay || !0, className: ChangelogClasses.video })); - continue; - case 'youtube': - items.push(React.createElement(EmbedVideo, { className: ChangelogClasses.video, allowFullScreen: !1, href: `https://youtu.be/${item.youtube_id}`, thumbnail: { url: `https://i.ytimg.com/vi/${item.youtube_id}/maxresdefault.jpg`, width: item.width || 451, height: item.height || 254 }, video: { url: `https://www.youtube.com/embed/${item.youtube_id}?vq=large&rel=0&controls=0&showinfo=0`, width: item.width || 451, height: item.height || 254 }, width: item.width || 451, height: item.height || 254, renderVideoComponent: ComponentRenderers.renderVideoComponent || NOOP_NULL, renderImageComponent: ComponentRenderers.renderImageComponent || NOOP_NULL, renderLinkComponent: ComponentRenderers.renderMaskedLinkComponent || NOOP_NULL })); - continue; - case 'description': - items.push(React.createElement('p', {}, FancyParser(item.content))); - continue; - default: - const logType = ChangelogClasses[item.type] || ChangelogClasses.added; - items.push(React.createElement('h1', { className: XenoLib.joinClassNames(logType.value, { [ChangelogClasses.marginTop.value]: item.marginTop || isFistType }) }, item.title)); - items.push(React.createElement( - 'ul', - { className: 'XL-chl-p' }, - item.items.map(e => - React.createElement( - 'li', - {}, - React.createElement( - 'p', - {}, - Array.isArray(e) - ? e.map(e => - (Array.isArray(e) - ? React.createElement( - 'ul', - {}, - e.map(e => React.createElement('li', {}, FancyParser(e))) - ) - : FancyParser(e))) - : FancyParser(e) - ) - )) - )); - isFistType = false; - } - } - const renderFooter = () => ['Need support? ', React.createElement('a', { className: XenoLib.joinClassNames(AnchorClasses.anchor, AnchorClasses.anchorUnderlineOnHover), onClick: () => Modals.showConfirmationModal('Please confirm', 'Are you sure you want to join my support server?', { confirmText: 'Yes', cancelText: 'Nope', onConfirm: () => (LayerManager.popLayer(), ModalStack.pop(), NewModalStack.closeAllModals(), InviteActions.acceptInviteAndTransitionToInviteChannel('NYvWdN5')) }) }, 'Join my support server'), '! Or consider donating via ', React.createElement('a', { className: XenoLib.joinClassNames(AnchorClasses.anchor, AnchorClasses.anchorUnderlineOnHover), onClick: () => window.open('https://paypal.me/lighty13') }, 'Paypal'), ', ', React.createElement('a', { className: XenoLib.joinClassNames(AnchorClasses.anchor, AnchorClasses.anchorUnderlineOnHover), onClick: () => window.open('https://ko-fi.com/lighty_') }, 'Ko-fi'), ', ', React.createElement('a', { className: XenoLib.joinClassNames(AnchorClasses.anchor, AnchorClasses.anchorUnderlineOnHover), onClick: () => window.open('https://www.patreon.com/lightyp') }, 'Patreon'), '!', showDisclaimer ? '\nBy using these plugins, you agree to being part of the anonymous user counter, unless disabled in settings.' : '']; - NewModalStack.openModal(props => React.createElement(XenoLib.ReactComponents.ErrorBoundary, { label: 'Changelog', onError: () => props.onClose() }, React.createElement(ChangelogModal, { className: ChangelogClasses.container, selectable: true, onScroll: _ => _, onClose: _ => _, renderHeader: () => React.createElement(FlexChild.Child, { grow: 1, shrink: 1 }, React.createElement(Titles.default, { tag: Titles.Tags.H4 }, title), React.createElement(TextElement, { size: TextElement?.Sizes?.SIZE_12, variant: 'text-xs/normal', className: ChangelogClasses.date }, `Version ${version}`)), renderFooter: () => React.createElement(FlexChild.Child, { gro: 1, shrink: 1 }, React.createElement(TextElement, { size: TextElement?.Sizes?.SIZE_12, variant: 'text-xs/normal' }, footer ? (typeof footer === 'string' ? FancyParser(footer) : footer) : renderFooter())), children: items, ...props }))); - }; - - /* https://github.com/react-spring/zustand - * MIT License - * - * Copyright (c) 2019 Paul Henschel - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - XenoLib.zustand = createState => { - let state; - const listeners = new Set(); - const setState = partial => { - const partialState = typeof partial === 'function' ? partial(state) : partial; - if (partialState !== state) { - state = { ...state, ...partialState }; - listeners.forEach(listener => listener()); - } - }; - const getState = () => state; - const getSubscriber = (listener, selector, equalityFn) => { - if (selector === void 0) selector = getState; - if (equalityFn === void 0) equalityFn = Object.is; - return { currentSlice: selector(state), equalityFn, errored: false, listener, selector, unsubscribe: function unsubscribe() { } }; - }; - const subscribe = function subscribe(subscriber) { - function listener() { - // Selector or equality function could throw but we don't want to stop - // the listener from being called. - // https://github.com/react-spring/zustand/pull/37 - try { - const newStateSlice = subscriber.selector(state); - if (!subscriber.equalityFn(subscriber.currentSlice, newStateSlice)) subscriber.listener((subscriber.currentSlice = newStateSlice)); - } catch (error) { - subscriber.errored = true; - subscriber.listener(null, error); - } - } - - listeners.add(listener); - return () => listeners.delete(listener); - }; - const apiSubscribe = (listener, selector, equalityFn) => subscribe(getSubscriber(listener, selector, equalityFn)); - const destroy = () => listeners.clear(); - const useStore = (selector, equalityFn) => { - if (selector === void 0) selector = getState; - if (equalityFn === void 0) equalityFn = Object.is; - const forceUpdate = React.useReducer(c => c + 1, 0)[1]; - const subscriberRef = React.useRef(); - if (!subscriberRef.current) { - subscriberRef.current = getSubscriber(forceUpdate, selector, equalityFn); - subscriberRef.current.unsubscribe = subscribe(subscriberRef.current); - } - const subscriber = subscriberRef.current; - let newStateSlice; - let hasNewStateSlice = false; // The selector or equalityFn need to be called during the render phase if - // they change. We also want legitimate errors to be visible so we re-run - // them if they errored in the subscriber. - if (subscriber.selector !== selector || subscriber.equalityFn !== equalityFn || subscriber.errored) { - // Using local variables to avoid mutations in the render phase. - newStateSlice = selector(state); - hasNewStateSlice = !equalityFn(subscriber.currentSlice, newStateSlice); - } // Syncing changes in useEffect. - React.useLayoutEffect(() => { - if (hasNewStateSlice) subscriber.currentSlice = newStateSlice; - subscriber.selector = selector; - subscriber.equalityFn = equalityFn; - subscriber.errored = false; - }); - React.useLayoutEffect(() => subscriber.unsubscribe, []); - return hasNewStateSlice ? newStateSlice : subscriber.currentSlice; - }; - const api = { setState, getState, subscribe: apiSubscribe, destroy }; - state = createState(setState, getState, api); - return [useStore, api]; - }; - /* NOTIFICATIONS START */ - let UPDATEKEY = {}; - const notificationEvents = canUseAstraNotifAPI ? null : new (require('events').EventEmitter)(); - if (!canUseAstraNotifAPI) notificationEvents.dispatch = data => notificationEvents.emit(data.type, data); - try { - if (canUseAstraNotifAPI) { - const defaultOptions = { - loading: false, - progress: -1, - channelId: undefined, - timeout: 3500, - color: '#2196f3', - onLeave: NOOP - }; - const utils = { - success(content, options = {}) { - return this.show(content, { color: '#43b581', ...options }); - }, - info(content, options = {}) { - return this.show(content, { color: '#4a90e2', ...options }); - }, - warning(content, options = {}) { - return this.show(content, { color: '#ffa600', ...options }); - }, - danger(content, options = {}) { - return this.show(content, { color: '#f04747', ...options }); - }, - error(content, options = {}) { - return this.danger(content, options); - }, - /** - * @param {string|HTMLElement|React} content - Content to display. If it's a string, it'll be formatted with markdown, including URL support [like this](https://google.com/) - * @param {object} options - * @param {string} [options.channelId] Channel ID if content is a string which gets formatted, and you want to mention a role for example. - * @param {Number} [options.timeout] Set to 0 to keep it permanently until user closes it, or if you want a progress bar - * @param {Boolean} [options.loading] Makes the bar animate differently instead of fading in and out slowly - * @param {Number} [options.progress] 0-100, -1 sets it to 100%, setting it to 100% closes the notification automatically - * @param {string} [options.color] Bar color - * @param {string} [options.allowDuplicates] By default, notifications that are similar get grouped together, use true to disable that - * @param {function} [options.onLeave] Callback when notification is leaving - * @return {Number} - Notification ID. Store this if you plan on force closing it, changing its content or want to set the progress - */ - show(content, options = {}) { - const { timeout, loading, progress, color, allowDuplicates, onLeave, channelId, onClick, onContext, onMiddleClick } = Object.assign(Utilities.deepclone(defaultOptions), options); - return Astra.n11s.show(content instanceof HTMLElement ? ReactTools.createWrappedElement(content) : content, { - timeout, - loading, - progress, - color, - allowDuplicates, - onClick, - onContext, - onMiddleClick, - onClose: onLeave, - markdownOptions: { channelId } - }); - }, - remove(id) { - Astra.n11s.remove(id); - }, - /** - * @param {Number} id Notification ID - * @param {object} options - * @param {string} [options.channelId] Channel ID if content is a string which gets formatted, and you want to mention a role for example. - * @param {Boolean} [options.loading] Makes the bar animate differently instead of fading in and out slowly - * @param {Number} [options.progress] 0-100, -1 sets it to 100%, setting it to 100% closes the notification automatically - * @param {string} [options.color] Bar color - * @param {function} [options.onLeave] Callback when notification is leaving - */ - update(id, options) { - const obj = {}; - for (const key of ['content', 'timeout', 'loading', 'progress', 'color', 'onClick', 'onContext', 'onMiddleClick']) if (typeof options[key] !== 'undefined') obj[key] = options[key]; - if (options.onLeave) obj.onClose = options.onLeave; - if (options.channelId) obj.markdownOptions = { channelId: options.channelId }; - Astra.n11s.update(id, obj); - }, - exists(id) { - return Astra.n11s.exists(id); - } - }; - XenoLib.Notifications = utils; - } else { - const DeepEqualityCheck = (content1, content2) => { - if (typeof content1 !== typeof content2) return false; - const isCNT1HTML = content1 instanceof HTMLElement; - const isCNT2HTML = content2 instanceof HTMLElement; - if (isCNT1HTML !== isCNT2HTML) return false; - else if (isCNT1HTML) return content1.isEqualNode(content2); - if (content1 !== content2) if (Array.isArray(content1)) { - if (content1.length !== content2.length) return false; - for (const [index, item] of content1.entries()) if (!DeepEqualityCheck(item, content2[index])) return false; - - } else if (typeof content1 === 'object') { - if (content1.type) { - if (typeof content1.type !== typeof content2.type) return false; - if (content1.type !== content2.type) return false; - } - if (typeof content1.props !== typeof content2.props) return false; - if (content1.props) { - if (Object.keys(content1.props).length !== Object.keys(content2.props).length) return false; - for (const prop in content1.props) if (!DeepEqualityCheck(content1.props[prop], content2.props[prop])) return false; - - } - } else return false; - - return true; - }; - const [useStore, api] = XenoLib.zustand(e => ({ data: [] })); - const defaultOptions = { - loading: false, - progress: -1, - channelId: undefined, - timeout: 3500, - color: '#2196f3', - onLeave: NOOP - }; - const utils = { - success(content, options = {}) { - return this.show(content, { color: '#43b581', ...options }); - }, - info(content, options = {}) { - return this.show(content, { color: '#4a90e2', ...options }); - }, - warning(content, options = {}) { - return this.show(content, { color: '#ffa600', ...options }); - }, - danger(content, options = {}) { - return this.show(content, { color: '#f04747', ...options }); - }, - error(content, options = {}) { - return this.danger(content, options); - }, - /** - * @param {string|HTMLElement|React} content - Content to display. If it's a string, it'll be formatted with markdown, including URL support [like this](https://google.com/) - * @param {object} options - * @param {string} [options.channelId] Channel ID if content is a string which gets formatted, and you want to mention a role for example. - * @param {Number} [options.timeout] Set to 0 to keep it permanently until user closes it, or if you want a progress bar - * @param {Boolean} [options.loading] Makes the bar animate differently instead of fading in and out slowly - * @param {Number} [options.progress] 0-100, -1 sets it to 100%, setting it to 100% closes the notification automatically - * @param {string} [options.color] Bar color - * @param {string} [options.allowDuplicates] By default, notifications that are similar get grouped together, use true to disable that - * @param {function} [options.onLeave] Callback when notification is leaving - * @return {Number} - Notification ID. Store this if you plan on force closing it, changing its content or want to set the progress - */ - show(content, options = {}) { - let id = null; - options = Object.assign(Utilities.deepclone(defaultOptions), options); - api.setState(state => { - if (!options.allowDuplicates) { - const notif = state.data.find(n => DeepEqualityCheck(n.content, content) && n.timeout === options.timeout && !n.leaving); - if (notif) { - id = notif.id; - notificationEvents.dispatch({ type: 'XL_NOTIFS_DUPLICATE', id: notif.id }); - return state; - } - } - if (state.data.length >= 100) return state; - do id = Math.floor(4294967296 * Math.random()); - while (state.data.findIndex(n => n.id === id) !== -1); - return { data: [].concat(state.data, [{ content, ...options, id }]) }; - }); - return id; - }, - remove(id) { - notificationEvents.dispatch({ type: 'XL_NOTIFS_REMOVE', id }); - }, - /** - * @param {Number} id Notification ID - * @param {object} options - * @param {string} [options.channelId] Channel ID if content is a string which gets formatted, and you want to mention a role for example. - * @param {Boolean} [options.loading] Makes the bar animate differently instead of fading in and out slowly - * @param {Number} [options.progress] 0-100, -1 sets it to 100%, setting it to 100% closes the notification automatically - * @param {string} [options.color] Bar color - * @param {function} [options.onLeave] Callback when notification is leaving - */ - update(id, options) { - delete options.id; - api.setState(state => { - const idx = state.data.findIndex(n => n.id === id); - if (idx === -1) return state; - state.data[idx] = Object.assign(state.data[idx], options); - return state; - }); - notificationEvents.dispatch({ type: 'XL_NOTIFS_UPDATE', id, ...options }); - }, - exists(id) { - return api.getState().data.findIndex(e => e.id === id && !e.leaving) !== -1; - } - }; - XenoLib.Notifications = utils; - const ReactSpring = (() => { - const olfilter = Array.prototype.filter - Array.prototype.filter = function (callbackFn, thisArg) { - return []; - } - try { - return WebpackModules.getByProps('useTransition') - } finally { - Array.prototype.filter = olfilter; - } - })(); - - function hex2int(hex) { - return parseInt(hex, 16); - } - - function int2rgba(int, alpha) { - const r = int >> 16 & 255; - const g = int >> 8 & 255; - const b = int & 255; - return `rgba(${r}, ${g}, ${b}, ${alpha})`; - } - - const BadgesModule = WebpackModules.getByProps('NumberBadge'); - const CloseButton = React.createElement('svg', { width: 16, height: 16, viewBox: '0 0 24 24' }, React.createElement('path', { d: 'M18.4 4L12 10.4L5.6 4L4 5.6L10.4 12L4 18.4L5.6 20L12 13.6L18.4 20L20 18.4L13.6 12L20 5.6L18.4 4Z', fill: 'currentColor' })); - class Notification extends React.PureComponent { - constructor(props) { - super(props); - this.state = { - closeFast: false /* close button pressed, XL_NOTIFS_REMOVE dispatch */, - offscreen: false /* don't do anything special if offscreen, not timeout */, - counter: 1 /* how many times this notification was shown */, - resetBar: false /* reset bar to 0 in the event counter goes up */, - hovered: false, - leaving: true /* prevent hover events from messing up things */, - loading: props.loading /* loading animation, enable progress */, - progress: props.progress /* -1 means undetermined */, - content: props.content, - contentParsed: this.parseContent(props.content, props.channelId), - color: props.color - }; - this._contentRef = null; - this._ref = null; - this._animationCancel = NOOP; - this._oldOffsetHeight = 0; - this._initialProgress = !this.props.timeout ? (this.state.loading && this.state.progress !== -1 ? this.state.progress : 100) : 0; - XenoLib._.bindAll(this, ['closeNow', 'handleDispatch', '_setContentRef', 'onMouseEnter', 'onMouseLeave']); - this.handleResizeEvent = XenoLib._.throttle(this.handleResizeEvent.bind(this), 100); - this.resizeObserver = new ResizeObserver(this.handleResizeEvent); - this._timeout = props.timeout; - } - componentDidMount() { - this._unsubscribe = api.subscribe(_ => this.checkOffScreen()); - window.addEventListener('resize', this.handleResizeEvent); - notificationEvents.on('XL_NOTIFS_DUPLICATE', this.handleDispatch); - notificationEvents.on('XL_NOTIFS_REMOVE', this.handleDispatch); - notificationEvents.on('XL_NOTIFS_UPDATE', this.handleDispatch); - notificationEvents.on('XL_NOTIFS_ANIMATED', this.handleDispatch); - notificationEvents.on('XL_NOTIFS_SETTINGS_UPDATE', this.handleDispatch); - } - componentWillUnmount() { - this._unsubscribe(); - window.window.removeEventListener('resize', this.handleResizeEvent); - notificationEvents.off('XL_NOTIFS_DUPLICATE', this.handleDispatch); - notificationEvents.off('XL_NOTIFS_REMOVE', this.handleDispatch); - notificationEvents.off('XL_NOTIFS_UPDATE', this.handleDispatch); - notificationEvents.off('XL_NOTIFS_ANIMATED', this.handleDispatch); - notificationEvents.off('XL_NOTIFS_SETTINGS_UPDATE', this.handleDispatch); - this.resizeObserver.disconnect(); - this.resizeObserver = null; /* no mem leaks plz */ - this._ref = null; - this._contentRef = null; - } - handleDispatch(e) { - if (this.state.leaving || this.state.closeFast) return; - if (e.type === 'XL_NOTIFS_SETTINGS_UPDATE') { - if (e.key !== UPDATEKEY) return; - this._animationCancel(); - this.forceUpdate(); - return; - } - if (e.type === 'XL_NOTIFS_ANIMATED') this.checkOffScreen(); - if (e.id !== this.props.id) return; - const { content, channelId, loading, progress, color } = e; - const { content: curContent, channelId: curChannelId, loading: curLoading, progress: curProgress, color: curColor } = this.state; - switch (e.type) { - case 'XL_NOTIFS_REMOVE': - this.closeNow(); - break; - case 'XL_NOTIFS_DUPLICATE': - this._animationCancel(); - this.setState({ counter: this.state.counter + 1, resetBar: !!this.props.timeout, closeFast: false }); - break; - case 'XL_NOTIFS_UPDATE': - /* if (!this.state.initialAnimDone) { - this.state.content = content || curContent; - this.state.channelId = channelId || curChannelId; - this.state.contentParsed = this.parseContent(content || curContent, channelId || curChannelId); - if (typeof loading !== 'undefined') this.state.loading = loading; - if (typeof progress !== 'undefined') this.state.progress = progress; - this.state.color = color || curColor; - return; - }*/ - if (this.state.initialAnimDone) this._animationCancel(); - this.setState({ - content: content || curContent, - channelId: channelId || curChannelId, - contentParsed: this.parseContent(content || curContent, channelId || curChannelId), - loading: typeof loading !== 'undefined' ? loading : curLoading, - progress: typeof progress !== 'undefined' ? progress : curProgress, - color: color || curColor - }); - break; - } - } - parseContent(content, channelId) { - if (typeof content === 'string') return FancyParser(content, true, { channelId }); - else if (content instanceof Element) return ReactTools.createWrappedElement(content); - return content; - } - checkOffScreen() { - if (this.state.leaving || !this._contentRef) return; - const bcr = this._contentRef.getBoundingClientRect(); - if (Math.floor(bcr.bottom) - 1 > Structs.Screen.height || Math.ceil(bcr.top) + 1 < 0) { - if (!this.state.offscreen) { - this._animationCancel(); - this.setState({ offscreen: true }); - } - } else if (this.state.offscreen) { - this._animationCancel(); - this.setState({ offscreen: false }); - } - } - closeNow() { - if (this.state.closeFast) return; - this.resizeObserver.disconnect(); - this._animationCancel(); - api.setState(state => { - const dt = state.data.find(m => m.id === this.props.id); - if (dt) dt.leaving = true; - return { data: state.data }; - }); - this.setState({ closeFast: true }); - } - handleResizeEvent() { - if (this._oldOffsetHeight !== this._contentRef.offsetHeight) { - this._animationCancel(); - this.forceUpdate(); - } - } - _setContentRef(ref) { - if (this._contentRef) { - this._contentRef.removeEventListener('mouseenter', this.onMouseEnter); - this._contentRef.removeEventListener('mouseleave', this.onMouseLeave); - } - if (!ref) return; - ref.addEventListener('mouseenter', this.onMouseEnter); - ref.addEventListener('mouseleave', this.onMouseLeave); - this._contentRef = ref; - this.resizeObserver.observe(ref); - } - onMouseEnter(e) { - if (this.state.leaving || !this.props.timeout || this.state.closeFast) return; - this._animationCancel(); - if (this._startProgressing) this._timeout -= Date.now() - this._startProgressing; - this.setState({ hovered: true }); - } - onMouseLeave(e) { - if (this.state.leaving || !this.props.timeout || this.state.closeFast) return; - this._animationCancel(); - this.setState({ hovered: false }); - } - render() { - const config = { duration: 200 }; - if (this._contentRef) this._oldOffsetHeight = this._contentRef.offsetHeight; - return React.createElement( - ReactSpring.Spring, - { - native: true, - from: { opacity: 0, height: 0, progress: this._initialProgress, loadbrightness: 1 }, - to: async (next, cancel) => { - this.state.leaving = false; - this._animationCancel = cancel; - if (this.state.offscreen) { - if (this.state.closeFast) { - this.state.leaving = true; - await next({ opacity: 0, height: 0 }); - api.setState(state => ({ data: state.data.filter(n => n.id !== this.props.id) })); - return; - } - await next({ opacity: 1, height: this._contentRef.offsetHeight, loadbrightness: 1 }); - if (this.props.timeout) await next({ progress: 0 }); - else - if (this.state.loading && this.state.progress !== -1) await next({ progress: 0 }); - else await next({ progress: 100 }); - - - return; - } - const isSettingHeight = this._ref.offsetHeight !== this._contentRef.offsetHeight; - await next({ opacity: 1, height: this._contentRef.offsetHeight }); - if (isSettingHeight) notificationEvents.dispatch({ type: 'XL_NOTIFS_ANIMATED' }); - this.state.initialAnimDone = true; - if (this.state.resetBar || (this.state.hovered && LibrarySettings.notifications.timeoutReset)) { - await next({ progress: 0 }); /* shit gets reset */ - this.state.resetBar = false; - } - - if (!this.props.timeout && !this.state.closeFast) { - if (!this.state.loading) await next({ progress: 100 }); - else { - await next({ loadbrightness: 1 }); - if (this.state.progress === -1) await next({ progress: 100 }); - else await next({ progress: this.state.progress }); - } - if (this.state.progress < 100 || !this.state.loading) return; - } - if (this.state.hovered && !this.state.closeFast) return; - if (!this.state.closeFast && !LibrarySettings.notifications.timeoutReset) this._startProgressing = Date.now(); - await next({ progress: 100 }); - if (this.state.hovered && !this.state.closeFast) return; /* race condition: notif is hovered, but it continues and closes! */ - this.state.leaving = true; - if (!this.state.closeFast) api.setState(state => { - const dt = state.data.find(m => m.id === this.props.id); - if (dt) dt.leaving = true; - return { data: state.data }; - }); - - this.props.onLeave(); - await next({ opacity: 0, height: 0 }); - api.setState(state => ({ data: state.data.filter(n => n.id !== this.props.id) })); - }, - config: key => { - if (key === 'progress') { - let duration = this._timeout; - if (this.state.closeFast || !this.props.timeout || this.state.resetBar || this.state.hovered) duration = 150; - if (this.state.offscreen) duration = 0; /* don't animate at all */ - return { duration }; - } - if (key === 'loadbrightness') return { duration: 750 }; - return config; - } - }, - e => React.createElement( - ReactSpring.animated.div, - { - style: { - height: e.height, - opacity: e.opacity - }, - className: 'xenoLib-notification', - ref: e => e && (this._ref = e) - }, - React.createElement( - 'div', - { - className: 'xenoLib-notification-content-wrapper', - ref: this._setContentRef, - style: { - '--grad-one': this.state.color, - '--grad-two': ColorConverter.lightenColor(this.state.color, 20), - '--bar-color': ColorConverter.darkenColor(this.state.color, 30) - }, - onClick: e => { - if (!this.props.onClick) return; - if (e.target && e.target.getAttribute('role') === 'button') return; - this.props.onClick(e); - this.closeNow(); - }, - onContextMenu: e => { - if (!this.props.onContext) return; - this.props.onContext(e); - this.closeNow(); - }, - onMouseUp: e => { - if (!this.props.onMiddleClick || e.button !== 1) return; - if (e.target && e.target.getAttribute('role') === 'button') return; - this.props.onMiddleClick(e); - this.closeNow(); - } - }, - React.createElement( - 'div', - { - className: 'xenoLib-notification-content', - style: { - backdropFilter: LibrarySettings.notifications.backdrop ? 'blur(5px)' : undefined, - background: int2rgba(hex2int(LibrarySettings.notifications.backdropColor), LibrarySettings.notifications.backdrop ? 0.3 : 1.0), - border: LibrarySettings.notifications.backdrop ? 'none' : undefined - }, - ref: e => { - if (!LibrarySettings.notifications.backdrop || !e) return; - e.style.setProperty('backdrop-filter', e.style.backdropFilter, 'important'); - e.style.setProperty('background', e.style.background, 'important'); - e.style.setProperty('border', e.style.border, 'important'); - } - }, - React.createElement(ReactSpring.animated.div, { - className: XenoLib.joinClassNames('xenoLib-notification-loadbar', { 'xenoLib-notification-loadbar-striped': !this.props.timeout && this.state.loading, 'xenoLib-notification-loadbar-user': !this.props.timeout && !this.state.loading }), - style: { right: e.progress.to(e => `${100 - e}%`), filter: e.loadbrightness.to(e => `brightness(${e * 100}%)`) } - }), - React.createElement( - XenoLib.ReactComponents.Button, - { - look: XenoLib.ReactComponents.Button.Looks.BLANK, - size: XenoLib.ReactComponents.Button.Sizes.NONE, - onClick: e => { - e.preventDefault(); - e.stopPropagation(); - this.closeNow(); - }, - onContextMenu: e => { - e.preventDefault(); - e.stopPropagation(); - const state = api.getState(); - state.data.forEach(notif => utils.remove(notif.id)); - }, - className: 'xenoLib-notification-close' - }, - CloseButton - ), - this.state.counter > 1 && BadgesModule.NumberBadge({ count: this.state.counter, className: 'xenLib-notification-counter', color: '#2196f3' }), - this.state.contentParsed - ) - ) - ) - ); - } - } - function NotificationsWrapper(e) { - const notifications = useStore(e => e.data); - return notifications.map(item => React.createElement(XenoLib.ReactComponents.ErrorBoundary, { label: `Notification ${item.id}`, onError: () => api.setState(state => ({ data: state.data.filter(n => n.id !== item.id) })), key: item.id.toString() }, React.createElement(Notification, { ...item, leaving: false }))).reverse(); - } - NotificationsWrapper.displayName = 'XenoLibNotifications'; - const DOMElement = document.createElement('div'); - document.querySelector('#app-mount').appendChild(DOMElement); // fucking incompetent powercord needs me to append it first - DOMElement.className = XenoLib.joinClassNames('xenoLib-notifications', `xenoLib-centering-${LibrarySettings.notifications.position}`); - ReactDOM.render(React.createElement(NotificationsWrapper, {}), DOMElement); - } - } catch (e) { - Logger.stacktrace('There has been an error loading the Notifications system, fallback object has been put in place to prevent errors', e); - XenoLib.Notifications = { - success(content, options = {}) { }, - info(content, options = {}) { }, - warning(content, options = {}) { }, - danger(content, options = {}) { }, - error(content, options = {}) { }, - show(content, options = {}) { }, - remove(id) { }, - update(id, options) { } - }; - } - /* NOTIFICATIONS END */ - - global.XenoLib = XenoLib; - - const notifLocations = ['topLeft', 'topMiddle', 'topRight', 'bottomLeft', 'bottomMiddle', 'bottomRight']; - const notifLocationClasses = [ - 'topLeft-xenoLib option-xenoLib', - 'topMiddle-xenoLib option-xenoLib', - 'topRight-xenoLib option-xenoLib', - 'bottomLeft-xenoLib option-xenoLib', - 'bottomMiddle-xenoLib option-xenoLib', - 'bottomRight-xenoLib option-xenoLib' - ]; - const PositionSelectorWrapperClassname = 'xenoLib-position-wrapper'; - const PositionSelectorSelectedClassname = 'selected-xenoLib'; - const PositionSelectorHiddenInputClassname = 'xenoLib-position-hidden-input'; - const FormText = WebpackModules.getByDisplayName('FormText'); - class NotificationPosition extends React.PureComponent { - constructor(props) { - super(props); - this.state = { - position: props.position - }; - } - componentDidMount() { - this._notificationId = XenoLib.Notifications.show('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur lacinia justo eget libero ultrices mollis.', { timeout: 0 }); - } - componentWillUnmount() { - XenoLib.Notifications.remove(this._notificationId); - } - getSelected() { - switch (this.state.position) { - case 'topLeft': - return 'Top Left'; - case 'topMiddle': - return 'Top Middle'; - case 'topRight': - return 'Top Right'; - case 'bottomLeft': - return 'Bottom Left'; - case 'bottomMiddle': - return 'Bottom Middle'; - case 'bottomRight': - return 'Bottom Right'; - default: - return 'Unknown'; - } - } - render() { - return React.createElement( - 'div', - {}, - React.createElement( - 'div', - { - className: PositionSelectorWrapperClassname - }, - notifLocations.map((e, i) => React.createElement( - 'label', - { - className: XenoLib.joinClassNames(notifLocationClasses[i], { [PositionSelectorSelectedClassname]: this.state.position === e }) - }, - React.createElement('input', { - type: 'radio', - name: 'xenolib-notif-position-selector', - value: e, - onChange: () => { - this.props.onChange(e); - this.setState({ position: e }); - }, - className: PositionSelectorHiddenInputClassname - }) - )) - ), - React.createElement( - FormText, - { - type: FormText.Types.DESCRIPTION, - className: DiscordClasses.Margins.marginTop8 - }, - this.getSelected() - ) - ); - } - } - - class NotificationPositionField extends Settings.SettingField { - constructor(name, note, onChange, value) { - super(name, note, onChange, NotificationPosition, { - position: value, - onChange: reactElement => position => { - this.onChange(position); - } - }); - } - } - - class RadioGroupWrapper extends React.PureComponent { - render() { - return React.createElement(DiscordModules.RadioGroup, this.props); - } - } - - class RadioGroup extends Settings.SettingField { - constructor(name, note, defaultValue, values, onChange, options = {}) { - super(name, note, onChange, RadioGroupWrapper, { - noteOnTop: true, - disabled: !!options.disabled, - options: values, - onChange: reactElement => option => { - reactElement.props.value = option.value; - reactElement.forceUpdate(); - this.onChange(option.value); - }, - value: defaultValue - }); - } - } - - - class SwitchItemWrapper extends React.PureComponent { - render() { - return React.createElement(DiscordModules.SwitchRow, this.props); - } - } - - class Switch extends Settings.SettingField { - constructor(name, note, isChecked, onChange, options = {}) { - super(name, note, onChange); - this.disabled = !!options.disabled; - this.value = !!isChecked; - } - - onAdded() { - const reactElement = ReactDOM.render(React.createElement(SwitchItemWrapper, { - children: this.name, - note: this.note, - disabled: this.disabled, - hideBorder: false, - value: this.value, - onChange: e => { - reactElement.props.value = e; - reactElement.forceUpdate(); - this.onChange(e); - } - }), this.getElement()); - } - } - - class TimerWrapper extends React.PureComponent { - constructor(...args) { - super(...args); - this.moment = Moment(this.props.value + this.props.time); - } - componentDidUpdate() { - this.moment = Moment(this.props.value + this.props.time); - } - componentDidMount() { - const { moment } = this; - const vv = moment.clone().seconds(0).add(1, 'm').diff(moment); - this.timer = setInterval(() => { - clearInterval(this.timer); - this.timer = setInterval(() => this.forceUpdate(), 60 * 1000); - this.forceUpdate(); - }, vv); - } - componentWillUnmount() { - if (this.timer) clearInterval(this.timer); - } - render() { - const { value, after, active, inactive, time } = this.props; - const future = (value + time); - return React.createElement(TextElement, {}, value ? Date.now() > future ? active : `${after}${this.moment.fromNow()}` : inactive); - } - } - - class Timer extends Settings.SettingField { - constructor(name, note, value, onChange, after, active, inactive, time) { - super(name, note, onChange, TimerWrapper, { after, active, inactive, time, value }); - } - } - - XenoLib.buildSetting = function buildSetting(data) { - const { name, note, type, value, onChange, id } = data; - let setting = null; - if (type == 'color') setting = new XenoLib.Settings.ColorPicker(name, note, value, onChange, { disabled: data.disabled, defaultColor: value }); - else if (type == 'dropdown') setting = new Settings.Dropdown(name, note, value, data.options, onChange); - else if (type == 'file') setting = new Settings.FilePicker(name, note, onChange); - else if (type == 'keybind') setting = new Settings.Keybind(name, note, value, onChange); - else if (type == 'radio') setting = new RadioGroup(name, note, value, data.options, onChange, { disabled: data.disabled }); - else if (type == 'slider') setting = new Settings.Slider(name, note, data.min, data.max, value, onChange, data); - else if (type == 'switch') setting = new Switch(name, note, value, onChange, { disabled: data.disabled }); - else if (type == 'textbox') setting = new Settings.Textbox(name, note, value, onChange, { placeholder: data.placeholder || '' }); - if (id) setting.id = id; - return setting; - }; - - /* - * Function versionComparator from 0PluginLibrary as defaultComparator, required copyright notice: - * - * Copyright 2018 Zachary Rauen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - */ - XenoLib.versionComparator = (currentVersion, remoteVersion) => { - currentVersion = currentVersion.split(".").map((e) => {return parseInt(e);}); - remoteVersion = remoteVersion.split(".").map((e) => {return parseInt(e);}); - - if (remoteVersion[0] > currentVersion[0]) return true; - else if (remoteVersion[0] == currentVersion[0] && remoteVersion[1] > currentVersion[1]) return true; - else if (remoteVersion[0] == currentVersion[0] && remoteVersion[1] == currentVersion[1] && remoteVersion[2] > currentVersion[2]) return true; - return false; - } - - /* - * Function extractVersion from 0PluginLibrary as defaultVersioner, required copyright notice: - * - * Copyright 2018 Zachary Rauen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - */ - XenoLib.extractVersion = (content) => { - const remoteVersion = content.match(/['"][0-9]+\.[0-9]+\.[0-9]+['"]/i); - if (!remoteVersion) return "0.0.0"; - return remoteVersion.toString().replace(/['"]/g, ""); - } - - return class CXenoLib extends Plugin { - constructor() { - super(); - this.settings = LibrarySettings; - const _zerecantcode_path = require('path'); - const theActualFileNameZere = _zerecantcode_path.join(__dirname, _zerecantcode_path.basename(__filename)); - XenoLib.changeName(theActualFileNameZere, '1XenoLib'); /* prevent user from changing libs filename */ - try { - NewModalStack.closeModal(`${this.name}_DEP_MODAL`); - } catch (e) { } - } - load() { - if (super.load) super.load(); - try { - if (!BdApi.Plugins) return; /* well shit what now */ - if (!BdApi.isSettingEnabled) return; - const list = BdApi.Plugins.getAll().filter(k => k._XL_PLUGIN || (k.instance && k.instance._XL_PLUGIN)).map(k => k.instance || k); - for (let p = 0; p < list.length; p++) try { - BdApi.Plugins.reload(list[p].getName()); - } catch (e) { - try { - Logger.stacktrace(`Failed to reload plugin ${list[p].getName()}`, e); - } catch (e) { - Logger.error('Failed telling you about failing to reload a plugin', list[p], e); - } - } - - const pluginsDir = (BdApi.Plugins && BdApi.Plugins.folder) || (window.ContentManager && window.ContentManager.pluginsFolder); - const PLUGINS_LIST = ['BetterImageViewer', 'BetterTypingUsers', 'BetterUnavailableGuilds', 'CrashRecovery', 'InAppNotifications', 'MessageLoggerV2', 'MultiUploads', 'SaveToRedux', 'UnreadBadgesRedux']; - const fs = require('fs'); - const path = require('path'); - - const pluginsToCheck = []; - - let alreadyFoundZLib = false; - - for (const file of fs.readdirSync(pluginsDir)) { - if (file.indexOf('.js') !== file.length - 3) continue; - - try { - const { name } = _extractMeta(fs.readFileSync(path.join(pluginsDir, file), 'utf8')); - if (PLUGINS_LIST.indexOf(name) === -1) { - switch (name) { - case 'XenoLib': { - if (file !== path.basename(__filename)) fs.unlinkSync(path.join(pluginsDir, file)); - break; - } - case 'ZeresPluginLibrary': { - if (alreadyFoundZLib) fs.unlinkSync(path.join(pluginsDir, file)); - else alreadyFoundZLib = true; - } - } - continue; - } - if (~pluginsToCheck.findIndex(e => e.name === name)) { - fs.unlinkSync(path.join(pluginsDir, file)); - continue; - } - pluginsToCheck.push({ name, file }); - } catch (e) { } - } - setTimeout(() => { - try { - const https = require('https'); - for (const { name, file } of pluginsToCheck) { - // eslint-disable-next-line no-undef - const isPluginEnabled = BdApi.Plugins.isEnabled(name); - let plugin = BdApi.Plugins.get(name); - if (plugin && plugin.instance) plugin = plugin.instance; - // eslint-disable-next-line no-loop-func - const req = https.request(`https://raw.githubusercontent.com/1Lighty/BetterDiscordPlugins/master/Plugins/${name}/${name}.plugin.js`, { headers: { origin: 'discord.com' } }, res => { - let body = ''; - // eslint-disable-next-line no-void - res.on('data', chunk => ((body += chunk), void 0)); - res.on('end', () => { - try { - if (res.statusCode !== 200) return /* XenoLib.Notifications.error(`Failed to check for updates for ${name}`, { timeout: 0 }) */; - if (plugin && (name === 'MessageLoggerV2' || Utilities.getNestedProp(plugin, '_config.info.version')) && !XenoLib.versionComparator(name === 'MessageLoggerV2' ? plugin.getVersion() : plugin._config.info.version, XenoLib.extractVersion(body))) return; - const newFile = `${name}.plugin.js`; - fs.unlinkSync(path.join(pluginsDir, file)); - // avoid BDs watcher being shit as per usual - setTimeout(() => { - try { - fs.writeFileSync(path.join(pluginsDir, newFile), body); - if (window.pluginModule && window.pluginModule.loadPlugin) { - BdApi.Plugins.reload(name); - if (newFile !== file) window.pluginModule.loadPlugin(name); - // eslint-disable-next-line curly - } else if (BdApi.version ? !BdApi.isSettingEnabled('settings', 'addons', 'autoReload') : !BdApi.isSettingEnabled('fork-ps-5')) { - // eslint-disable-next-line no-negated-condition - if (newFile !== file) { - // eslint-disable-next-line no-undef - BdApi.showConfirmationModal('Hmm', 'You must reload in order to finish plugin installation', { onConfirm: () => location.reload() }); - isPluginEnabled = false; - } else BdApi.Plugins.reload(name); - } - if (isPluginEnabled) setTimeout(() => BdApi.Plugins.enable(name), 3000); - } catch (e) { } - }, 1000); - } catch (e) { } - }); - }); - req.on('error', _ => XenoLib.Notifications.error(`Failed to check for updates for ${name}`, { timeout: 0 })); - } - } catch (e) { } - }, 3000); - } catch (err) { - Logger.log('Failed to execute load', err); - } - const end = performance.now(); - Logger.log(`Loaded in ${Math.round(end - start)}ms`); - } - buildSetting(data) { - if (data.type === 'position') { - const setting = new NotificationPositionField(data.name, data.note, data.onChange, data.value); - if (data.id) setting.id = data.id; - return setting; - } else 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 === 'timeStatus') { - const setting = new Timer(data.name, data.note, data.value, data.onChange, data.after, data.active, data.inactive, data.time); - if (data.id) setting.id = data.id; - return setting; - } - return XenoLib.buildSetting(data); - } - getSettingsPanel() { - return this.buildSettingsPanel() - .append(new XenoLib.Settings.PluginFooter(() => this.showChangelog())) - .getElement(); - } - saveSettings(category, setting, value) { - this.settings[category][setting] = value; - LibrarySettings[category][setting] = value; - PluginUtilities.saveSettings(this.name, LibrarySettings); - if (category === 'notifications') { - if (setting === 'position') { - const DOMElement = document.querySelector('.xenoLib-notifications'); - if (DOMElement) { - DOMElement.className = XenoLib.joinClassNames('xenoLib-notifications', `xenoLib-centering-${LibrarySettings.notifications.position}`); - notificationEvents.dispatch({ type: 'XL_NOTIFS_ANIMATED' }); - } - } else if (setting === 'backdrop' || setting === 'backdropColor') { - notificationEvents.dispatch({ type: 'XL_NOTIFS_SETTINGS_UPDATE', key: UPDATEKEY }); - UPDATEKEY = {}; - } - - } else if (category === 'addons') if (setting === 'extra') { - if (value && !patchAddonCardAnyway.patched) patchAddonCardAnyway(true); - XenoLib.Notifications.warning('Reopen plugins section for immediate effect'); - } else if (category === 'userCounter') if (setting === 'enabled') { - if (value) { - LibrarySettings.userCounter.enableTime = Date.now(); - LibrarySettings.userCounter.lastSubmission = Date.now(); - } else { - LibrarySettings.userCounter.enableTime = 0; - LibrarySettings.userCounter.lastSubmission = 0; - } - PluginUtilities.saveSettings(this.name, LibrarySettings); - } - - - } - showChangelog(footer) { - return; - XenoLib.showChangelog(`${this.name} has been updated!`, this.version, this._config.changelog, void 0, true); - } - 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]); - let b = BdApi.Plugins.get('ZeresPluginLibrary'); - ((b, c) => b && b.version && a(b.version, c))(b, '2.0.8') && (ZeresPluginLibraryOutdated = !0); - } catch (e) { - console.error('Error checking if ZeresPluginLibrary is out of date', e); - } - - return !global.ZeresPluginLibrary || ZeresPluginLibraryOutdated || window.__XL_requireRenamePls - ? class { - constructor() { - this._config = config; - } - 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 to download it.`; - } - start() { } - load() { - try { - // asking people to do simple tasks is stupid, relying on stupid modals that are *supposed* to help them is unreliable - // forcing the download on enable is good enough - const fs = require('fs'); - const path = require('path'); - const pluginsDir = (BdApi.Plugins && BdApi.Plugins.folder) || (window.ContentManager && window.ContentManager.pluginsFolder); - const zeresLibDir = path.join(pluginsDir, '0PluginLibrary.plugin.js'); - - if (window.__XL_requireRenamePls) { - try { - delete window.__XL_requireRenamePls; - const oldSelfPath = path.join(pluginsDir, path.basename(__filename)); - const selfContent = fs.readFileSync(oldSelfPath); - // avoid windows blocking the file - fs.unlinkSync(oldSelfPath); - // avoid BDs watcher being shit as per usual - setTimeout(() => { - try { - fs.writeFileSync(path.join(pluginsDir, '1XenoLib.plugin.js'), selfContent); - window.__XL_waitingForWatcherTimeout = setTimeout(() => { - // what the fuck? - BdApi.Plugins.reload(this.getName()); - }, 3000); - } catch (e) { } - }, 1000); - } catch (e) { } - return; - } - - if (window.__XL_assumingZLibLoaded) return; - - for (const file of fs.readdirSync(pluginsDir)) { - if (file.indexOf('.js') !== file.length - 3) continue; - try { - switch (_extractMeta(fs.readFileSync(path.join(pluginsDir, file), 'utf8')).name) { - case 'XenoLib': { - if (file !== '1XenoLib.plugin.js') if (file === path.basename(__filename)) window.__XL_requireRenamePls = true; - else fs.unlinkSync(path.join(pluginsDir, file)); - - continue; - } - case 'ZeresPluginLibrary': { - fs.unlinkSync(path.join(pluginsDir, file)); - continue; - } - default: continue; - } - } catch (e) { } - } - - const https = require('https'); - - const onFail = () => BdApi.showConfirmationModal('Well shit', 'Failed to download Zeres Plugin Library, join this server for further assistance:https://discord.gg/NYvWdN5'); - - const req = https.get('https://raw.githubusercontent.com/rauenzi/BDPluginLibrary/master/release/0PluginLibrary.plugin.js', { headers: { origin: 'discord.com' } }, res => { - let body = ''; - // eslint-disable-next-line no-void - res.on('data', chunk => ((body += new TextDecoder("utf-8").decode(chunk)), void 0)); - res.on('end', (rez) => { - try { - if (rez.statusCode !== 200) return onFail(); - fs.writeFileSync(zeresLibDir, body); - // eslint-disable-next-line no-undef - window.__XL_waitingForWatcherTimeout = setTimeout(() => { - try { - if (!window.pluginModule || !window.pluginModule.loadPlugin) { - window.__XL_assumingZLibLoaded = true; - const didRename = window.__XL_requireRenamePls; - window.__XL_waitingForWatcherTimeout = setTimeout(() => { - try { - window.__XL_waitingForWatcherTimeout = setTimeout(() => { - try { - location.reload(); - } catch (e) { } - }, 3000); - BdApi.Plugins.reload(this.getName()); - } catch (e) { } - }, window.__XL_requireRenamePls ? 3000 : 0); - if (window.__XL_requireRenamePls) { - delete window.__XL_requireRenamePls; - const oldSelfPath = path.join(pluginsDir, path.basename(__filename)); - const selfContent = fs.readFileSync(oldSelfPath); - // avoid windows blocking the file - fs.unlinkSync(oldSelfPath); - fs.writeFileSync(path.join(pluginsDir, '1XenoLib.plugin.js'), selfContent); - } - return; - } - window.__XL_assumingZLibLoaded = true; - window.pluginModule.loadPlugin('0PluginLibrary'); - window.__XL_waitingForWatcherTimeout = setTimeout(() => { - try { - const didRename = window.__XL_requireRenamePls; - window.__XL_waitingForWatcherTimeout = setTimeout(() => { - try { - window.__XL_waitingForWatcherTimeout = setTimeout(onFail, 3000); - BdApi.Plugins.reload(this.getName()); - if (!BdApi.Plugins.get('XenoLib')) window.pluginModule.loadPlugin('1XenoLib'); - } catch (e) { } - }, window.__XL_requireRenamePls ? 3000 : 0); - if (window.__XL_requireRenamePls) { - delete window.__XL_requireRenamePls; - const oldSelfPath = path.join(pluginsDir, path.basename(__filename)); - const selfContent = fs.readFileSync(oldSelfPath); - // avoid windows blocking the file - fs.unlinkSync(oldSelfPath); - fs.writeFileSync(path.join(pluginsDir, '1XenoLib.plugin.js'), selfContent); - } - } catch (e) { } - }, 3000); - } catch (e) { } - }, 3000); - } catch (e) { } - }); - }); - req.on('error', _ => { - onFail(); - }); - } catch (e) { } - } - stop() { } - 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@*/ diff --git a/discord/vencord-settings-backup.json b/discord/vencord-settings-backup.json new file mode 100644 index 0000000..abc5605 --- /dev/null +++ b/discord/vencord-settings-backup.json @@ -0,0 +1,368 @@ +{ + "settings": { + "notifyAboutUpdates": true, + "autoUpdate": false, + "autoUpdateNotification": true, + "useQuickCss": true, + "themeLinks": [], + "enableReactDevtools": false, + "frameless": false, + "transparent": false, + "winCtrlQ": false, + "winNativeTitleBar": false, + "plugins": { + "MessageEventsAPI": { + "enabled": true + }, + "CommandsAPI": { + "enabled": true + }, + "MenuItemDeobfuscatorAPI": { + "enabled": true + }, + "ContextMenuAPI": { + "enabled": true + }, + "MessagePopoverAPI": { + "enabled": true + }, + "MessageAccessoriesAPI": { + "enabled": true + }, + "MessageDecorationsAPI": { + "enabled": true + }, + "MemberListDecoratorsAPI": { + "enabled": true + }, + "ServerListAPI": { + "enabled": true + }, + "AlwaysTrust": { + "enabled": false + }, + "AnonymiseFileNames": { + "enabled": false, + "method": 2, + "randomisedLength": 7, + "consistent": "image" + }, + "BANger": { + "enabled": false + }, + "BetterGifAltText": { + "enabled": true + }, + "BetterNotesBox": { + "enabled": false + }, + "BetterRoleDot": { + "enabled": false, + "bothStyles": false + }, + "BetterUploadButton": { + "enabled": false + }, + "BlurNSFW": { + "enabled": false + }, + "CallTimer": { + "enabled": false + }, + "ColorSighted": { + "enabled": false + }, + "CrashHandler": { + "enabled": true, + "attemptToPreventCrashes": true, + "attemptToNavigateToHome": false + }, + "DisableDMCallIdle": { + "enabled": false + }, + "EmoteCloner": { + "enabled": false + }, + "Experiments": { + "enabled": false, + "enableIsStaff": false, + "forceStagingBanner": false + }, + "FakeNitro": { + "enabled": false, + "enableEmojiBypass": true, + "emojiSize": 48, + "enableStickerBypass": true, + "stickerSize": 160, + "enableStreamQualityBypass": true + }, + "FixInbox": { + "enabled": false + }, + "ForceOwnerCrown": { + "enabled": true + }, + "iLoveSpam": { + "enabled": false + }, + "IgnoreActivities": { + "enabled": false + }, + "InvisibleChat": { + "enabled": false + }, + "LoadingQuotes": { + "enabled": false + }, + "MemberCount": { + "enabled": false + }, + "MessageLinkEmbeds": { + "enabled": false + }, + "MessageLogger": { + "enabled": true, + "deleteStyle": "text", + "ignoreBots": false, + "ignoreSelf": true + }, + "MuteNewGuild": { + "enabled": true + }, + "NoBlockedMessages": { + "enabled": false + }, + "NoDevtoolsWarning": { + "enabled": false + }, + "NoF1": { + "enabled": false + }, + "NoRPC": { + "enabled": false + }, + "NoReplyMention": { + "enabled": true, + "exemptList": "" + }, + "NoScreensharePreview": { + "enabled": false + }, + "NoSystemBadge": { + "enabled": false + }, + "NoUnblockToJump": { + "enabled": false + }, + "NSFWGateBypass": { + "enabled": false + }, + "PlainFolderIcon": { + "enabled": false + }, + "PlatformIndicators": { + "enabled": true, + "colorMobileIndicator": true, + "list": true, + "badges": true, + "messages": false + }, + "PronounDB": { + "enabled": true, + "showSelf": false, + "pronounsFormat": "LOWERCASE" + }, + "RevealAllSpoilers": { + "enabled": false + }, + "ReverseImageSearch": { + "enabled": false + }, + "richerCider": { + "enabled": false + }, + "RoleColorEverywhere": { + "enabled": true, + "chatMentions": false, + "memberList": true, + "voiceUsers": true + }, + "ShikiCodeblocks": { + "enabled": true, + "theme": "https://raw.githubusercontent.com/shikijs/shiki/0b28ad8ccfbf2615f2d9d38ea8255416b8ac3043/packages/shiki/themes/dracula-soft.json", + "tryHljs": "SECONDARY", + "useDevIcon": "GREYSCALE", + "bgOpacity": 100 + }, + "ShowHiddenChannels": { + "enabled": false + }, + "SilentMessageToggle": { + "enabled": true + }, + "SilentTyping": { + "enabled": true, + "showIcon": true, + "isEnabled": true + }, + "SortFriendRequests": { + "enabled": false + }, + "SpotifyControls": { + "enabled": false, + "hoverControls": false + }, + "SpotifyCrack": { + "enabled": true, + "noSpotifyAutoPause": true, + "keepSpotifyActivityOnIdle": false + }, + "StartupTimings": { + "enabled": false + }, + "TimeBarAllActivities": { + "enabled": false + }, + "TypingIndicator": { + "enabled": true, + "includeMutedChannels": false, + "includeBlockedUsers": false + }, + "TypingTweaks": { + "enabled": true, + "showAvatars": true, + "showRoleColors": true, + "alternativeFormatting": true + }, + "Unindent": { + "enabled": true + }, + "ReactErrorDecoder": { + "enabled": false + }, + "VoiceChatDoubleClick": { + "enabled": false + }, + "ViewIcons": { + "enabled": false + }, + "VolumeBooster": { + "enabled": false + }, + "Webhook Tags": { + "enabled": false + }, + "WhoReacted": { + "enabled": false + }, + "Settings": { + "enabled": true, + "settingsLocation": "aboveActivity" + }, + "ClearURLs": { + "enabled": true + }, + "ConsoleShortcuts": { + "enabled": false + }, + "CorruptMp4s": { + "enabled": false + }, + "CustomRPC": { + "enabled": false, + "type": 0 + }, + "UrbanDictionary": { + "enabled": false + }, + "F8Break": { + "enabled": false + }, + "Fart2": { + "enabled": false, + "volume": 0.5 + }, + "FriendInvites": { + "enabled": false + }, + "FxTwitter": { + "enabled": true + }, + "HideAttachments": { + "enabled": false + }, + "KeepCurrentChannel": { + "enabled": false + }, + "LastFMRichPresence": { + "enabled": false + }, + "MessageClickActions": { + "enabled": false + }, + "MessageTags": { + "enabled": false + }, + "MoreCommands": { + "enabled": false + }, + "MoreKaomoji": { + "enabled": false + }, + "Moyai": { + "enabled": false + }, + "NoCanaryMessageLinks": { + "enabled": false + }, + "oneko": { + "enabled": false + }, + "petpet": { + "enabled": false + }, + "QuickMention": { + "enabled": false + }, + "QuickReply": { + "enabled": false + }, + "ReadAllNotificationsButton": { + "enabled": false + }, + "ServerListIndicators": { + "enabled": false + }, + "SpotifyShareCommands": { + "enabled": false + }, + "UwUifier": { + "enabled": false + }, + "VcNarrator": { + "enabled": false + }, + "ViewRaw": { + "enabled": false + }, + "BadgeAPI": { + "enabled": true + }, + "NoticesAPI": { + "enabled": true + }, + "NoTrack": { + "enabled": true + }, + "SupportHelper": { + "enabled": true + } + }, + "notifications": { + "timeout": 5000, + "position": "bottom-right", + "useNative": "not-focused" + } + }, + "quickCss": "@import url(\"https://api.fonts.coollabs.io/css2?family=Fira+Sans&display=swap\");\r\n\r\n:root {\r\n --font-sans: \"Fira Sans\";\r\n --font-code: \"Fire Code\";\r\n}\r\n\r\nbody,\r\ninput,\r\ntextarea {\r\n font-family: var(--font-sans) !important;\r\n --font-display: var(--font-sans);\r\n --font-primary: var(--font-sans);\r\n}\r\n\r\npre {\r\n font-family: var(--font-code) !important;\r\n}\r\n" +} \ No newline at end of file