Skip to content

Commit

Permalink
Fix for Discord re-enabling mangled exports (#631)
Browse files Browse the repository at this point in the history
  • Loading branch information
colin273 authored Jun 21, 2024
1 parent d9e8097 commit 98342c5
Show file tree
Hide file tree
Showing 16 changed files with 230 additions and 74 deletions.
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

0 comments on commit 98342c5

Please sign in to comment.