mirror of
https://github.com/Equicord/Equicord.git
synced 2025-06-09 14:43:03 -04:00
Fix AllCallTimers + Bring Back WebpackTarball
This commit is contained in:
parent
cb6a8502fd
commit
f4f817eaed
5 changed files with 315 additions and 5 deletions
|
@ -10,7 +10,7 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch
|
||||||
|
|
||||||
### Extra included plugins
|
### Extra included plugins
|
||||||
<details>
|
<details>
|
||||||
<summary>150 additional plugins</summary>
|
<summary>151 additional plugins</summary>
|
||||||
|
|
||||||
### All Platforms
|
### All Platforms
|
||||||
- AllCallTimers by MaxHerbold & D3SOX
|
- 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
|
- ViewRawVariant (ViewRaw2) by Kyuuhachi
|
||||||
- VoiceChatUtilities by D3SOX
|
- VoiceChatUtilities by D3SOX
|
||||||
- VoiceJoinMessages by Sqaaakoi & maintained by thororen
|
- VoiceJoinMessages by Sqaaakoi & maintained by thororen
|
||||||
|
- WebpackTarball by Kyuuhachi
|
||||||
- WhitelistedEmojis by Creations
|
- WhitelistedEmojis by Creations
|
||||||
- WhosWatching by fres
|
- WhosWatching by fres
|
||||||
- WigglyText by nexpid
|
- WigglyText by nexpid
|
||||||
|
|
|
@ -17,7 +17,7 @@ export const settings = definePluginSettings({
|
||||||
showWithoutHover: {
|
showWithoutHover: {
|
||||||
type: OptionType.BOOLEAN,
|
type: OptionType.BOOLEAN,
|
||||||
description: "Always show the timer without needing to hover",
|
description: "Always show the timer without needing to hover",
|
||||||
restartNeeded: false,
|
restartNeeded: true,
|
||||||
default: true
|
default: true
|
||||||
},
|
},
|
||||||
showRoleColor: {
|
showRoleColor: {
|
||||||
|
@ -106,6 +106,17 @@ export default definePlugin({
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: ".usernameSpeaking]",
|
find: ".usernameSpeaking]",
|
||||||
|
predicate: () => !settings.store.showWithoutHover,
|
||||||
|
replacement: [
|
||||||
|
{
|
||||||
|
match: /(?<=user:(\i).*?)iconGroup,children:\[/,
|
||||||
|
replace: "$&$self.renderTimer($1.id),"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: ".usernameSpeaking]",
|
||||||
|
predicate: () => settings.store.showWithoutHover,
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /function\(\)\{.+:""(?=.*?userId:(\i))/,
|
match: /function\(\)\{.+:""(?=.*?userId:(\i))/,
|
||||||
|
@ -217,9 +228,6 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
|
|
||||||
renderTimer(userId: string) {
|
renderTimer(userId: string) {
|
||||||
if (!settings.store.showWithoutHover) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
// get the user join time from the users object
|
// get the user join time from the users object
|
||||||
const joinTime = userJoinTimes.get(userId);
|
const joinTime = userJoinTimes.get(userId);
|
||||||
if (!joinTime?.time) {
|
if (!joinTime?.time) {
|
||||||
|
|
161
src/equicordplugins/webpackTarball/index.tsx
Normal file
161
src/equicordplugins/webpackTarball/index.tsx
Normal file
|
@ -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 => (
|
||||||
|
<TarModal
|
||||||
|
modalProps={props}
|
||||||
|
close={() => 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 (
|
||||||
|
<ModalRoot {...modalProps}>
|
||||||
|
<ModalHeader>
|
||||||
|
<Forms.FormTitle tag="h2">
|
||||||
|
Webpack Tarball
|
||||||
|
</Forms.FormTitle>
|
||||||
|
<Text variant="text-md/normal">
|
||||||
|
<Timestamp timestamp={new Date(builtAt)} isInline={false}>
|
||||||
|
{"Build number "}
|
||||||
|
{buildNumber}
|
||||||
|
</Timestamp>
|
||||||
|
</Text>
|
||||||
|
<ModalCloseButton onClick={close} />
|
||||||
|
</ModalHeader>
|
||||||
|
|
||||||
|
<ModalContent>
|
||||||
|
<div style={{ marginTop: "8px", marginBottom: "24px" }}>
|
||||||
|
<Forms.FormTitle>
|
||||||
|
Lazy chunks
|
||||||
|
</Forms.FormTitle>
|
||||||
|
<Flex align={Flex.Align.CENTER}>
|
||||||
|
<Text
|
||||||
|
variant="text-md/normal"
|
||||||
|
style={{ flexGrow: 1 }}
|
||||||
|
>
|
||||||
|
{loaded}/{all}
|
||||||
|
{errored ? ` (${errored} errors)` : null}
|
||||||
|
</Text>
|
||||||
|
<Button
|
||||||
|
disabled={loading === all || isLoading}
|
||||||
|
onClick={async () => {
|
||||||
|
setLoading(true);
|
||||||
|
// @ts-ignore
|
||||||
|
await Webpack.protectWebpack(window[WEBPACK_CHUNK], async () => {
|
||||||
|
await Webpack.forceLoadAll(webpackRequire, rerender);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{loaded === all ? "Loaded" : loading === all ? "Loading" : "Load all"}
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Switch
|
||||||
|
value={patched}
|
||||||
|
onChange={v => settings.store.patched = v}
|
||||||
|
hideBorder
|
||||||
|
>
|
||||||
|
{settings.def.patched.description}
|
||||||
|
</Switch>
|
||||||
|
</ModalContent>
|
||||||
|
|
||||||
|
<ModalFooter>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
saveTar(patched);
|
||||||
|
close();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Create
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalRoot>
|
||||||
|
);
|
||||||
|
}
|
74
src/equicordplugins/webpackTarball/tar.ts
Normal file
74
src/equicordplugins/webpackTarball/tar.ts
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
66
src/equicordplugins/webpackTarball/webpack.ts
Normal file
66
src/equicordplugins/webpackTarball/webpack.ts
Normal file
|
@ -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<T>(webpack: any[], body: () => Promise<T>): Promise<T> {
|
||||||
|
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);
|
||||||
|
}));
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue