From d1387f5183d2d80dd37995fd54eb54b4149e1735 Mon Sep 17 00:00:00 2001 From: Jiiks Date: Fri, 24 Aug 2018 13:46:58 +0300 Subject: [PATCH] Leave the original intact for now --- client/src/modules/reflection/modules.js | 2 +- client/src/modules/reflection/wpm.depr.js | 430 ++++++++++++++++++++++ client/src/modules/webpackmodules.js | 8 +- 3 files changed, 435 insertions(+), 5 deletions(-) create mode 100644 client/src/modules/reflection/wpm.depr.js diff --git a/client/src/modules/reflection/modules.js b/client/src/modules/reflection/modules.js index 5c380e2f..d3a54fd4 100644 --- a/client/src/modules/reflection/modules.js +++ b/client/src/modules/reflection/modules.js @@ -409,7 +409,7 @@ class Modules { } const ModuleProxy = new Proxy(Modules, { - get(WebpackModules, property) { + get(Modules, property) { return Modules[property] || Modules.getModuleByName(property); } }); diff --git a/client/src/modules/reflection/wpm.depr.js b/client/src/modules/reflection/wpm.depr.js new file mode 100644 index 00000000..0e1e8fdb --- /dev/null +++ b/client/src/modules/reflection/wpm.depr.js @@ -0,0 +1,430 @@ +/** + * BetterDiscord WebpackModules Module + * Copyright (c) 2015-present Jiiks/JsSucks - https://github.com/Jiiks / https://github.com/JsSucks + * All rights reserved. + * https://betterdiscord.net + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. +*/ + +import { Utils, Filters } from 'common'; +import Events from '../events'; + +const KnownModules = { + React: Filters.byProperties(['createElement', 'cloneElement']), + ReactDOM: Filters.byProperties(['render', 'findDOMNode']), + + Events: Filters.byPrototypeFields(['setMaxListeners', 'emit']), + + /* Guild Info, Stores, and Utilities */ + GuildStore: Filters.byProperties(['getGuild']), + SortedGuildStore: Filters.byProperties(['getSortedGuilds']), + SelectedGuildStore: Filters.byProperties(['getLastSelectedGuildId']), + GuildSync: Filters.byProperties(['getSyncedGuilds']), + GuildInfo: Filters.byProperties(['getAcronym']), + GuildChannelsStore: Filters.byProperties(['getChannels', 'getDefaultChannel']), + GuildMemberStore: Filters.byProperties(['getMember']), + MemberCountStore: Filters.byProperties(['getMemberCounts']), + GuildEmojiStore: Filters.byProperties(['getEmojis']), + GuildActions: Filters.byProperties(['markGuildAsRead']), + GuildPermissions: Filters.byProperties(['getGuildPermissions']), + + /* Channel Store & Actions */ + ChannelStore: Filters.byProperties(['getChannels', 'getDMFromUserId']), + SelectedChannelStore: Filters.byProperties(['getLastSelectedChannelId']), + ChannelActions: Filters.byProperties(['selectChannel']), + PrivateChannelActions: Filters.byProperties(['openPrivateChannel']), + ChannelSelector: Filters.byProperties(['selectGuild', 'selectChannel']), + VoiceChannelActions: Filters.byProperties(['selectVoiceChannel']), + + /* Current User Info, State and Settings */ + UserInfoStore: Filters.byProperties(['getToken']), + UserSettingsStore: Filters.byProperties(['guildPositions']), + AccountManager: Filters.byProperties(['register', 'login']), + UserSettingsUpdater: Filters.byProperties(['updateRemoteSettings']), + OnlineWatcher: Filters.byProperties(['isOnline']), + CurrentUserIdle: Filters.byProperties(['getIdleTime']), + RelationshipStore: Filters.byProperties(['isBlocked', 'isFriend']), + RelationshipManager: Filters.byProperties(['addRelationship']), + MentionStore: Filters.byProperties(['getMentions']), + + /* User Stores and Utils */ + UserStore: Filters.byProperties(['getCurrentUser']), + UserStatusStore: Filters.byProperties(['getStatuses']), + UserTypingStore: Filters.byProperties(['isTyping']), + UserActivityStore: Filters.byProperties(['getActivity']), + UserNameResolver: Filters.byProperties(['getName']), + UserNoteStore: Filters.byProperties(['getNote']), + UserNoteActions: Filters.byProperties(['updateNote']), + DraftActions: Filters.byProperties(['changeDraft']), + + /* Emoji Store and Utils */ + EmojiInfo: Filters.byProperties(['isEmojiDisabled']), + EmojiUtils: Filters.byProperties(['getGuildEmoji']), + EmojiStore: Filters.byProperties(['getByCategory', 'EMOJI_NAME_RE']), + + /* Invite Store and Utils */ + InviteStore: Filters.byProperties(['getInvites']), + InviteResolver: Filters.byProperties(['findInvite']), + InviteActions: Filters.byProperties(['acceptInvite']), + + /* Discord Objects & Utils */ + DiscordConstants: Filters.byProperties(['Permissions', 'ActivityTypes', 'StatusTypes']), + Permissions: Filters.byProperties(['getHighestRole']), + ColorConverter: Filters.byProperties(['hex2int']), + ColorShader: Filters.byProperties(['darken']), + TinyColor: Filters.byPrototypeFields(['toRgb']), + ClassResolver: Filters.byProperties(['getClass']), + ButtonData: Filters.byProperties(['ButtonSizes']), + IconNames: Filters.byProperties(['IconNames']), + NavigationUtils: Filters.byProperties(['transitionTo', 'replaceWith', 'getHistory']), + + /* Discord Messages */ + MessageStore: Filters.byProperties(['getMessages']), + MessageActions: Filters.byProperties(['jumpToMessage', '_sendMessage']), + MessageQueue: Filters.byProperties(['enqueue']), + MessageParser: Filters.byProperties(['createMessage', 'parse', 'unparse']), + + /* In-Game Overlay */ + OverlayUserPopoutSettings: Filters.byProperties(['openUserPopout']), + OverlayUserPopoutInfo: Filters.byProperties(['getOpenedUserPopout']), + + /* Experiments */ + ExperimentStore: Filters.byProperties(['getExperimentOverrides']), + ExperimentsManager: Filters.byProperties(['isDeveloper']), + CurrentExperiment: Filters.byProperties(['getExperimentId']), + + /* Images, Avatars and Utils */ + ImageResolver: Filters.byProperties(['getUserAvatarURL']), + ImageUtils: Filters.byProperties(['getSizedImageSrc']), + AvatarDefaults: Filters.byProperties(['getUserAvatarURL', 'DEFAULT_AVATARS']), + + /* Drag & Drop */ + DNDActions: Filters.byProperties(['beginDrag']), + DNDSources: Filters.byProperties(['addTarget']), + DNDObjects: Filters.byProperties(['DragSource']), + + /* Electron & Other Internals with Utils */ + ElectronModule: Filters.byProperties(['_getMainWindow']), + Dispatcher: Filters.byProperties(['dirtyDispatch']), + PathUtils: Filters.byProperties(['hasBasename']), + NotificationModule: Filters.byProperties(['showNotification']), + RouterModule: Filters.byProperties(['Router']), + APIModule: Filters.byProperties(['getAPIBaseURL']), + AnalyticEvents: Filters.byProperties(['AnalyticEventConfigs']), + KeyGenerator: Filters.byCode(/"binary"/), + Buffers: Filters.byProperties(['Buffer', 'kMaxLength']), + DeviceStore: Filters.byProperties(['getDevices']), + SoftwareInfo: Filters.byProperties(['os']), + CurrentContext: Filters.byProperties(['setTagsContext']), + + /* Media Stuff (Audio/Video) */ + MediaDeviceInfo: Filters.byProperties(['Codecs', 'SUPPORTED_BROWSERS']), + MediaInfo: Filters.byProperties(['getOutputVolume']), + MediaEngineInfo: Filters.byProperties(['MediaEngineFeatures']), + VoiceInfo: Filters.byProperties(['EchoCancellation']), + VideoStream: Filters.byProperties(['getVideoStream']), + SoundModule: Filters.byProperties(['playSound']), + + /* Window, DOM, HTML */ + WindowInfo: Filters.byProperties(['isFocused', 'windowSize']), + TagInfo: Filters.byProperties(['VALID_TAG_NAMES']), + DOMInfo: Filters.byProperties(['canUseDOM']), + + /* Locale/Location and Time */ + LocaleManager: Filters.byProperties(['setLocale']), + Moment: Filters.byProperties(['parseZone']), + LocationManager: Filters.byProperties(['createLocation']), + Timestamps: Filters.byProperties(['fromTimestamp']), + TimeFormatter: Filters.byProperties(['dateFormat']), + + /* Strings and Utils */ + Strings: Filters.byProperties(['TEXT', 'TEXTAREA_PLACEHOLDER']), + StringFormats: Filters.byProperties(['a', 'z']), + StringUtils: Filters.byProperties(['toASCII']), + + /* URLs and Utils */ + URLParser: Filters.byProperties(['Url', 'parse']), + ExtraURLs: Filters.byProperties(['getArticleURL']), + + /* Text Processing */ + hljs: Filters.byProperties(['highlight', 'highlightBlock']), + SimpleMarkdown: Filters.byProperties(['parseBlock', 'parseInline', 'defaultOutput']), + + /* DOM/React Components */ + /* ==================== */ + LayerManager: Filters.byProperties(['popLayer', 'pushLayer']), + UserSettingsWindow: Filters.byProperties(['open', 'updateAccount']), + ChannelSettingsWindow: Filters.byProperties(['open', 'updateChannel']), + GuildSettingsWindow: Filters.byProperties(['open', 'updateGuild']), + + /* Modals */ + ModalStack: Filters.byProperties(['push', 'update', 'pop', 'popWithKey']), + ConfirmModal: Filters.byPrototypeFields(['handleCancel', 'handleSubmit', 'handleMinorConfirm']), + UserProfileModal: Filters.byProperties(['fetchMutualFriends', 'setSection']), + ChangeNicknameModal: Filters.byProperties(['open', 'changeNickname']), + CreateChannelModal: Filters.byProperties(['open', 'createChannel']), + PruneMembersModal: Filters.byProperties(['open', 'prune']), + NotificationSettingsModal: Filters.byProperties(['open', 'updateNotificationSettings']), + PrivacySettingsModal: Filters.byCode(/PRIVACY_SETTINGS_MODAL_OPEN/, m => m.open), + CreateInviteModal: Filters.byProperties(['open', 'createInvite']), + + /* Popouts */ + PopoutStack: Filters.byProperties(['open', 'close', 'closeAll']), + PopoutOpener: Filters.byProperties(['openPopout']), + EmojiPicker: Filters.byPrototypeFields(['onHoverEmoji', 'selectEmoji']), + + /* Context Menus */ + ContextMenuActions: Filters.byCode(/CONTEXT_MENU_CLOSE/, c => c.close), + ContextMenuItemsGroup: Filters.byCode(/itemGroup/), + ContextMenuItem: Filters.byCode(/\.label\b.*\.hint\b.*\.action\b/), + + /* In-Message Links */ + ExternalLink: Filters.byCode(/\.trusted\b/) +}; + +class WebpackModules { + + /** + * Finds a module using a filter function. + * @param {Function} filter A function to use to filter modules + * @param {Boolean} first Whether to return only the first matching module + * @param {Array} modules An array of modules to search in + * @return {Any} + */ + static getModule(filter, first = true, _modules) { + const modules = _modules || this.getAllModules(); + const rm = []; + for (const index in modules) { + if (!modules.hasOwnProperty(index)) continue; + const module = modules[index]; + const { exports } = module; + let foundModule = null; + + if (!exports) continue; + if (exports.__esModule && exports.default && filter(exports.default)) foundModule = exports.default; + if (filter(exports)) foundModule = exports; + if (!foundModule) continue; + if (first) return foundModule; + rm.push(foundModule); + } + return first ? undefined : rm; + } + + /** + * Finds a module by it's name. + * @param {String} name The name of the module + * @param {Function} fallback A function to use to filter modules if not finding a known module + * @return {Any} + */ + static getModuleByName(name, fallback) { + if (Cache.hasOwnProperty(name)) return Cache[name]; + if (KnownModules.hasOwnProperty(name)) fallback = KnownModules[name]; + if (!fallback) return undefined; + const module = this.getModule(fallback, true); + return module ? Cache[name] = module : undefined; + } + + /** + * Finds a module by it's display name. + * @param {String} name The display name of the module + * @return {Any} + */ + static getModuleByDisplayName(name) { + return this.getModule(Filters.byDisplayName(name), true); + } + + /** + * Finds a module using it's code. + * @param {RegEx} regex A regular expression to use to filter modules + * @param {Boolean} first Whether to return the only the first matching module + * @return {Any} + */ + static getModuleByRegex(regex, first = true) { + return this.getModule(Filters.byCode(regex), first); + } + + /** + * Finds a module using properties on it's prototype. + * @param {Array} props Properties to use to filter modules + * @param {Boolean} first Whether to return only the first matching module + * @return {Any} + */ + static getModuleByPrototypes(prototypes, first = true) { + return this.getModule(Filters.byPrototypeFields(prototypes), first); + } + + /** + * Finds a module using it's own properties. + * @param {Array} props Properties to use to filter modules + * @param {Boolean} first Whether to return only the first matching module + * @return {Any} + */ + static getModuleByProps(props, first = true) { + return this.getModule(Filters.byProperties(props), first); + } + + /** + * Discord's __webpack_require__ function. + */ + static get require() { + if (this._require) return this._require; + + const __webpack_require__ = this.getWebpackRequire(); + if (!__webpack_require__) return; + + this.hookWebpackRequireCache(__webpack_require__); + return this._require = __webpack_require__; + } + + static getWebpackRequire() { + const id = 'bd-webpackmodules'; + + if (typeof window.webpackJsonp === 'function') { + const __webpack_require__ = window['webpackJsonp']([], { + [id]: (module, exports, __webpack_require__) => exports.default = __webpack_require__ + }, [id]).default; + delete __webpack_require__.m[id]; + delete __webpack_require__.c[id]; + return __webpack_require__; + } else if (window.webpackJsonp && window.webpackJsonp.push) { + const __webpack_require__ = window['webpackJsonp'].push([[], { + [id]: (module, exports, req) => exports.default = req + }, [[id]]]).default; + window['webpackJsonp'].pop(); + delete __webpack_require__.m[id]; + delete __webpack_require__.c[id]; + return __webpack_require__; + } + } + + static hookWebpackRequireCache(__webpack_require__) { + __webpack_require__.c = new Proxy(__webpack_require__.c, { + set(module_cache, module_id, module) { + // Add it to our emitter cache and emit a module-loading event + this.moduleLoading(module_id, module); + Events.emit('module-loading', module); + + // Add the module to the cache as normal + module_cache[module_id] = module; + } + }); + } + + static moduleLoading(module_id, module) { + if (this.require.c[module_id]) return; + + if (!this.moduleLoadedEventTimeout) { + this.moduleLoadedEventTimeout = setTimeout(() => { + this.moduleLoadedEventTimeout = undefined; + + // Emit a module-loaded event for every module + for (const module of this.modulesLoadingCache) { + Events.emit('module-loaded', module); + } + + // Emit a modules-loaded event + Events.emit('modules-loaded', this.modulesLoadingCache); + + this.modulesLoadedCache = []; + }, 0); + } + + // Add this to our own cache + if (!this.modulesLoadingCache) this.modulesLoadingCache = []; + this.modulesLoadingCache.push(module); + } + + static waitForWebpackRequire() { + return Utils.until(() => this.require, 10); + } + + /** + * Waits for a module to load. + * This only returns a single module, as it can't guarentee there are no more modules that could + * match the filter, which is pretty much what that would be asking for. + * @param {Function} filter The name of a known module or a filter function + * @return {Any} + */ + static async waitForModule(filter) { + const module = this.getModule(filter); + if (module) return module; + + while (this.require.m.length > this.require.c.length) { + const additionalModules = await Events.once('modules-loaded'); + + const module = this.getModule(filter, true, additionalModules); + if (module) return module; + } + + throw new Error('All modules have now been loaded. None match the passed filter.'); + } + + /** + * Finds a module by it's name. + * @param {String} name The name of the module + * @param {Function} fallback A function to use to filter modules if not finding a known module + * @return {Any} + */ + static async waitForModuleByName(name, fallback) { + if (Cache.hasOwnProperty(name)) return Cache[name]; + if (KnownModules.hasOwnProperty(name)) fallback = KnownModules[name]; + if (!fallback) return undefined; + const module = await this.waitForModule(fallback, true); + return module ? Cache[name] = module : undefined; + } + + static waitForModuleByDisplayName(props) { + return this.waitForModule(Filters.byDisplayName(props)); + } + static waitForModuleByRegex(props) { + return this.waitForModule(Filters.byCode(props)); + } + static waitForModuleByProps(props) { + return this.waitForModule(Filters.byProperties(props)); + } + static waitForModuleByPrototypes(props) { + return this.waitForModule(Filters.byPrototypeFields(props)); + } + + /** + * Searches for a class module and returns a class from it. + * @param {String} base The first part of the class to find + * @param {String} ...additional_classes Additional classes to look for to filter duplicate class modules + * @return {String} + */ + static getClassName(base, ...additional_classes) { + const class_module = this.getModuleByProps([base, ...additional_classes]); + if (class_module && class_module[base]) return class_module[base].split(' ')[0]; + } + static async waitForClassName(base, ...additional_classes) { + const class_module = await this.waitForModuleByProps([base, ...additional_classes]); + if (class_module && class_module[base]) return class_module[base].split(' ')[0]; + } + static getSelector(base, ...additional_classes) { + const gcn = this.getClassName(base, ...additional_classes); + if (gcn) return `.${gcn}`; + } + + /** + * Returns all loaded modules. + * @return {Array} + */ + static getAllModules() { + return this.require.c; + } + + /** + * Returns an array of known modules. + * @return {Array} + */ + static listKnownModules() { + return Object.keys(KnownModules); + } + + static get KnownModules() { return KnownModules } + +} + +export { WebpackModules } diff --git a/client/src/modules/webpackmodules.js b/client/src/modules/webpackmodules.js index 90011ec7..4d977b68 100644 --- a/client/src/modules/webpackmodules.js +++ b/client/src/modules/webpackmodules.js @@ -1,11 +1,11 @@ /* Deprecation Notice */ -import { Modules } from './reflection/modules'; +import { WebpackModules } from './reflection/wpm.depr.js'; import { ClientLogger as Logger } from 'common'; -const DeprecationWarning = new Proxy(Modules, { - get(Modules, property) { +const DeprecationWarning = new Proxy(WebpackModules, { + get(WebpackModules, property) { Logger.warn('DEPR', 'WebpackModules is deprecated. Use Reflection.Modules instead.'); - return Modules[property] || Modules.getModuleByName(property); + return WebpackModules[property] || WebpackModules.getModuleByName(property); } });