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
1import { loadLazyChunks } from "@debug/loadLazyChunks";
2import { Devs } from "@utils/constants";
3import { getCurrentChannel, getCurrentGuild } from "@utils/discord";
4import { runtimeHashMessageKey } from "@utils/intlHash";
5import { SYM_LAZY_CACHED, SYM_LAZY_GET } from "@utils/lazy";
6import { sleep } from "@utils/misc";
7import { relaunch } from "@utils/native";
8import { canonicalizeMatch, canonicalizeReplace, canonicalizeReplacement } from "@utils/patches";
9import definePlugin, { PluginNative, StartAt } from "@utils/types";
10import * as Webpack from "@webpack";
11import { extract, filters, findAll, findModuleId, search } from "@webpack";
12import * as Common from "@webpack/common";
13import type { ComponentType } from "react";
14
15const DESKTOP_ONLY = (f: string) => () => {
16 throw new Error(`'${f}' is Discord Desktop only.`);
17};
18
19const 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
27const 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 ...desc
36 });
37 };
38
39function 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 === false
122 ? prevWin
123 : 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 else
141 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", 0
166 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
181function 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
221const webpackModulesProbablyLoaded = Webpack.onceReady.then(() => sleep(1000));
222
223export 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 modules
263 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 DEV
282 }
283 },
284
285 stop() {
286 delete window.shortcutList;
287 for (const key in makeShortcuts()) {
288 delete window[key];
289 }
290 }
291});
292