2024-08-21 01:41:47 -04:00
|
|
|
/*
|
|
|
|
|
* Vencord, a Discord client mod
|
|
|
|
|
* Copyright (c) 2024 Vendicated and contributors
|
|
|
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
|
*/
|
|
|
|
|
|
2025-01-09 02:58:32 -05:00
|
|
|
import { popNotice, showNotice } from "@api/Notices";
|
|
|
|
|
import ErrorBoundary from "@components/ErrorBoundary";
|
2024-08-21 01:41:47 -04:00
|
|
|
import { canonicalizeMatch, canonicalizeReplace } from "@utils/patches";
|
|
|
|
|
import { filters, findAll, search, wreq } from "@webpack";
|
2025-01-09 02:58:32 -05:00
|
|
|
import { React, Toasts, useState } from "@webpack/common";
|
|
|
|
|
import { loadLazyChunks } from "debug/loadLazyChunks";
|
2024-08-28 01:35:47 -04:00
|
|
|
import { Settings } from "Vencord";
|
2024-08-21 01:41:47 -04:00
|
|
|
|
|
|
|
|
import { logger, PORT, settings } from ".";
|
2025-01-09 02:58:32 -05:00
|
|
|
import { Recieve } from "./types";
|
|
|
|
|
import { FullOutgoingMessage, OutgoingMessage } from "./types/send";
|
|
|
|
|
import { extractModule, extractOrThrow, findModuleId, mkRegexFind, parseNode, toggleEnabled, } from "./util";
|
2024-08-21 01:41:47 -04:00
|
|
|
|
|
|
|
|
export function stopWs() {
|
|
|
|
|
socket?.close(1000, "Plugin Stopped");
|
|
|
|
|
socket = void 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export let socket: WebSocket | undefined;
|
|
|
|
|
|
|
|
|
|
export function initWs(isManual = false) {
|
|
|
|
|
let wasConnected = isManual;
|
|
|
|
|
let hasErrored = false;
|
|
|
|
|
const ws = socket = new WebSocket(`ws://localhost:${PORT}`);
|
|
|
|
|
|
2025-01-09 02:58:32 -05:00
|
|
|
function replyData(data: OutgoingMessage) {
|
2024-08-21 01:41:47 -04:00
|
|
|
ws.send(JSON.stringify(data));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ws.addEventListener("open", () => {
|
|
|
|
|
wasConnected = true;
|
|
|
|
|
|
|
|
|
|
logger.info("Connected to WebSocket");
|
|
|
|
|
|
|
|
|
|
// send module cache to vscode
|
|
|
|
|
replyData({
|
|
|
|
|
type: "moduleList",
|
2025-01-09 02:58:32 -05:00
|
|
|
data: {
|
|
|
|
|
modules: Object.keys(wreq.m)
|
|
|
|
|
},
|
|
|
|
|
ok: true
|
2024-08-21 01:41:47 -04:00
|
|
|
});
|
2024-08-23 13:25:13 -04:00
|
|
|
|
2025-01-09 02:58:32 -05:00
|
|
|
try {
|
|
|
|
|
if (settings.store.notifyOnAutoConnect || isManual) {
|
|
|
|
|
Toasts.show({
|
|
|
|
|
message: "Connected to WebSocket",
|
|
|
|
|
id: Toasts.genId(),
|
|
|
|
|
type: Toasts.Type.SUCCESS,
|
|
|
|
|
options: {
|
|
|
|
|
position: Toasts.Position.TOP
|
|
|
|
|
}
|
|
|
|
|
});
|
2024-09-21 15:11:29 -04:00
|
|
|
}
|
2025-01-09 02:58:32 -05:00
|
|
|
}
|
|
|
|
|
catch (e) {
|
|
|
|
|
console.error(e);
|
|
|
|
|
}
|
2024-08-21 01:41:47 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
ws.addEventListener("error", e => {
|
|
|
|
|
if (!wasConnected) return;
|
|
|
|
|
|
|
|
|
|
hasErrored = true;
|
|
|
|
|
|
|
|
|
|
logger.error("Dev Companion Error:", e);
|
|
|
|
|
|
2024-09-21 15:11:29 -04:00
|
|
|
Toasts.show({
|
|
|
|
|
message: "Dev Companion Error",
|
|
|
|
|
id: Toasts.genId(),
|
|
|
|
|
type: Toasts.Type.FAILURE,
|
|
|
|
|
options: {
|
|
|
|
|
position: Toasts.Position.TOP
|
|
|
|
|
}
|
2024-08-21 01:41:47 -04:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
ws.addEventListener("close", e => {
|
|
|
|
|
if (!wasConnected || hasErrored) return;
|
|
|
|
|
|
|
|
|
|
logger.info("Dev Companion Disconnected:", e.code, e.reason);
|
|
|
|
|
|
2024-09-21 15:11:29 -04:00
|
|
|
Toasts.show({
|
|
|
|
|
message: "Dev Companion Disconnected",
|
|
|
|
|
id: Toasts.genId(),
|
|
|
|
|
type: Toasts.Type.FAILURE,
|
|
|
|
|
options: {
|
|
|
|
|
position: Toasts.Position.TOP
|
2024-08-21 01:41:47 -04:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
ws.addEventListener("message", e => {
|
|
|
|
|
try {
|
2025-01-09 02:58:32 -05:00
|
|
|
var d = JSON.parse(e.data) as Recieve.FullIncomingMessage;
|
2024-08-21 01:41:47 -04:00
|
|
|
} catch (err) {
|
|
|
|
|
logger.error("Invalid JSON:", err, "\n" + e.data);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-01-09 02:58:32 -05:00
|
|
|
/**
|
|
|
|
|
* @param error the error to reply with. if there is no error, the reply is a sucess
|
|
|
|
|
*/
|
2024-08-21 01:41:47 -04:00
|
|
|
function reply(error?: string) {
|
2025-01-09 02:58:32 -05:00
|
|
|
const data = { nonce: d.nonce, ok: !error } as Record<string, unknown>;
|
2024-08-21 01:41:47 -04:00
|
|
|
if (error) data.error = error;
|
|
|
|
|
|
|
|
|
|
ws.send(JSON.stringify(data));
|
|
|
|
|
}
|
2025-01-09 02:58:32 -05:00
|
|
|
function replyData(data: OutgoingMessage) {
|
|
|
|
|
const toSend: FullOutgoingMessage = {
|
|
|
|
|
...data,
|
|
|
|
|
nonce: d.nonce
|
|
|
|
|
};
|
|
|
|
|
// data.nonce = d.nonce;
|
|
|
|
|
ws.send(JSON.stringify(toSend));
|
2024-08-21 01:41:47 -04:00
|
|
|
}
|
|
|
|
|
|
2025-01-09 02:58:32 -05:00
|
|
|
logger.info("Received Message:", d.type, "\n", d.data);
|
2024-08-21 01:41:47 -04:00
|
|
|
|
2025-01-09 02:58:32 -05:00
|
|
|
switch (d.type) {
|
2024-08-28 01:35:47 -04:00
|
|
|
case "disable": {
|
2025-01-09 02:58:32 -05:00
|
|
|
const m = d.data;
|
|
|
|
|
const settings = Settings.plugins[m.pluginName];
|
|
|
|
|
if (m.enabled !== settings.enabled)
|
|
|
|
|
toggleEnabled(m.pluginName, reply);
|
2024-08-28 01:35:47 -04:00
|
|
|
break;
|
|
|
|
|
}
|
2024-09-17 17:00:34 -04:00
|
|
|
case "rawId": {
|
2025-01-09 02:58:32 -05:00
|
|
|
const m = d.data;
|
2024-09-17 17:00:34 -04:00
|
|
|
replyData({
|
2025-01-09 02:58:32 -05:00
|
|
|
type: "rawId",
|
2024-09-17 17:00:34 -04:00
|
|
|
ok: true,
|
2025-01-09 02:58:32 -05:00
|
|
|
data: extractModule(m.id),
|
2024-09-17 17:00:34 -04:00
|
|
|
});
|
|
|
|
|
break;
|
|
|
|
|
}
|
2024-08-21 01:41:47 -04:00
|
|
|
case "diff": {
|
|
|
|
|
try {
|
2025-01-09 02:58:32 -05:00
|
|
|
const m = d.data;
|
|
|
|
|
switch (m.extractType) {
|
2024-08-21 01:41:47 -04:00
|
|
|
case "id": {
|
2025-01-09 02:58:32 -05:00
|
|
|
if (typeof m.idOrSearch !== "number")
|
|
|
|
|
throw new Error("Id is not a number, got :" + typeof m.idOrSearch);
|
2024-08-21 01:41:47 -04:00
|
|
|
replyData({
|
|
|
|
|
type: "diff",
|
|
|
|
|
ok: true,
|
|
|
|
|
data: {
|
2025-01-09 02:58:32 -05:00
|
|
|
patched: extractOrThrow(m.idOrSearch),
|
|
|
|
|
source: extractModule(m.idOrSearch, false),
|
|
|
|
|
moduleNumber: m.idOrSearch
|
2024-08-21 01:41:47 -04:00
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case "search": {
|
2025-01-09 02:58:32 -05:00
|
|
|
let moduleId: number;
|
|
|
|
|
if (m.findType === "string")
|
|
|
|
|
moduleId = +findModuleId([m.idOrSearch.toString()]);
|
2024-08-21 01:41:47 -04:00
|
|
|
else
|
2025-01-09 02:58:32 -05:00
|
|
|
moduleId = +findModuleId(mkRegexFind(m.idOrSearch));
|
2024-08-21 01:41:47 -04:00
|
|
|
const p = extractOrThrow(moduleId);
|
|
|
|
|
const p2 = extractModule(moduleId, false);
|
2025-01-09 02:58:32 -05:00
|
|
|
|
2024-08-21 01:41:47 -04:00
|
|
|
replyData({
|
|
|
|
|
type: "diff",
|
|
|
|
|
ok: true,
|
|
|
|
|
data: {
|
|
|
|
|
patched: p,
|
2025-01-09 02:58:32 -05:00
|
|
|
source: p2,
|
|
|
|
|
moduleNumber: moduleId
|
2024-08-21 01:41:47 -04:00
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
reply(String(error));
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case "reload": {
|
|
|
|
|
reply();
|
|
|
|
|
window.location.reload();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case "extract": {
|
|
|
|
|
try {
|
2025-01-09 02:58:32 -05:00
|
|
|
const m = d.data;
|
|
|
|
|
switch (m.extractType) {
|
2024-08-21 01:41:47 -04:00
|
|
|
case "id": {
|
2025-01-09 02:58:32 -05:00
|
|
|
if (typeof m.idOrSearch !== "number")
|
|
|
|
|
throw new Error("Id is not a number, got :" + typeof m.idOrSearch);
|
2024-08-21 01:41:47 -04:00
|
|
|
|
|
|
|
|
else
|
|
|
|
|
replyData({
|
|
|
|
|
type: "extract",
|
|
|
|
|
ok: true,
|
2025-01-09 02:58:32 -05:00
|
|
|
data: {
|
|
|
|
|
module: extractModule(m.idOrSearch),
|
|
|
|
|
moduleNumber: m.idOrSearch,
|
|
|
|
|
},
|
2024-08-21 01:41:47 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case "search": {
|
|
|
|
|
let moduleId;
|
2025-01-09 02:58:32 -05:00
|
|
|
if (m.findType === "string")
|
|
|
|
|
moduleId = +findModuleId([m.idOrSearch.toString()]);
|
2024-08-21 01:41:47 -04:00
|
|
|
|
|
|
|
|
else
|
2025-01-09 02:58:32 -05:00
|
|
|
moduleId = +findModuleId(mkRegexFind(m.idOrSearch));
|
2024-08-21 01:41:47 -04:00
|
|
|
replyData({
|
|
|
|
|
type: "extract",
|
|
|
|
|
ok: true,
|
2025-01-09 02:58:32 -05:00
|
|
|
data: {
|
|
|
|
|
module: extractModule(moduleId),
|
|
|
|
|
moduleNumber: moduleId
|
|
|
|
|
},
|
2024-08-21 01:41:47 -04:00
|
|
|
});
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case "find": {
|
|
|
|
|
try {
|
2025-01-09 02:58:32 -05:00
|
|
|
var parsedArgs = m.findArgs.map(parseNode);
|
2024-08-21 01:41:47 -04:00
|
|
|
} catch (err) {
|
|
|
|
|
return reply("Failed to parse args: " + err);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
let results: any[];
|
2025-01-09 02:58:32 -05:00
|
|
|
switch (m.findType.replace("find", "").replace("Lazy", "")) {
|
2024-08-21 01:41:47 -04:00
|
|
|
case "":
|
2024-08-28 07:24:56 +12:00
|
|
|
case "Component":
|
2024-08-21 01:41:47 -04:00
|
|
|
results = findAll(parsedArgs[0]);
|
|
|
|
|
break;
|
|
|
|
|
case "ByProps":
|
|
|
|
|
results = findAll(filters.byProps(...parsedArgs));
|
|
|
|
|
break;
|
|
|
|
|
case "Store":
|
|
|
|
|
results = findAll(filters.byStoreName(parsedArgs[0]));
|
|
|
|
|
break;
|
|
|
|
|
case "ByCode":
|
|
|
|
|
results = findAll(filters.byCode(...parsedArgs));
|
|
|
|
|
break;
|
|
|
|
|
case "ModuleId":
|
|
|
|
|
results = Object.keys(search(parsedArgs[0]));
|
|
|
|
|
break;
|
|
|
|
|
case "ComponentByCode":
|
|
|
|
|
results = findAll(filters.componentByCode(...parsedArgs));
|
|
|
|
|
break;
|
|
|
|
|
default:
|
2025-01-09 02:58:32 -05:00
|
|
|
return reply("Unknown Find Type " + m.findType);
|
2024-08-21 01:41:47 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const uniqueResultsCount = new Set(results).size;
|
|
|
|
|
if (uniqueResultsCount === 0) throw "No results";
|
|
|
|
|
if (uniqueResultsCount > 1) throw "Found more than one result! Make this filter more specific";
|
|
|
|
|
// best name ever
|
|
|
|
|
const foundFind: string = [...results][0].toString();
|
|
|
|
|
replyData({
|
|
|
|
|
type: "extract",
|
|
|
|
|
ok: true,
|
2025-01-09 02:58:32 -05:00
|
|
|
data: {
|
|
|
|
|
module: foundFind,
|
|
|
|
|
find: true,
|
|
|
|
|
moduleNumber: +findModuleId([foundFind])
|
|
|
|
|
},
|
2024-08-21 01:41:47 -04:00
|
|
|
});
|
|
|
|
|
} catch (err) {
|
|
|
|
|
return reply("Failed to find: " + err);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
default:
|
2025-01-09 02:58:32 -05:00
|
|
|
reply(`Unknown Extract type. Got: ${d.data.extractType}`);
|
2024-08-21 01:41:47 -04:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
reply(String(error));
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case "testPatch": {
|
2025-01-09 02:58:32 -05:00
|
|
|
const m = d.data;
|
2024-08-21 01:41:47 -04:00
|
|
|
let candidates;
|
2025-01-09 02:58:32 -05:00
|
|
|
console.log(m.find.toString());
|
|
|
|
|
if (d.data.findType === "string")
|
|
|
|
|
candidates = search(m.find.toString());
|
2024-08-21 01:41:47 -04:00
|
|
|
|
|
|
|
|
else
|
2025-01-09 02:58:32 -05:00
|
|
|
candidates = search(...mkRegexFind(m.find));
|
2024-08-21 01:41:47 -04:00
|
|
|
|
|
|
|
|
// const candidates = search(find);
|
|
|
|
|
const keys = Object.keys(candidates);
|
|
|
|
|
if (keys.length !== 1)
|
|
|
|
|
return reply("Expected exactly one 'find' matches, found " + keys.length);
|
|
|
|
|
|
|
|
|
|
const mod = candidates[keys[0]];
|
|
|
|
|
let src = String(mod.original ?? mod).replaceAll("\n", "");
|
|
|
|
|
|
|
|
|
|
if (src.startsWith("function(")) {
|
|
|
|
|
src = "0," + src;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let i = 0;
|
|
|
|
|
|
2025-01-09 02:58:32 -05:00
|
|
|
for (const { match, replace } of m.replacement) {
|
2024-08-21 01:41:47 -04:00
|
|
|
i++;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const matcher = canonicalizeMatch(parseNode(match));
|
|
|
|
|
const replacement = canonicalizeReplace(parseNode(replace), "PlaceHolderPluginName");
|
|
|
|
|
|
|
|
|
|
const newSource = src.replace(matcher, replacement as string);
|
|
|
|
|
|
|
|
|
|
if (src === newSource) throw "Had no effect";
|
|
|
|
|
Function(newSource);
|
|
|
|
|
|
|
|
|
|
src = newSource;
|
|
|
|
|
} catch (err) {
|
|
|
|
|
return reply(`Replacement ${i} failed: ${err}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
reply();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case "testFind": {
|
2025-01-09 02:58:32 -05:00
|
|
|
const m = d.data;
|
2024-08-21 01:41:47 -04:00
|
|
|
try {
|
2025-01-09 02:58:32 -05:00
|
|
|
var parsedArgs = m.args.map(parseNode);
|
2024-08-21 01:41:47 -04:00
|
|
|
} catch (err) {
|
|
|
|
|
return reply("Failed to parse args: " + err);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
let results: any[];
|
2025-01-09 02:58:32 -05:00
|
|
|
switch (m.type.replace("find", "").replace("Lazy", "")) {
|
2024-08-21 01:41:47 -04:00
|
|
|
case "":
|
2024-08-28 07:24:56 +12:00
|
|
|
case "Component":
|
2024-08-21 01:41:47 -04:00
|
|
|
results = findAll(parsedArgs[0]);
|
|
|
|
|
break;
|
|
|
|
|
case "ByProps":
|
|
|
|
|
results = findAll(filters.byProps(...parsedArgs));
|
|
|
|
|
break;
|
|
|
|
|
case "Store":
|
|
|
|
|
results = findAll(filters.byStoreName(parsedArgs[0]));
|
|
|
|
|
break;
|
|
|
|
|
case "ByCode":
|
|
|
|
|
results = findAll(filters.byCode(...parsedArgs));
|
|
|
|
|
break;
|
|
|
|
|
case "ModuleId":
|
|
|
|
|
results = Object.keys(search(parsedArgs[0]));
|
|
|
|
|
break;
|
|
|
|
|
case "ComponentByCode":
|
|
|
|
|
results = findAll(filters.componentByCode(...parsedArgs));
|
|
|
|
|
break;
|
|
|
|
|
default:
|
2025-01-09 02:58:32 -05:00
|
|
|
return reply("Unknown Find Type " + m.type);
|
2024-08-21 01:41:47 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const uniqueResultsCount = new Set(results).size;
|
|
|
|
|
if (uniqueResultsCount === 0) throw "No results";
|
|
|
|
|
if (uniqueResultsCount > 1) throw "Found more than one result! Make this filter more specific";
|
|
|
|
|
} catch (err) {
|
|
|
|
|
return reply("Failed to find: " + err);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
reply();
|
|
|
|
|
break;
|
|
|
|
|
}
|
2025-01-09 02:58:32 -05:00
|
|
|
case "allModules": {
|
|
|
|
|
const { promise, resolve, reject } = Promise.withResolvers<void>();
|
|
|
|
|
// wrap in try/catch to prevent crashing if notice api is not loaded
|
|
|
|
|
try {
|
|
|
|
|
let closed = false;
|
|
|
|
|
const close = () => {
|
|
|
|
|
if (closed) return;
|
|
|
|
|
closed = true;
|
|
|
|
|
popNotice();
|
|
|
|
|
};
|
|
|
|
|
// @ts-expect-error it accepts react components
|
|
|
|
|
showNotice(<AllModulesNoti done={promise} close={close} />, "OK", () => {
|
|
|
|
|
closed = true;
|
|
|
|
|
popNotice();
|
|
|
|
|
});
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error(e);
|
|
|
|
|
}
|
|
|
|
|
loadLazyChunks()
|
|
|
|
|
.then(() => {
|
|
|
|
|
resolve();
|
|
|
|
|
replyData({
|
|
|
|
|
type: "moduleList",
|
|
|
|
|
data: {
|
|
|
|
|
modules: Object.keys(wreq.m)
|
|
|
|
|
},
|
|
|
|
|
ok: true
|
|
|
|
|
});
|
|
|
|
|
})
|
|
|
|
|
.catch(e => {
|
|
|
|
|
console.error(e);
|
|
|
|
|
replyData({
|
|
|
|
|
type: "moduleList",
|
|
|
|
|
ok: false,
|
|
|
|
|
error: String(e),
|
|
|
|
|
data: null
|
|
|
|
|
});
|
|
|
|
|
reject(e);
|
|
|
|
|
});
|
|
|
|
|
break;
|
|
|
|
|
}
|
2024-08-21 01:41:47 -04:00
|
|
|
default:
|
2025-01-09 02:58:32 -05:00
|
|
|
reply("Unknown Type " + (d as any).type);
|
2024-08-21 01:41:47 -04:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-01-09 02:58:32 -05:00
|
|
|
|
|
|
|
|
interface AllModulesNotiProps {
|
|
|
|
|
done: Promise<unknown>;
|
|
|
|
|
close: () => void;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const AllModulesNoti = ErrorBoundary.wrap(function ({ done, close }: AllModulesNotiProps) {
|
|
|
|
|
const [state, setState] = useState<0 | 1 | -1>(0);
|
|
|
|
|
done.then(setState.bind(null, 1)).catch(setState.bind(null, -1));
|
|
|
|
|
console.log("test");
|
|
|
|
|
if (state === 1) setTimeout(close, 5000);
|
|
|
|
|
return (<>
|
|
|
|
|
{state === 0 && "Loading lazy modules, restarting could lead to errors"}
|
|
|
|
|
{state === 1 && "Loaded all lazy modules"}
|
|
|
|
|
{state === -1 && "Failed to load lazy modules, check console for errors"}
|
|
|
|
|
</>);
|
|
|
|
|
}, { noop: true });
|