Plugin
ConsoleShortcuts
Adds shorter Aliases for many things on the window. Run `shortcutList` for a list.
1
import { loadLazyChunks } from "@debug/loadLazyChunks";2
import { Devs } from "@utils/constants";3
import { getCurrentChannel, getCurrentGuild } from "@utils/discord";4
import { runtimeHashMessageKey } from "@utils/intlHash";5
import { SYM_LAZY_CACHED, SYM_LAZY_GET } from "@utils/lazy";6
import { sleep } from "@utils/misc";7
import { relaunch } from "@utils/native";8
import { canonicalizeMatch, canonicalizeReplace, canonicalizeReplacement } from "@utils/patches";9
import definePlugin, { PluginNative, StartAt } from "@utils/types";10
import * as Webpack from "@webpack";11
import { extract, filters, findAll, findModuleId, search } from "@webpack";12
import * as Common from "@webpack/common";13
import type { ComponentType } from "react";14
15
const DESKTOP_ONLY = (f: string) => () => {16
throw new Error(`039;${f}039; is Discord Desktop only.`);17
};18
19
const makeVesktopSwitcher = (branch: string) => () => {20
if (Vesktop.Settings.store.discordBranch === branch)21
throw new Error(`Already on ${branch}`);22
23
Vesktop.Settings.store.discordBranch = branch;24
VesktopNative.app.relaunch();25
};26
27
const define: typeof Object.defineProperty =28
(obj, prop, desc) => {29
if (Object.hasOwn(desc, "value"))30
desc.writable = true;31
32
return Object.defineProperty(obj, prop, {33
configurable: true,34
enumerable: true,35
...desc36
});37
};38
39
function makeShortcuts() {40
function newFindWrapper(filterFactory: (...props: any[]) => Webpack.FilterFn, topLevelOnly = false) {41
const cache = new Map<string, unknown>();42
43
return function (...filterProps: unknown[]) {44
const cacheKey = String(filterProps);45
if (cache.has(cacheKey)) return cache.get(cacheKey);46
47
const matches = findAll(filterFactory(...filterProps), { topLevelOnly });48
49
const result = (() => {50
switch (matches.length) {51
case 0: return null;52
case 1: return matches[0];53
default:54
const uniqueMatches = [...new Set(matches)];55
if (uniqueMatches.length > 1)56
console.warn(`Warning: This filter matches ${uniqueMatches.length} exports. Make it more specific!\n`, uniqueMatches);57
58
return matches[0];59
}60
})();61
if (result && cacheKey) cache.set(cacheKey, result);62
return result;63
};64
}65
66
function findStoreWrapper(findStore: typeof Webpack.findStore) {67
const cache = new Map<string, unknown>();68
69
return function (storeName: string) {70
const cacheKey = String(storeName);71
if (cache.has(cacheKey)) return cache.get(cacheKey);72
73
let store: unknown;74
try {75
store = findStore(storeName);76
} catch { }77
if (store) cache.set(cacheKey, store);78
return store;79
};80
}81
82
let fakeRenderWin: WeakRef<Window> | undefined;83
const find = newFindWrapper(f => f);84
const findByProps = newFindWrapper(filters.byProps);85
86
return {87
...Object.fromEntries(Object.keys(Common).map(key => [key, { getter: () => Common[key] }])),88
wp: Webpack,89
wpc: { getter: () => Webpack.cache },90
wreq: { getter: () => Webpack.wreq },91
wpPatcher: { getter: () => Vencord.WebpackPatcher },92
wpInstances: { getter: () => Vencord.WebpackPatcher.allWebpackInstances },93
wpsearch: search,94
wpex: extract,95
wpexs: (code: string) => extract(findModuleId(code)!),96
loadLazyChunks: IS_DEV ? loadLazyChunks : () => { throw new Error("loadLazyChunks is dev only."); },97
find,98
findAll: findAll,99
findByProps,100
findAllByProps: (...props: string[]) => findAll(filters.byProps(...props)),101
findByCode: newFindWrapper(filters.byCode),102
findCssClasses: newFindWrapper(filters.byClassNames, true),103
findAllByCode: (code: string) => findAll(filters.byCode(code)),104
findComponentByCode: newFindWrapper(filters.componentByCode),105
findAllComponentsByCode: (...code: string[]) => findAll(filters.componentByCode(...code)),106
findExportedComponent: (...props: string[]) => findByProps(...props)[props[0]],107
findStore: findStoreWrapper(Webpack.findStore),108
PluginsApi: { getter: () => Vencord.Plugins },109
plugins: { getter: () => Vencord.Plugins.plugins },110
Settings: { getter: () => Vencord.Settings },111
Api: { getter: () => Vencord.Api },112
Util: { getter: () => Vencord.Util },113
reload: () => location.reload(),114
restart: IS_WEB ? DESKTOP_ONLY("restart") : relaunch,115
canonicalizeMatch,116
canonicalizeReplace,117
canonicalizeReplacement,118
runtimeHashMessageKey,119
fakeRender: (component: ComponentType, props: any) => {120
const prevWin = fakeRenderWin?.deref();121
const win = prevWin?.closed === false122
? prevWin123
: window.open("about:blank", "Fake Render", "popup,width=500,height=500")!;124
fakeRenderWin = new WeakRef(win);125
win.focus();126
127
const doc = win.document;128
doc.body.style.margin = "1em";129
130
if (!win.prepared) {131
win.prepared = true;132
133
[...document.querySelectorAll("style"), ...document.querySelectorAll("link[rel=stylesheet]")].forEach(s => {134
const n = s.cloneNode(true) as HTMLStyleElement | HTMLLinkElement;135
136
if (s.parentElement?.tagName === "HEAD")137
doc.head.append(n);138
else if (n.id?.startsWith("vencord-") || n.id?.startsWith("vcd-"))139
doc.documentElement.append(n);140
else141
doc.body.append(n);142
});143
}144
145
const root = Common.createRoot(doc.body.appendChild(document.createElement("div")));146
root.render(Common.React.createElement(component, props));147
148
doc.addEventListener("close", () => root.unmount(), { once: true });149
},150
151
preEnable: (plugin: string) => (Vencord.Settings.plugins[plugin] ??= { enabled: true }).enabled = true,152
153
channel: { getter: () => getCurrentChannel(), preload: false },154
channelId: { getter: () => Common.SelectedChannelStore.getChannelId(), preload: false },155
guild: { getter: () => getCurrentGuild(), preload: false },156
guildId: { getter: () => Common.SelectedGuildStore.getGuildId(), preload: false },157
me: { getter: () => Common.UserStore.getCurrentUser(), preload: false },158
meId: { getter: () => Common.UserStore.getCurrentUser().id, preload: false },159
messages: { getter: () => Common.MessageStore.getMessages(Common.SelectedChannelStore.getChannelId()), preload: false },160
openModal: { getter: () => Common.openModal },161
openModalLazy: { getter: () => Common.openModalLazy },162
163
Stores: { getter: () => Object.fromEntries(Webpack.fluxStores) },164
165
// e.g. "2024-05_desktop_visual_refresh", 0166
setExperiment: (id: string, bucket: number) => {167
Common.FluxDispatcher.dispatch({168
type: "EXPERIMENT_OVERRIDE_BUCKET",169
experimentId: id,170
experimentBucket: bucket,171
});172
},173
...IS_VESKTOP ? {174
vesktopStable: makeVesktopSwitcher("stable"),175
vesktopCanary: makeVesktopSwitcher("canary"),176
vesktopPtb: makeVesktopSwitcher("ptb"),177
} : {},178
};179
}180
181
function loadAndCacheShortcut(key: string, val: any, forceLoad: boolean) {182
const currentVal = val.getter();183
if (!currentVal || val.preload === false) return currentVal;184
185
function unwrapProxy(value: any) {186
if (value[SYM_LAZY_GET]) {187
forceLoad ? currentVal[SYM_LAZY_GET]() : currentVal[SYM_LAZY_CACHED];188
} else if (value.$$vencordGetWrappedComponent) {189
return forceLoad ? value.$$vencordGetWrappedComponent() : value;190
}191
192
return value;193
}194
195
const value = unwrapProxy(currentVal);196
if (typeof value === "object" && value !== null) {197
const descriptors = Object.getOwnPropertyDescriptors(value);198
199
for (const propKey in descriptors) {200
if (value[propKey] == null) continue;201
202
const descriptor = descriptors[propKey];203
if (descriptor.writable === true || descriptor.set != null) {204
const currentValue = value[propKey];205
const newValue = unwrapProxy(currentValue);206
if (newValue != null && currentValue !== newValue) {207
value[propKey] = newValue;208
}209
}210
}211
}212
213
if (value != null) {214
define(window.shortcutList, key, { value });215
define(window, key, { value });216
}217
218
return value;219
}220
221
const webpackModulesProbablyLoaded = Webpack.onceReady.then(() => sleep(1000));222
223
export default definePlugin({224
name: "ConsoleShortcuts",225
description: "Adds shorter Aliases for many things on the window. Run `shortcutList` for a list.",226
authors: [Devs.Ven],227
tags: ["Developers", "Console", "Shortcuts", "Utility"],228
startAt: StartAt.Init,229
230
patches: [231
{232
find: "&&this.initializeIfNeeded()",233
replacement: [234
{235
match: /\i&&this\.initializeIfNeeded\(\)/,236
replace: "$&,Reflect.defineProperty(this,Symbol.toStringTag,{value:this.getName(),configurable:!0,writable:!0,enumerable:!1})"237
}238
]239
}240
],241
242
243
start() {244
const shortcuts = makeShortcuts();245
window.shortcutList = {};246
247
for (const [key, val] of Object.entries(shortcuts)) {248
if ("getter" in val) {249
define(window.shortcutList, key, {250
get: () => loadAndCacheShortcut(key, val, true)251
});252
253
define(window, key, {254
get: () => window.shortcutList[key]255
});256
} else {257
window.shortcutList[key] = val;258
window[key] = val;259
}260
}261
262
// unproxy loaded modules263
this.eagerLoad(false);264
265
if (!IS_WEB) {266
const Native = VencordNative.pluginHelpers.ConsoleShortcuts as PluginNative<typeof import("./native")>;267
Native.initDevtoolsOpenEagerLoad();268
}269
},270
271
async eagerLoad(forceLoad: boolean) {272
await webpackModulesProbablyLoaded;273
274
const shortcuts = makeShortcuts();275
276
for (const [key, val] of Object.entries(shortcuts)) {277
if (!Object.hasOwn(val, "getter") || (val as any).preload === false) continue;278
279
try {280
loadAndCacheShortcut(key, val, forceLoad);281
} catch { } class="ts-cmt">// swallow not found errors in DEV282
}283
},284
285
stop() {286
delete window.shortcutList;287
for (const key in makeShortcuts()) {288
delete window[key];289
}290
}291
});292