From f4f817eaedfecf6124819d3d0b300c53a6322311 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Sat, 8 Feb 2025 14:59:04 -0500 Subject: [PATCH] Fix AllCallTimers + Bring Back WebpackTarball --- README.md | 3 +- src/equicordplugins/allCallTimers/index.tsx | 16 +- src/equicordplugins/webpackTarball/index.tsx | 161 ++++++++++++++++++ src/equicordplugins/webpackTarball/tar.ts | 74 ++++++++ src/equicordplugins/webpackTarball/webpack.ts | 66 +++++++ 5 files changed, 315 insertions(+), 5 deletions(-) create mode 100644 src/equicordplugins/webpackTarball/index.tsx create mode 100644 src/equicordplugins/webpackTarball/tar.ts create mode 100644 src/equicordplugins/webpackTarball/webpack.ts diff --git a/README.md b/README.md index f9d0ea10..da3cefc9 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch ### Extra included plugins
-150 additional plugins +151 additional plugins ### All Platforms - AllCallTimers by MaxHerbold & D3SOX @@ -151,6 +151,7 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch - ViewRawVariant (ViewRaw2) by Kyuuhachi - VoiceChatUtilities by D3SOX - VoiceJoinMessages by Sqaaakoi & maintained by thororen +- WebpackTarball by Kyuuhachi - WhitelistedEmojis by Creations - WhosWatching by fres - WigglyText by nexpid diff --git a/src/equicordplugins/allCallTimers/index.tsx b/src/equicordplugins/allCallTimers/index.tsx index 8ef7cec2..0127a48b 100644 --- a/src/equicordplugins/allCallTimers/index.tsx +++ b/src/equicordplugins/allCallTimers/index.tsx @@ -17,7 +17,7 @@ export const settings = definePluginSettings({ showWithoutHover: { type: OptionType.BOOLEAN, description: "Always show the timer without needing to hover", - restartNeeded: false, + restartNeeded: true, default: true }, showRoleColor: { @@ -106,6 +106,17 @@ export default definePlugin({ patches: [ { find: ".usernameSpeaking]", + predicate: () => !settings.store.showWithoutHover, + replacement: [ + { + match: /(?<=user:(\i).*?)iconGroup,children:\[/, + replace: "$&$self.renderTimer($1.id)," + }, + ] + }, + { + find: ".usernameSpeaking]", + predicate: () => settings.store.showWithoutHover, replacement: [ { match: /function\(\)\{.+:""(?=.*?userId:(\i))/, @@ -217,9 +228,6 @@ export default definePlugin({ }, renderTimer(userId: string) { - if (!settings.store.showWithoutHover) { - return ""; - } // get the user join time from the users object const joinTime = userJoinTimes.get(userId); if (!joinTime?.time) { diff --git a/src/equicordplugins/webpackTarball/index.tsx b/src/equicordplugins/webpackTarball/index.tsx new file mode 100644 index 00000000..852c2eff --- /dev/null +++ b/src/equicordplugins/webpackTarball/index.tsx @@ -0,0 +1,161 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { definePluginSettings } from "@api/Settings"; +import { Devs } from "@utils/constants"; +import { makeLazy } from "@utils/lazy"; +import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModal } from "@utils/modal"; +import definePlugin, { OptionType } from "@utils/types"; +import { findByProps, wreq } from "@webpack"; +import { Button, Flex, Forms, Switch, Text, Timestamp, useState } from "@webpack/common"; +import __webpack_require__ from "discord-types/other/WebpackInstance"; + +import TarFile from "./tar"; +import * as Webpack from "./webpack"; + +export const settings = definePluginSettings({ + patched: { + type: OptionType.BOOLEAN, + default: true, + description: "Include patched modules", + restartNeeded: true, + }, +}); + +export default definePlugin({ + name: "WebpackTarball", + description: "Converts Discord's webpack sources into a tarball.", + authors: [Devs.Kyuuhachi], + settings, + + toolboxActions: { + "Webpack Tarball"() { + const key = openModal(props => ( + closeModal(key)} + /> + )); + } + }, +}); + +export const getBuildNumber = makeLazy(() => { + try { + const metrics = findByProps("_getMetricWithDefaults")._flush.toString(); + const [, builtAt, buildNumber] = metrics.match(/\{built_at:"(\d+)",build_number:"(\d+)"\}/); + return { buildNumber, builtAt: new Date(Number(builtAt)) }; + } catch (e) { + console.error("failed to get build number:", e); + return { buildNumber: "unknown", builtAt: new Date() }; + } +}); + +async function saveTar(patched: boolean) { + const tar = new TarFile(); + const { buildNumber, builtAt } = getBuildNumber(); + const mtime = (builtAt.getTime() / 1000) | 0; + + const root = patched ? `equicord-${buildNumber}` : `discord-${buildNumber}`; + + for (const [id, module] of Object.entries(wreq.m)) { + const patchedSrc = Function.toString.call(module); + const originalSrc = module.toString(); + if (patched && patchedSrc !== originalSrc) + tar.addTextFile( + `${root}/${id}.v.js`, + `webpack[${JSON.stringify(id)}] = ${patchedSrc}\n`, + { mtime }, + ); + tar.addTextFile( + `${root}/${id}.js`, + `webpack[${JSON.stringify(id)}] = ${originalSrc}\n`, + { mtime }, + ); + } + tar.save(`${root}.tar`); +} + +function TarModal({ modalProps, close }: { modalProps: ModalProps; close(): void; }) { + const webpackRequire = wreq as unknown as __webpack_require__; + const { buildNumber, builtAt } = getBuildNumber(); + const [, rerender] = useState({}); + const [isLoading, setLoading] = useState(false); + const paths = Webpack.getChunkPaths(webpackRequire); + const status = Object.entries(Webpack.getLoadedChunks(webpackRequire)) + .filter(([k]) => webpackRequire.o(paths, k)) + .map(([, v]) => v); + const loading = status.length; + const loaded = status.filter(v => v === 0 || v === undefined).length; + const errored = status.filter(v => v === undefined).length; + const all = Object.keys(paths).length; + const { patched } = settings.use(["patched"]); + const WEBPACK_CHUNK = "webpackChunkdiscord_app"; + return ( + + + + Webpack Tarball + + + + {"Build number "} + {buildNumber} + + + + + + +
+ + Lazy chunks + + + + {loaded}/{all} + {errored ? ` (${errored} errors)` : null} + + + +
+ + settings.store.patched = v} + hideBorder + > + {settings.def.patched.description} + +
+ + + + +
+ ); +} diff --git a/src/equicordplugins/webpackTarball/tar.ts b/src/equicordplugins/webpackTarball/tar.ts new file mode 100644 index 00000000..d23ec2a7 --- /dev/null +++ b/src/equicordplugins/webpackTarball/tar.ts @@ -0,0 +1,74 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +export type Metadata = { mtime?: number; }; +export default class TarFile { + buffers: ArrayBuffer[]; + + constructor() { + this.buffers = []; + } + + addTextFile(name: string, text: string, metadata: Metadata = {}) { + this.addFile(name, new TextEncoder().encode(text), metadata); + } + + addFile(name: string, data: Uint8Array, { mtime = 0 }: Metadata = {}) { + this.buffers.push(this.header([ + [100, name.toString()], // name + [8, 0o644], // mode + [8, 0o1000], // uid + [8, 0o1000], // gid + [12, data.length], // size + [12, mtime], // mtime + [8, null], // checksum + [1, "0"], // type + [100, ""], // name of linked file (??) + [255, ""], // padding + ])); + this.buffers.push(data.buffer as ArrayBuffer); + this.buffers.push(new ArrayBuffer(-data.length & 0x1FF)); + } + + header(fields: [number, number | string | null][]) { + const buffer = new ArrayBuffer(512); + const u1 = new Uint8Array(buffer); + let checksum = 0; + let checksumPos: number = null!; + + let pos = 0; + for (const [size, val] of fields) { + let string: string; + if (val === null) { + checksumPos = pos; + string = " ".repeat(size); + } else if (typeof val === "string") { + string = val; + } else if (typeof val === "number") { + string = val.toString(8).padStart(size - 1, "0"); + } else { + throw new Error("invalid value", val); + } + if (string.length > size) throw new Error(`${string} is longer than ${size} characters`); + Array.from(string).forEach((c, i) => checksum += u1[pos + i] = c.charCodeAt(0)); + pos += size; + } + Array.from("\0".repeat(8)).forEach((c, i) => u1[checksumPos + i] = c.charCodeAt(0)); + Array.from(checksum.toString(8).padStart(7, "0")).forEach((c, i) => u1[checksumPos + i] = c.charCodeAt(0)); + return buffer; + } + + save(filename: string) { + const a = document.createElement("a"); + a.href = URL.createObjectURL(new Blob(this.buffers, { "type": "application/x-tar" })); + a.download = filename; + a.style.display = "none"; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(a.href); + } +} diff --git a/src/equicordplugins/webpackTarball/webpack.ts b/src/equicordplugins/webpackTarball/webpack.ts new file mode 100644 index 00000000..da0bf4b6 --- /dev/null +++ b/src/equicordplugins/webpackTarball/webpack.ts @@ -0,0 +1,66 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import type { WebpackInstance } from "discord-types/other"; + +export async function protectWebpack(webpack: any[], body: () => Promise): Promise { + const prev_m = Object.getOwnPropertyDescriptor(Function.prototype, "m")!; + Object.defineProperty(Function.prototype, "m", { + get() { throw "get require.m"; }, + set() { throw "set require.m"; }, + enumerable: true, + configurable: true, + }); + + try { + return await body(); + } finally { + Object.defineProperty(Function.prototype, "m", prev_m); + } +} + +export function getLoadedChunks(wreq: WebpackInstance): { [chunkId: string | symbol]: 0 | undefined; } { + const { o } = wreq; + try { + wreq.o = (a: any) => { throw a; }; + wreq.f.j(); + } catch (e: any) { + return e; + } finally { + wreq.o = o; + } + throw new Error("getLoadedChunks failed"); +} + +export function getChunkPaths(wreq: WebpackInstance): { [chunkId: string]: string; } { + const sym = Symbol("getChunkPaths"); + try { + Object.defineProperty(Object.prototype, sym, { + get() { throw this; }, + set() { }, + configurable: true, + }); + wreq.u(sym); + } catch (e: any) { + return e; + } finally { + // @ts-ignore + delete Object.prototype[sym]; + } + throw new Error("getChunkPaths failed"); +} + +export async function forceLoadAll(wreq: WebpackInstance, on_chunk: (id: string) => void = () => { }) { + const chunks = getChunkPaths(wreq); + const loaded = getLoadedChunks(wreq); + const ids = Object.keys(chunks).filter(id => loaded[id] !== 0); + await Promise.all(ids.map(async id => { + try { + await wreq.e(id as any); + } catch { } + on_chunk(id); + })); +}