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