Plugin

ConsoleShortcuts

Adds shorter Aliases for many things on the window. Run `shortcutList` for a list.

Developers Console Shortcuts Utility
index.ts
Download

Source

src/plugins/consoleShortcuts/index.ts
1/*
2 * FiveCord — a Discord client mod
3 * Copyright (c) 2025 FiveCord
4 * SPDX-License-Identifier: GPL-3.0-or-later
5 */
6
7import { loadLazyChunks } from "@debug/loadLazyChunks";
8import { Devs } from "@utils/constants";
9import { getCurrentChannel, getCurrentGuild } from "@utils/discord";
10import { runtimeHashMessageKey } from "@utils/intlHash";
11import { SYM_LAZY_CACHED, SYM_LAZY_GET } from "@utils/lazy";
12import { sleep } from "@utils/misc";
13import { relaunch } from "@utils/native";
14import { canonicalizeMatch, canonicalizeReplace, canonicalizeReplacement } from "@utils/patches";
15import definePlugin, { PluginNative, StartAt } from "@utils/types";
16import * as Webpack from "@webpack";
17import { extract, filters, findAll, findModuleId, search } from "@webpack";
18import * as Common from "@webpack/common";
19import type { ComponentType } from "react";
20
21const DESKTOP_ONLY = (f: string) => () => {
22 throw new Error(`'${f}' is Discord Desktop only.`);
23};
24
25const 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
33const 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 ...desc
42 });
43 };
44
45function 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 === false
128 ? prevWin
129 : 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 else
147 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", 0
172 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
187function 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
227const webpackModulesProbablyLoaded = Webpack.onceReady.then(() => sleep(1000));
228
229export 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 modules
269 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 DEV
288 }
289 },
290
291 stop() {
292 delete window.shortcutList;
293 for (const key in makeShortcuts()) {
294 delete window[key];
295 }
296 }
297});
298