Merge remote-tracking branch 'upstream/dev' into dev

This commit is contained in:
thororen1234 2025-02-08 21:14:05 -05:00
commit d0c47f2c0e
5 changed files with 153 additions and 99 deletions

View file

@ -1,9 +1,18 @@
name: Test Patches name: Test Patches
on: on:
workflow_dispatch: workflow_dispatch:
schedule: inputs:
# Every day at midnight discord_branch:
- cron: 0 0 * * * type: choice
description: "Discord Branch to test patches on"
options:
- both
- stable
- canary
default: both
# schedule:
# # Every day at midnight
# - cron: 0 0 * * *
jobs: jobs:
TestPlugins: TestPlugins:
@ -40,28 +49,43 @@ jobs:
- name: Build Equicord Reporter Version - name: Build Equicord Reporter Version
run: pnpm buildReporter run: pnpm buildReporter
- name: Create Report - name: Run Reporter
timeout-minutes: 10 timeout-minutes: 10
run: | run: |
export PATH="$PWD/node_modules/.bin:$PATH" export PATH="$PWD/node_modules/.bin:$PATH"
export CHROMIUM_BIN=${{ steps.setup-chrome.outputs.chrome-path }} export CHROMIUM_BIN=${{ steps.setup-chrome.outputs.chrome-path }}
esbuild scripts/generateReport.ts > dist/report.mjs esbuild scripts/generateReport.ts > dist/report.mjs
node dist/report.mjs >> $GITHUB_STEP_SUMMARY
env:
DISCORD_TOKEN: ${{ secrets.DTOKEN }}
DISCORD_WEBHOOK: ${{ secrets.WEBHOOK }}
- name: Create Report (Canary) stable_output_file=$(mktemp)
timeout-minutes: 10 canary_output_file=$(mktemp)
if: success() || failure() # even run if previous one failed
run: |
export PATH="$PWD/node_modules/.bin:$PATH"
export CHROMIUM_BIN=${{ steps.setup-chrome.outputs.chrome-path }}
export USE_CANARY=true
esbuild scripts/generateReport.ts > dist/report.mjs pids=""
node dist/report.mjs >> $GITHUB_STEP_SUMMARY
branch="${{ inputs.discord_branch }}"
if [[ "${{ github.event_name }}" = "schedule" ]]; then
branch="both"
fi
if [[ "$branch" = "both" || "$branch" = "stable" ]]; then
node dist/report.mjs > "$stable_output_file" &
pids+=" $!"
fi
if [[ "$branch" = "both" || "$branch" = "canary" ]]; then
USE_CANARY=true node dist/report.mjs > "$canary_output_file" &
pids+=" $!"
fi
exit_code=0
for pid in $pids; do
if ! wait "$pid"; then
exit_code=1
fi
done
cat "$stable_output_file" "$canary_output_file" >> $GITHUB_STEP_SUMMARY
exit $exit_code
env: env:
DISCORD_TOKEN: ${{ secrets.DTOKEN }} WEBHOOK_URL: ${{ secrets.WEBHOOK }}
DISCORD_WEBHOOK: ${{ secrets.WEBHOOK }} WEBHOOK_SECRET: ${{ secrets.WEBHOOK_SECRET }}

View file

@ -23,17 +23,24 @@
// eslint-disable-next-line spaced-comment // eslint-disable-next-line spaced-comment
/// <reference types="../src/modules" /> /// <reference types="../src/modules" />
import { createHmac } from "crypto";
import { readFileSync } from "fs"; import { readFileSync } from "fs";
import pup, { JSHandle } from "puppeteer-core"; import pup, { JSHandle } from "puppeteer-core";
for (const variable of ["DISCORD_TOKEN", "CHROMIUM_BIN"]) { const logStderr = (...data: any[]) => console.error(`${CANARY ? "CANARY" : "STABLE"} ---`, ...data);
for (const variable of ["CHROMIUM_BIN"]) {
if (!process.env[variable]) { if (!process.env[variable]) {
console.error(`Missing environment variable ${variable}`); logStderr(`Missing environment variable ${variable}`);
process.exit(1); process.exit(1);
} }
} }
const CANARY = process.env.USE_CANARY === "true"; const CANARY = process.env.USE_CANARY === "true";
let metaData = {
buildNumber: "Unknown Build Number",
buildHash: "Unknown Build Hash"
};
const browser = await pup.launch({ const browser = await pup.launch({
headless: true, headless: true,
@ -128,16 +135,18 @@ async function printReport() {
console.log(); console.log();
if (process.env.DISCORD_WEBHOOK) { if (process.env.WEBHOOK_URL) {
await fetch(process.env.DISCORD_WEBHOOK, { const body = JSON.stringify({
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
description: "Here's the latest Equicord Report!",
username: "Equicord Reporter" + (CANARY ? " (Canary)" : ""), username: "Equicord Reporter" + (CANARY ? " (Canary)" : ""),
embeds: [ embeds: [
{
author: {
name: `Discord ${CANARY ? "Canary" : "Stable"} (${metaData.buildNumber})`,
url: `https://nelly.tools/builds/app/${metaData.buildHash}`,
icon_url: CANARY ? "https://cdn.discordapp.com/emojis/1252721945699549327.png?size=128" : "https://cdn.discordapp.com/emojis/1252721943463985272.png?size=128"
},
color: CANARY ? 0xfbb642 : 0x5865f2
},
{ {
title: "Bad Patches", title: "Bad Patches",
description: report.badPatches.map(p => { description: report.badPatches.map(p => {
@ -174,10 +183,26 @@ async function printReport() {
color: report.otherErrors.length ? 0xff0000 : 0x00ff00 color: report.otherErrors.length ? 0xff0000 : 0x00ff00
} }
] ]
}) });
const headers = {
"Content-Type": "application/json"
};
// functions similar to https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries
// used by venbot to ensure webhook invocations are genuine (since we will pass the webhook url as a workflow input which is publicly visible)
// generate a secret with something like `openssl rand -hex 128`
if (process.env.WEBHOOK_SECRET) {
headers["X-Signature"] = "sha256=" + createHmac("sha256", process.env.WEBHOOK_SECRET).update(body).digest("hex");
}
await fetch(process.env.WEBHOOK_URL, {
method: "POST",
headers,
body
}).then(res => { }).then(res => {
if (!res.ok) console.error(`Webhook failed with status ${res.status}`); if (!res.ok) logStderr(`Webhook failed with status ${res.status}`);
else console.error("Posted to Discord Webhook successfully"); else logStderr("Posted to Webhook successfully");
}); });
} }
} }
@ -186,10 +211,13 @@ page.on("console", async e => {
const level = e.type(); const level = e.type();
const rawArgs = e.args(); const rawArgs = e.args();
async function getText() { async function getText(skipFirst = true) {
let args = e.args();
if (skipFirst) args = args.slice(1);
try { try {
return await Promise.all( return await Promise.all(
e.args().map(async a => { args.map(async a => {
return await maybeGetError(a) || await a.jsonValue(); return await maybeGetError(a) || await a.jsonValue();
}) })
).then(a => a.join(" ").trim()); ).then(a => a.join(" ").trim());
@ -202,6 +230,12 @@ page.on("console", async e => {
const isEquicord = firstArg === "[Equicord]"; const isEquicord = firstArg === "[Equicord]";
const isDebug = firstArg === "[PUP_DEBUG]"; const isDebug = firstArg === "[PUP_DEBUG]";
const isReporterMeta = firstArg === "[REPORTER_META]";
if (isReporterMeta) {
metaData = await rawArgs[1].jsonValue() as any;
return;
}
outer: outer:
if (isEquicord) { if (isEquicord) {
@ -218,7 +252,7 @@ page.on("console", async e => {
const patchFailMatch = message.match(/Patch by (.+?) (had no effect|errored|found no module|took [\d.]+?ms) \(Module id is (.+?)\): (.+)/)!; const patchFailMatch = message.match(/Patch by (.+?) (had no effect|errored|found no module|took [\d.]+?ms) \(Module id is (.+?)\): (.+)/)!;
if (!patchFailMatch) break; if (!patchFailMatch) break;
console.error(await getText()); logStderr(await getText());
process.exitCode = 1; process.exitCode = 1;
const [, plugin, type, id, regex] = patchFailMatch; const [, plugin, type, id, regex] = patchFailMatch;
@ -235,7 +269,7 @@ page.on("console", async e => {
const failedToStartMatch = message.match(/Failed to start (.+)/); const failedToStartMatch = message.match(/Failed to start (.+)/);
if (!failedToStartMatch) break; if (!failedToStartMatch) break;
console.error(await getText()); logStderr(await getText());
process.exitCode = 1; process.exitCode = 1;
const [, name] = failedToStartMatch; const [, name] = failedToStartMatch;
@ -246,7 +280,7 @@ page.on("console", async e => {
break; break;
case "LazyChunkLoader:": case "LazyChunkLoader:":
console.error(await getText()); logStderr(await getText());
switch (message) { switch (message) {
case "A fatal error occurred:": case "A fatal error occurred:":
@ -255,7 +289,7 @@ page.on("console", async e => {
break; break;
case "Reporter:": case "Reporter:":
console.error(await getText()); logStderr(await getText());
switch (message) { switch (message) {
case "A fatal error occurred:": case "A fatal error occurred:":
@ -275,51 +309,38 @@ page.on("console", async e => {
} }
if (isDebug) { if (isDebug) {
console.error(await getText()); logStderr(await getText());
} else if (level === "error") { } else if (level === "error") {
const text = await getText(); const text = await getText(false);
if (text.length && !text.startsWith("Failed to load resource: the server responded with a status of") && !text.includes("Webpack")) { if (text.length && !text.startsWith("Failed to load resource: the server responded with a status of") && !text.includes("Webpack")) {
if (IGNORED_DISCORD_ERRORS.some(regex => text.match(regex))) { if (IGNORED_DISCORD_ERRORS.some(regex => text.match(regex))) {
report.ignoredErrors.push(text); report.ignoredErrors.push(text);
} else { } else {
console.error("[Unexpected Error]", text); logStderr("[Unexpected Error]", text);
report.otherErrors.push(text); report.otherErrors.push(text);
} }
} }
} }
}); });
page.on("error", e => { page.on("error", e => logStderr("[Error]", e.message));
console.error("[Error]", e.message);
});
page.on("pageerror", e => { page.on("pageerror", e => {
if (e.message.includes("Sentry successfully disabled")) return; if (e.message.includes("Sentry successfully disabled")) return;
if (e.message.includes("the network is offline")) return; if (e.message.includes("the network is offline")) return;
if (e.message.includes("Cannot read properties of undefined (reading 'includes')")) return; if (e.message.includes("Cannot read properties of undefined (reading 'includes')")) return;
if (!e.message.startsWith("Object") && !e.message.includes("Cannot find module") && !/^.{1,2}$/.test(e.message)) { if (!e.message.startsWith("Object") && !e.message.includes("Cannot find module") && !/^.{1,2}$/.test(e.message)) {
console.error("[Page Error]", e.message); logStderr("[Page Error]", e.message);
report.otherErrors.push(e.message); report.otherErrors.push(e.message);
} else { } else {
report.ignoredErrors.push(e.message); report.ignoredErrors.push(e.message);
} }
}); });
async function reporterRuntime(token: string) {
Vencord.Webpack.waitFor(
"loginToken",
m => {
console.log("[PUP_DEBUG]", "Logging in with token...");
m.loginToken(token);
}
);
}
await page.evaluateOnNewDocument(` await page.evaluateOnNewDocument(`
if (location.host.endsWith("discord.com")) { if (location.host.endsWith("discord.com")) {
${readFileSync("./dist/browser/browser.js", "utf-8")}; ${readFileSync("./dist/browser.js", "utf-8")};
(${reporterRuntime.toString()})(${JSON.stringify(process.env.DISCORD_TOKEN)});
} }
`); `);

View file

@ -8,6 +8,7 @@ import { Logger } from "@utils/Logger";
import * as Webpack from "@webpack"; import * as Webpack from "@webpack";
import { addPatch, patches } from "plugins"; import { addPatch, patches } from "plugins";
import { initWs } from "plugins/devCompanion.dev/initWs"; import { initWs } from "plugins/devCompanion.dev/initWs";
import { getBuildNumber } from "webpack/patchWebpack";
import { loadLazyChunks } from "./loadLazyChunks"; import { loadLazyChunks } from "./loadLazyChunks";
import { reporterData } from "./reporterData"; import { reporterData } from "./reporterData";
@ -39,6 +40,13 @@ async function runReporter() {
await loadLazyChunksDone; await loadLazyChunksDone;
if (IS_REPORTER && IS_WEB && !IS_VESKTOP) {
console.log("[REPORTER_META]", {
buildNumber: getBuildNumber(),
buildHash: window.GLOBAL_ENV.SENTRY_TAGS.buildId
});
}
for (const patch of patches) { for (const patch of patches) {
if (!patch.all) { if (!patch.all) {
new Logger("WebpackInterceptor").warn(`Patch by ${patch.plugin} found no module (Module id is -): ${patch.find}`); new Logger("WebpackInterceptor").warn(`Patch by ${patch.plugin} found no module (Module id is -): ${patch.find}`);
@ -48,7 +56,7 @@ async function runReporter() {
} }
for (const [plugin, moduleId, match, totalTime] of Vencord.WebpackPatcher.patchTimings) { for (const [plugin, moduleId, match, totalTime] of Vencord.WebpackPatcher.patchTimings) {
if (totalTime > 3) { if (totalTime > 5) {
new Logger("WebpackInterceptor").warn(`Patch by ${plugin} took ${Math.round(totalTime * 100) / 100}ms (Module id is ${String(moduleId)}): ${match}`); new Logger("WebpackInterceptor").warn(`Patch by ${plugin} took ${Math.round(totalTime * 100) / 100}ms (Module id is ${String(moduleId)}): ${match}`);
} }
} }

View file

@ -32,7 +32,7 @@ export class Logger {
constructor(public name: string, public color: string = "white") { } constructor(public name: string, public color: string = "white") { }
private _log(level: "log" | "error" | "warn" | "info" | "debug", levelColor: string, args: any[], customFmt = "") { private _log(level: "log" | "error" | "warn" | "info" | "debug", levelColor: string, args: any[], customFmt = "") {
if (IS_REPORTER && IS_WEB) { if (IS_REPORTER && IS_WEB && !IS_VESKTOP) {
console[level]("[Equicord]", this.name + ":", ...args); console[level]("[Equicord]", this.name + ":", ...args);
return; return;
} }

View file

@ -428,11 +428,12 @@ function patchFactory(id: PropertyKey, factory: AnyModuleFactory): [patchedFacto
continue; continue;
} }
const buildNumber = getBuildNumber(); // Reporter eagerly patches and cannot retrieve the build number because this code runs before the module for it is loaded
const shouldCheckBuildId = !Settings.eagerPatches && buildNumber !== -1; const buildNumber = IS_REPORTER ? -1 : getBuildNumber();
const shouldCheckBuildNumber = !Settings.eagerPatches && buildNumber !== -1;
if ( if (
shouldCheckBuildId && shouldCheckBuildNumber &&
(patch.fromBuild != null && buildNumber < patch.fromBuild) || (patch.fromBuild != null && buildNumber < patch.fromBuild) ||
(patch.toBuild != null && buildNumber > patch.toBuild) (patch.toBuild != null && buildNumber > patch.toBuild)
) { ) {
@ -454,7 +455,7 @@ function patchFactory(id: PropertyKey, factory: AnyModuleFactory): [patchedFacto
// We change all patch.replacement to array in plugins/index // We change all patch.replacement to array in plugins/index
for (const replacement of patch.replacement as PatchReplacement[]) { for (const replacement of patch.replacement as PatchReplacement[]) {
if ( if (
shouldCheckBuildId && shouldCheckBuildNumber &&
(replacement.fromBuild != null && buildNumber < replacement.fromBuild) || (replacement.fromBuild != null && buildNumber < replacement.fromBuild) ||
(replacement.toBuild != null && buildNumber > replacement.toBuild) (replacement.toBuild != null && buildNumber > replacement.toBuild)
) { ) {