ConsoleShortcuts: Fix autocomplete on lazies, add more utils (#2519)

This commit is contained in:
vee 2024-05-28 22:31:58 +02:00 committed by GitHub
parent b9e83d9d28
commit a78dba321d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 192 additions and 113 deletions

View file

@ -17,24 +17,33 @@
*/
import { Devs } from "@utils/constants";
import { getCurrentChannel, getCurrentGuild } from "@utils/discord";
import { SYM_LAZY_CACHED, SYM_LAZY_GET } from "@utils/lazy";
import { relaunch } from "@utils/native";
import { canonicalizeMatch, canonicalizeReplace, canonicalizeReplacement } from "@utils/patches";
import definePlugin, { StartAt } from "@utils/types";
import definePlugin, { PluginNative, StartAt } from "@utils/types";
import * as Webpack from "@webpack";
import { extract, filters, findAll, findModuleId, search } from "@webpack";
import * as Common from "@webpack/common";
import type { ComponentType } from "react";
const WEB_ONLY = (f: string) => () => {
const DESKTOP_ONLY = (f: string) => () => {
throw new Error(`'${f}' is Discord Desktop only.`);
};
export default definePlugin({
name: "ConsoleShortcuts",
description: "Adds shorter Aliases for many things on the window. Run `shortcutList` for a list.",
authors: [Devs.Ven],
const define: typeof Object.defineProperty =
(obj, prop, desc) => {
if (Object.hasOwn(desc, "value"))
desc.writable = true;
getShortcuts(): Record<PropertyKey, any> {
return Object.defineProperty(obj, prop, {
configurable: true,
enumerable: true,
...desc
});
};
function makeShortcuts() {
function newFindWrapper(filterFactory: (...props: any[]) => Webpack.FilterFn) {
const cache = new Map<string, unknown>();
@ -87,14 +96,17 @@ export default definePlugin({
plugins: { getter: () => Vencord.Plugins.plugins },
Settings: { getter: () => Vencord.Settings },
Api: { getter: () => Vencord.Api },
Util: { getter: () => Vencord.Util },
reload: () => location.reload(),
restart: IS_WEB ? WEB_ONLY("restart") : relaunch,
restart: IS_WEB ? DESKTOP_ONLY("restart") : relaunch,
canonicalizeMatch,
canonicalizeReplace,
canonicalizeReplacement,
fakeRender: (component: ComponentType, props: any) => {
const prevWin = fakeRenderWin?.deref();
const win = prevWin?.closed === false ? prevWin : window.open("about:blank", "Fake Render", "popup,width=500,height=500")!;
const win = prevWin?.closed === false
? prevWin
: window.open("about:blank", "Fake Render", "popup,width=500,height=500")!;
fakeRenderWin = new WeakRef(win);
win.focus();
@ -117,38 +129,86 @@ export default definePlugin({
}
Common.ReactDOM.render(Common.React.createElement(component, props), doc.body.appendChild(document.createElement("div")));
}
};
},
preEnable: (plugin: string) => (Vencord.Settings.plugins[plugin] ??= { enabled: true }).enabled = true,
channel: { getter: () => getCurrentChannel(), preload: false },
channelId: { getter: () => Common.SelectedChannelStore.getChannelId(), preload: false },
guild: { getter: () => getCurrentGuild(), preload: false },
guildId: { getter: () => Common.SelectedGuildStore.getGuildId(), preload: false },
me: { getter: () => Common.UserStore.getCurrentUser(), preload: false },
meId: { getter: () => Common.UserStore.getCurrentUser().id, preload: false },
messages: { getter: () => Common.MessageStore.getMessages(Common.SelectedChannelStore.getChannelId()), preload: false }
};
}
function loadAndCacheShortcut(key: string, val: any, forceLoad: boolean) {
const currentVal = val.getter();
if (!currentVal || val.preload === false) return currentVal;
const value = currentVal[SYM_LAZY_GET]
? forceLoad ? currentVal[SYM_LAZY_GET]() : currentVal[SYM_LAZY_CACHED]
: currentVal;
if (value) define(window.shortcutList, key, { value });
return value;
}
export default definePlugin({
name: "ConsoleShortcuts",
description: "Adds shorter Aliases for many things on the window. Run `shortcutList` for a list.",
authors: [Devs.Ven],
startAt: StartAt.Init,
start() {
const shortcuts = this.getShortcuts();
const shortcuts = makeShortcuts();
window.shortcutList = {};
for (const [key, val] of Object.entries(shortcuts)) {
if (val.getter != null) {
Object.defineProperty(window.shortcutList, key, {
get: val.getter,
configurable: true,
enumerable: true
if ("getter" in val) {
define(window.shortcutList, key, {
get: () => loadAndCacheShortcut(key, val, true)
});
Object.defineProperty(window, key, {
get: () => window.shortcutList[key],
configurable: true,
enumerable: true
define(window, key, {
get: () => window.shortcutList[key]
});
} else {
window.shortcutList[key] = val;
window[key] = val;
}
}
// unproxy loaded modules
Webpack.onceReady.then(() => {
setTimeout(() => this.eagerLoad(false), 1000);
if (!IS_WEB) {
const Native = VencordNative.pluginHelpers.ConsoleShortcuts as PluginNative<typeof import("./native")>;
Native.initDevtoolsOpenEagerLoad();
}
});
},
async eagerLoad(forceLoad: boolean) {
await Webpack.onceReady;
const shortcuts = makeShortcuts();
for (const [key, val] of Object.entries(shortcuts)) {
if (!Object.hasOwn(val, "getter") || (val as any).preload === false) continue;
try {
loadAndCacheShortcut(key, val, forceLoad);
} catch { } // swallow not found errors in DEV
}
},
stop() {
delete window.shortcutList;
for (const key in this.getShortcuts()) {
for (const key in makeShortcuts()) {
delete window[key];
}
}

View file

@ -0,0 +1,16 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { IpcMainInvokeEvent } from "electron";
export function initDevtoolsOpenEagerLoad(e: IpcMainInvokeEvent) {
const handleDevtoolsOpened = () => e.sender.executeJavaScript("Vencord.Plugins.plugins.ConsoleShortcuts.eagerLoad(true)");
if (e.sender.isDevToolsOpened())
handleDevtoolsOpened();
else
e.sender.once("devtools-opened", () => handleDevtoolsOpened());
}

View file

@ -35,8 +35,8 @@ const unconfigurable = ["arguments", "caller", "prototype"];
const handler: ProxyHandler<any> = {};
const kGET = Symbol.for("vencord.lazy.get");
const kCACHE = Symbol.for("vencord.lazy.cached");
export const SYM_LAZY_GET = Symbol.for("vencord.lazy.get");
export const SYM_LAZY_CACHED = Symbol.for("vencord.lazy.cached");
for (const method of [
"apply",
@ -53,11 +53,11 @@ for (const method of [
"setPrototypeOf"
]) {
handler[method] =
(target: any, ...args: any[]) => Reflect[method](target[kGET](), ...args);
(target: any, ...args: any[]) => Reflect[method](target[SYM_LAZY_GET](), ...args);
}
handler.ownKeys = target => {
const v = target[kGET]();
const v = target[SYM_LAZY_GET]();
const keys = Reflect.ownKeys(v);
for (const key of unconfigurable) {
if (!keys.includes(key)) keys.push(key);
@ -69,7 +69,7 @@ handler.getOwnPropertyDescriptor = (target, p) => {
if (typeof p === "string" && unconfigurable.includes(p))
return Reflect.getOwnPropertyDescriptor(target, p);
const descriptor = Reflect.getOwnPropertyDescriptor(target[kGET](), p);
const descriptor = Reflect.getOwnPropertyDescriptor(target[SYM_LAZY_GET](), p);
if (descriptor) Object.defineProperty(target, p, descriptor);
return descriptor;
@ -92,31 +92,34 @@ export function proxyLazy<T>(factory: () => T, attempts = 5, isChild = false): T
let tries = 0;
const proxyDummy = Object.assign(function () { }, {
[kCACHE]: void 0 as T | undefined,
[kGET]() {
if (!proxyDummy[kCACHE] && attempts > tries++) {
proxyDummy[kCACHE] = factory();
if (!proxyDummy[kCACHE] && attempts === tries)
[SYM_LAZY_CACHED]: void 0 as T | undefined,
[SYM_LAZY_GET]() {
if (!proxyDummy[SYM_LAZY_CACHED] && attempts > tries++) {
proxyDummy[SYM_LAZY_CACHED] = factory();
if (!proxyDummy[SYM_LAZY_CACHED] && attempts === tries)
console.error("Lazy factory failed:", factory);
}
return proxyDummy[kCACHE];
return proxyDummy[SYM_LAZY_CACHED];
}
});
return new Proxy(proxyDummy, {
...handler,
get(target, p, receiver) {
if (p === SYM_LAZY_CACHED || p === SYM_LAZY_GET)
return Reflect.get(target, p, receiver);
// if we're still in the same tick, it means the lazy was immediately used.
// thus, we lazy proxy the get access to make things like destructuring work as expected
// meow here will also be a lazy
// `const { meow } = findByPropsLazy("meow");`
if (!isChild && isSameTick)
return proxyLazy(
() => Reflect.get(target[kGET](), p, receiver),
() => Reflect.get(target[SYM_LAZY_GET](), p, receiver),
attempts,
true
);
const lazyTarget = target[kGET]();
const lazyTarget = target[SYM_LAZY_GET]();
if (typeof lazyTarget === "object" || typeof lazyTarget === "function") {
return Reflect.get(lazyTarget, p, receiver);
}