Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix for Discord re-enabling mangled exports #631

Merged
merged 8 commits into from
Jun 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "replugged",
"version": "4.7.13",
"version": "4.8.0",
"description": "A lightweight @discord client mod focused on simplicity and performance",
"license": "MIT",
"main": "dist/main.mjs",
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/apis/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ interface CommandsAndSection {
commands: Map<string, AnyRepluggedCommand>;
}

void waitForModule<typeof User>(filters.bySource(".isStaffPersonal=")).then((User) => {
void waitForModule<typeof User>(filters.bySource("hasHadPremium(){")).then((User) => {
RepluggedUser = new User({
avatar: "replugged",
id: "replugged",
Expand Down
19 changes: 14 additions & 5 deletions src/renderer/coremods/badges/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Messages } from "@common/i18n";
import React from "@common/react";
import { Logger } from "@replugged";
import { filters, waitForModule, waitForProps } from "@webpack";
import { filters, getFunctionKeyBySource, waitForModule } from "@webpack";
import { DISCORD_BLURPLE, DISCORD_INVITE, WEBLATE_URL } from "src/constants";
import type { Badge, DisplayProfile } from "src/types";
import { Injector } from "../../modules/injector";
Expand Down Expand Up @@ -103,8 +103,9 @@ const badgeElements = [

export async function start(): Promise<void> {
const useBadgesMod = await waitForModule<UseBadgesMod>(filters.bySource(/:\w+\.getBadges\(\)/));
const defaultKey = getFunctionKeyBySource(useBadgesMod, "") as "default"; // It's actually Z, but shut up TS, nobody loves you

injector.after(useBadgesMod, "default", ([displayProfile], badges) => {
injector.after(useBadgesMod, defaultKey, ([displayProfile], badges) => {
if (!generalSettings.get("badges")) return badges;

try {
Expand Down Expand Up @@ -178,9 +179,17 @@ export async function start(): Promise<void> {
}
});

const userProfileConstantsMod = await waitForProps<UserProfileConstantsMod>("getBadgeAsset");

injector.instead(userProfileConstantsMod, "getBadgeAsset", (args, orig) => {
// TODO: patch badge components with our SVGs to make it actually fill in the badges.
//const userProfileConstantsMod = await waitForProps<UserProfileConstantsMod>("getBadgeAsset");
const userProfileConstantsMod = await waitForModule<UserProfileConstantsMod>(
filters.bySource('concat(n,"/badge-icons/"'),
);
const getBadgeAssetKey = getFunctionKeyBySource(
userProfileConstantsMod,
"badge-icons",
) as "getBadgeAsset"; // I hate this hack by now

injector.instead(userProfileConstantsMod, getBadgeAssetKey, (args, orig) => {
if (args[0].startsWith("replugged")) return args[0].replace("replugged", "");
return orig(...args);
});
Expand Down
39 changes: 31 additions & 8 deletions src/renderer/coremods/commands/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import type { AnyRepluggedCommand, RepluggedCommandSection } from "../../../types";
import type { Channel, Guild } from "discord-types/general";
import type { Store } from "@common/flux";
import flux, { type Store } from "@common/flux";
import { Injector } from "../../modules/injector";
import { Logger } from "../../modules/logger";
import { waitForProps } from "../../modules/webpack";
import {
filters,
getFunctionKeyBySource,
waitForModule,
waitForProps,
} from "../../modules/webpack";

import { commandAndSections, defaultSection } from "../../apis/commands";
import { loadCommands, unloadCommands } from "./commands";
Expand Down Expand Up @@ -112,18 +117,29 @@ async function injectRepluggedSectionIcon(): Promise<void> {

async function injectApplicationCommandIndexStore(): Promise<void> {
// The module which contains the store
/*
const ApplicationCommandIndexStoreMod = await waitForProps<ApplicationCommandIndexStoreMod>(
"useContextIndexState",
"useDiscoveryState",
"useGuildIndexState",
"useUserIndexState",
);
*/
const ApplicationCommandIndexStoreMod = await waitForModule<ApplicationCommandIndexStoreMod>(
filters.bySource("APPLICATION_COMMAND_INDEX"),
);

// This "as" hack is horrible.
const useDiscoveryStateKey = getFunctionKeyBySource(
ApplicationCommandIndexStoreMod,
"includeFrecency",
) as "useDiscoveryState";

// Base handler function for ApplicationCommandIndexStore which is ran to get the info in store
// commands are mainly added here
injector.after(
ApplicationCommandIndexStoreMod,
"useDiscoveryState",
useDiscoveryStateKey,
([, , { commandType }], res) => {
const commandAndSectionsArray = Array.from(commandAndSections.values()).filter(
(commandAndSection) => commandAndSection.commands.size,
Expand Down Expand Up @@ -196,7 +212,10 @@ async function injectApplicationCommandIndexStore(): Promise<void> {
);

// The store itself
const ApplicationCommandIndexStore = ApplicationCommandIndexStoreMod.default;
//const ApplicationCommandIndexStore = ApplicationCommandIndexStoreMod.default;
const ApplicationCommandIndexStore = Object.values(ApplicationCommandIndexStoreMod).find(
(v) => v instanceof flux.Store,
) as ApplicationCommandIndexStore;

// Slash command indexing patched to return our slash commands too
// only those which match tho
Expand Down Expand Up @@ -266,10 +285,14 @@ async function injectApplicationCommandIndexStore(): Promise<void> {
}

async function injectProfileFetch(): Promise<void> {
const mod = await waitForProps<{
fetchProfile: (id: string) => Promise<void>;
}>("fetchProfile");
injector.instead(mod, "fetchProfile", (args, res) => {
//const mod = await waitForProps<{
// fetchProfile: (id: string) => Promise<void>;
//}>("fetchProfile");
const mod = await waitForModule<Record<string, (id: string) => Promise<void>>>(
filters.bySource("fetchProfile"),
);
const fetchProfileKey = getFunctionKeyBySource(mod, "fetchProfile")!;
injector.instead(mod, fetchProfileKey, (args, res) => {
if (args[0] === "replugged") {
return;
}
Expand Down
11 changes: 3 additions & 8 deletions src/renderer/coremods/contextMenu/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { React } from "@common";
import { React, components } from "@common";
import type { ContextMenuProps } from "@components/ContextMenu";
import type {
ContextItem,
Expand All @@ -7,7 +7,6 @@ import type {
RawContextItem,
} from "../../../types/coremods/contextMenu";
import { Logger } from "../../modules/logger";
import { getByProps } from "../../modules/webpack";

const logger = Logger.api("ContextMenu");

Expand Down Expand Up @@ -100,12 +99,8 @@ export function _insertMenuItems(menu: ContextMenuData): void {

// We delay getting the items until now, as importing at the start of the file causes Discord to hang
// Using `await import(...)` is undesirable because the new items will only appear once the menu is interacted with
const { MenuGroup } = getByProps<Record<string, React.ComponentType>>([
"Menu",
"MenuItem",
"MenuGroup",
]) || { MenuGroup: undefined };
if (!MenuGroup) return;
const { MenuGroup } = components;
//if (!MenuGroup) return;

// The data as passed as Arguments from the calling function, so we just grab what we want from it
const data = menu.data[0];
Expand Down
6 changes: 3 additions & 3 deletions src/renderer/coremods/installer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Injector } from "@replugged";
import type { Capture, DefaultInRule } from "simple-markdown";
import { plugins } from "src/renderer/managers/plugins";
import { themes } from "src/renderer/managers/themes";
import { filters, waitForModule } from "src/renderer/modules/webpack";
import { filters, getFunctionKeyBySource, waitForModule } from "src/renderer/modules/webpack";
import { ObjectExports } from "src/types";
import { registerRPCCommand } from "../rpc";
import { generalSettings } from "../settings/pages";
Expand Down Expand Up @@ -93,8 +93,8 @@ async function injectLinks(): Promise<void> {
const exports = linkMod.exports as ObjectExports & {
Anchor: React.FC<React.PropsWithChildren<AnchorProps>>;
};

injector.instead(exports, "Anchor", (args, fn) => {
const anchorKey = getFunctionKeyBySource(exports, "")! as "Anchor"; // It's actually a mangled name, but TS can sit down and shut up
injector.instead(exports, anchorKey, (args, fn) => {
const { href } = args[0];
if (!href) return fn(...args);
const installLink = parseInstallLink(href);
Expand Down
20 changes: 18 additions & 2 deletions src/renderer/coremods/notices/noticeMod.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type React from "react";
import { filters, waitForModule } from "src/renderer/modules/webpack";
import { filters, getFunctionBySource, waitForModule } from "src/renderer/modules/webpack";
import { ValueOf } from "type-fest";

interface AnchorProps extends React.ComponentPropsWithoutRef<"a"> {
useDefaultUnderlineStyles?: boolean;
Expand Down Expand Up @@ -53,4 +54,19 @@ interface NoticeMod {
Notice: React.FC<React.PropsWithChildren<NoticeProps>>;
}

export default await waitForModule<NoticeMod>(filters.bySource(".colorPremiumTier1,"));
const actualNoticeMod = await waitForModule<Record<string, ValueOf<NoticeMod>>>(
filters.bySource(".colorPremiumTier1,"),
);

const remappedNoticeMod: NoticeMod = {
NoticeColors: Object.values(actualNoticeMod).find(
(v) => typeof v === "object",
) as NoticeMod["NoticeColors"],
NoticeButton: getFunctionBySource(actualNoticeMod, "buttonMinor")!,
PrimaryCTANoticeButton: getFunctionBySource(actualNoticeMod, "CTA")!,
NoticeButtonAnchor: getFunctionBySource(actualNoticeMod, ".Anchor")!,
NoticeCloseButton: getFunctionBySource(actualNoticeMod, "DISMISS")!,
Notice: getFunctionBySource(actualNoticeMod, "isMobile")!,
};

export default remappedNoticeMod;
21 changes: 21 additions & 0 deletions src/renderer/coremods/notrack/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { waitForProps } from "@webpack";
import { Injector } from "@replugged";

const inj = new Injector();

export async function start(): Promise<void> {
const { AnalyticsActionHandlers } = await waitForProps<{
AnalyticsActionHandlers: {
handleConnectionClosed: () => void;
handleConnectionOpen: (e: unknown) => void;
handleFingerprint: () => void;
handleTrack: (e: unknown) => void;
};
}>("AnalyticsActionHandlers");

inj.instead(AnalyticsActionHandlers, "handleTrack", () => {});
}

export function stop(): void {
inj.uninjectAll();
}
16 changes: 10 additions & 6 deletions src/renderer/coremods/rpc/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/consistent-type-definitions */
import { Injector } from "@replugged";
import { BETA_WEBSITE_URL, WEBSITE_URL } from "src/constants";
import { filters, waitForModule, waitForProps } from "src/renderer/modules/webpack";
import { filters, getFunctionKeyBySource, waitForModule } from "src/renderer/modules/webpack";
import { Jsonifiable } from "type-fest";

const injector = new Injector();
Expand Down Expand Up @@ -39,11 +39,15 @@ type RPCMod = { commands: Commands };
let commands: Commands = {};

async function injectRpc(): Promise<void> {
const rpcValidatorMod = await waitForProps<{
fetchApplicationsRPC: (socket: Socket, client_id: string, origin: string) => Promise<void>;
}>("fetchApplicationsRPC");

injector.instead(rpcValidatorMod, "fetchApplicationsRPC", (args, fn) => {
//const rpcValidatorMod = await waitForProps<{
// fetchApplicationsRPC: (socket: Socket, client_id: string, origin: string) => Promise<void>;
//}>("fetchApplicationsRPC");
const rpcValidatorMod = await waitForModule<
Record<string, (socket: Socket, client_id: string, origin: string) => Promise<void>>
>(filters.bySource("Invalid Client ID"));
const fetchApplicationsRPCKey = getFunctionKeyBySource(rpcValidatorMod, "Invalid Client ID")!;

injector.instead(rpcValidatorMod, fetchApplicationsRPCKey, (args, fn) => {
const [, clientId, origin] = args;
const isRepluggedClient = clientId.startsWith("REPLUGGED-");

Expand Down
1 change: 1 addition & 0 deletions src/renderer/managers/coremods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export async function startAll(): Promise<void> {
coremods.watcher = await import("../coremods/watcher");
coremods.commands = await import("../coremods/commands");
coremods.welcome = await import("../coremods/welcome");
coremods.notrack = await import("../coremods/notrack");

await Promise.all(
Object.entries(coremods).map(async ([name, mod]) => {
Expand Down
41 changes: 39 additions & 2 deletions src/renderer/modules/common/api.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ProgressEvent, Request, Response } from "superagent";
import { waitForProps } from "../webpack";
import { filters, getFunctionBySource, waitForModule } from "../webpack";

interface HTTPAttachment {
file: string | Blob | Buffer;
Expand Down Expand Up @@ -125,4 +125,41 @@ export interface API {
setRequestPatch: (patch: RequestPatch) => void;
}

export default await waitForProps<API>("getAPIBaseURL", "HTTP");
const realApiModule = await waitForModule<Record<string, API[keyof API]>>(
filters.bySource("rateLimitExpirationHandler"),
);
const exportedValues = Object.values(realApiModule);

type APIErrorClass = typeof V6OrEarlierAPIError | typeof APIError;
const exportedClasses = exportedValues.filter((v) => typeof v === "function" && v.prototype);
const v6ErrorClass = exportedClasses.find(
(c) => "getFieldMessage" in (c as APIErrorClass).prototype,
) as typeof V6OrEarlierAPIError;
const v8ErrorClass = exportedClasses.find(
(c) => "hasFieldErrors" in (c as APIErrorClass).prototype,
) as typeof APIError;
const http = exportedValues.find((v) => typeof v === "object") as HTTP;
const invalidFormBodyErrorCode = exportedValues.find((v) => typeof v === "number") as number;

const getAPIBaseURL = getFunctionBySource<API["getAPIBaseURL"]>(
realApiModule,
"GLOBAL_ENV.API_ENDPOINT",
)!;
const convertSkemaError = getFunctionBySource<API["convertSkemaError"]>(realApiModule, "message")!;
// TODO: these suck. Make them better later.
const setAwaitOnline = getFunctionBySource<API["setAwaitOnline"]>(realApiModule, /v\s*=\s*e/)!;
const setRequestPatch = getFunctionBySource<API["setRequestPatch"]>(realApiModule, /g\s*=\s*e/)!;

// "If only, if only," the woodpecker sighs...
//export default await waitForProps<API>("getAPIBaseURL", "HTTP");

export default {
INVALID_FORM_BODY_ERROR_CODE: invalidFormBodyErrorCode,
V6OrEarlierAPIError: v6ErrorClass,
V8APIError: v8ErrorClass,
HTTP: http,
getAPIBaseURL,
convertSkemaError,
setAwaitOnline,
setRequestPatch,
} as API;
19 changes: 16 additions & 3 deletions src/renderer/modules/common/constants.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import { virtualMerge } from "src/renderer/util";
import { filters, getExportsForProps, waitForModule, waitForProps } from "../webpack";
import { filters, getExportsForProps, waitForModule } from "../webpack";

type StringConcat = (...rest: string[]) => string;

const ConstantsCommon = await waitForProps<Record<string, unknown>>("Links", "RPCCommands");
const Constants = await waitForProps<Record<string, unknown>>("Endpoints", "Routes");
//const ConstantsCommon = await waitForProps<Record<string, unknown>>("Links", "RPCCommands");
const ConstantsCommon = await waitForModule<Record<string, unknown>>(
filters.bySource("dis.gd/request"),
);
//const Constants = await waitForProps<Record<string, unknown>>("Endpoints", "Routes");
const Constants = await waitForModule<Record<string, unknown>>(
filters.bySource("users/@me/relationships"),
);
export const raw = virtualMerge(ConstantsCommon, Constants);

export const Permissions = getExportsForProps<Record<string, bigint>>(ConstantsCommon, [
Expand Down Expand Up @@ -66,10 +72,16 @@ export const UserFlags = getExportsForProps<Record<string, string | number>>(Con
])!;

// ThemeColor
//Ambiguous: should this be the just-dashed-names or --var(css-var-strings)?
// Go with the latter for now.
/*
export const CSSVariables = await waitForProps<Record<string, string>>(
"TEXT_NORMAL",
"BACKGROUND_PRIMARY",
);
*/
// We *should* be able to do props, but there's so much extra junk with the current search implementation.
export const CSSVariables = await waitForModule(filters.bySource('="var(--background-floating)"'));

interface ColorResponse {
hex: () => string;
Expand Down Expand Up @@ -117,6 +129,7 @@ interface ColorMod {
unsafe_rawColors: Record<string, UnsafeRawColor>;
}

// This could really be a search by props, for unsafe_rawColors.
export const ColorGenerator = await waitForModule<ColorMod>(
filters.bySource(/\w+\.unsafe_rawColors\[\w+\]\.resolve\(\w+\)/),
);
Expand Down
Loading
Loading