Skip to content

Commit

Permalink
Fix various coremods patches and app crashes (#648)
Browse files Browse the repository at this point in the history
* fix(coremods): message popover plaintext patch

* fix(coremods): language and notice check in plaintext patch

remove unused commands plaintext patch

* fix: crash while typing after a bot mention

* fix: custom slash commands not showing its category

* feat: update types

* refactor(messagePopover): simplify the plaintext patch by getting the component by source

* feat: kill sentry

* fix: notices plaintext patch
  • Loading branch information
FedeIlLeone authored Oct 17, 2024
1 parent f731e09 commit e0b9ed3
Show file tree
Hide file tree
Showing 24 changed files with 179 additions and 249 deletions.
1 change: 1 addition & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"installdir",
"Jsonifiable",
"konami",
"leaderboard",
"lezer",
"LOCALAPPDATA",
"logname",
Expand Down
22 changes: 13 additions & 9 deletions src/renderer/apis/commands.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Channel, Guild, User } from "discord-types/general";
import { REPLUGGED_CLYDE_ID } from "../../constants";
import type {
AnyRepluggedCommand,
CommandOptionReturn,
Expand All @@ -12,12 +13,12 @@ import type {
} from "../../types";
// eslint-disable-next-line no-duplicate-imports
import { ApplicationCommandOptionType } from "../../types";
import icon from "../assets/logo.png";
import { constants, i18n, messages, users } from "../modules/common";
import type { Store } from "../modules/common/flux";
import { Logger } from "../modules/logger";
import { filters, getByStoreName, waitForModule } from "../modules/webpack";
import icon from "../assets/logo.png";
import { REPLUGGED_CLYDE_ID } from "../../constants";

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

let RepluggedUser: User | undefined;
Expand Down Expand Up @@ -204,9 +205,9 @@ export class CommandManager {
}

/**
* Code to register an slash command
* @param cmd Slash Command to be registered
* @returns An Callback to unregister the slash command
* Code to register a slash command
* @param command Slash command to be registered
* @returns A callback to unregister the slash command
*/
public registerCommand<const T extends CommandOptions>(command: RepluggedCommand<T>): () => void {
if (!commandAndSections.has(this.#section.id)) {
Expand All @@ -215,8 +216,10 @@ export class CommandManager {
commands: new Map<string, AnyRepluggedCommand>(),
});
}
const currentSection = commandAndSections.get(this.#section.id);
command.applicationId = currentSection?.section.id;

const currentSection = commandAndSections.get(this.#section.id)!; // Can't be undefined as we set it above
command.section = currentSection.section;
command.applicationId = currentSection.section.id;
command.displayName ??= command.name;
command.displayDescription ??= command.description;
command.type = 2;
Expand All @@ -233,15 +236,16 @@ export class CommandManager {
return option;
});

currentSection?.commands.set(command.id, command as AnyRepluggedCommand);
currentSection.commands.set(command.id, command as AnyRepluggedCommand);

const uninject = (): void => {
void currentSection?.commands.delete(command.id!);
void currentSection.commands.delete(command.id!);
this.#unregister = this.#unregister.filter((u) => u !== uninject);
};
this.#unregister.push(uninject);
return uninject;
}

/**
* Code to unregister all slash commands registered with this class
*/
Expand Down
185 changes: 67 additions & 118 deletions src/renderer/coremods/commands/index.ts
Original file line number Diff line number Diff line change
@@ -1,151 +1,104 @@
import type { AnyRepluggedCommand, RepluggedCommandSection } from "../../../types";
import { type Store } from "@common/flux";
import type { Channel, Guild } from "discord-types/general";
import flux, { type Store } from "@common/flux";
import { REPLUGGED_CLYDE_ID } from "../../../constants";
import type { AnyRepluggedCommand, RepluggedCommandSection } from "../../../types";
import { commandAndSections, defaultSection } from "../../apis/commands";
import { Injector } from "../../modules/injector";
import { Logger } from "../../modules/logger";
import {
filters,
getByStoreName,
getFunctionKeyBySource,
waitForModule,
waitForProps,
} from "../../modules/webpack";

import { commandAndSections, defaultSection } from "../../apis/commands";
import { loadCommands, unloadCommands } from "./commands";
import { REPLUGGED_CLYDE_ID } from "../../../constants";

const logger = Logger.api("Commands");
const injector = new Injector();

type CommandState =
| {
fetchState: { fetching: boolean };
result: {
sectionIdsByBotId: Record<string, string>;
sections: Record<
string,
{ commands: Record<string, AnyRepluggedCommand>; descriptor: RepluggedCommandSection }
>;
version: string;
};
serverVersion: symbol | string;
}
| undefined;
interface QueryOptions {
commandTypes: number[];
applicationCommands?: boolean;
text?: string;
builtIns?: "allow" | "only_text" | "deny";
}

interface FetchOptions {
applicationId?: string;
allowFetch?: boolean;
scoreMethod?: "none" | "application_only" | "command_only" | "command_or_application";
allowEmptySections?: boolean;
allowApplicationState?: boolean;
sortOptions?: {
applications?: { useFrecency: boolean; useScore: boolean };
commands?: { useFrecency: boolean; useScore: boolean };
};
installOnDemand?: boolean;
includeFrecency?: boolean;
}

interface ApplicationCommandIndex {
descriptors: RepluggedCommandSection[];
commands: AnyRepluggedCommand[];
sectionedCommands: Array<{ data: AnyRepluggedCommand[]; section: RepluggedCommandSection }>;
loading: boolean;
}

interface ApplicationCommandIndexStore extends Store {
getContextState: (channel: Channel) => CommandState;
getUserState: () => CommandState;
query: (
channel: Channel,
queryOptions: {
commandType: number;
text: string;
},
fetchOptions: {
allowFetch: boolean;
limit: number;
includeFrecency?: boolean;
placeholderCount?: number;
scoreMethod?: string;
},
) =>
| {
descriptors: RepluggedCommandSection[];
commands: AnyRepluggedCommand[];
sectionedCommands: Array<{ data: AnyRepluggedCommand[]; section: RepluggedCommandSection }>;
loading: boolean;
}
| undefined;
queryOptions: QueryOptions,
fetchOptions: FetchOptions,
) => ApplicationCommandIndex;
}

interface ApplicationCommandIndexStoreMod {
useContextIndexState: (
channel: Channel,
allowCache: boolean,
allowFetch: boolean,
) => CommandState;
useDiscoveryState: (
channel: Channel,
guild: Guild,
commandOptions: {
commandType: number;
applicationCommands?: boolean;
builtIns?: "allow" | "deny";
},
fetchOptions: {
allowFetch: boolean;
limit: number;
includeFrecency?: boolean;
placeholderCount?: number;
scoreMethod?: string;
},
) =>
| {
descriptors: RepluggedCommandSection[];
commands: AnyRepluggedCommand[];
loading: boolean;
sectionedCommands: Array<{ data: AnyRepluggedCommand[]; section: RepluggedCommandSection }>;
}
| undefined;
useGuildIndexState: (guildId: string, allowFetch: boolean) => CommandState;
useUserIndexState: (allowCache: boolean, allowFetch: boolean) => CommandState;
default: ApplicationCommandIndexStore;
}
type UseDiscoveryState = (
channel: Channel,
guild: Guild,
queryOptions: QueryOptions,
fetchOptions: FetchOptions,
) => ApplicationCommandIndex;

type FetchProfile = (id: string) => Promise<void>;

async function injectRepluggedBotIcon(): Promise<void> {
// Adds Avatar for replugged to default avatar to be used by system bot just like clyde
// Adds avatar for Replugged to be used by system bot just like Clyde
// Ain't removing it on stop because we have checks here
const DefaultAvatars = await waitForProps<{
BOT_AVATARS: Record<string, string> | undefined;
const avatarUtilsMod = await waitForProps<{
BOT_AVATARS: Record<string, string>;
}>("BOT_AVATARS");
if (DefaultAvatars.BOT_AVATARS) {
DefaultAvatars.BOT_AVATARS.replugged = defaultSection.icon;
} else {
logger.error("Error while injecting custom icon for slash command replies.");
}
avatarUtilsMod.BOT_AVATARS.replugged = defaultSection.icon;
}

async function injectRepluggedSectionIcon(): Promise<void> {
// Patches the function which gets icon URL for slash command sections
// makes it return the custom url if it's our section
const AssetsUtils = await waitForProps<{
const avatarUtilsMod = await waitForProps<{
getApplicationIconURL: (args: { id: string; icon: string }) => string;
}>("getApplicationIconURL");
injector.after(AssetsUtils, "getApplicationIconURL", ([section], res) =>
injector.after(avatarUtilsMod, "getApplicationIconURL", ([section], res) =>
commandAndSections.has(section.id) ? commandAndSections.get(section.id)?.section.icon : res,
);
}

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>(
const applicationCommandIndexStoreMod = await waitForModule<Record<string, UseDiscoveryState>>(
filters.bySource("APPLICATION_COMMAND_INDEX"),
);

// This "as" hack is horrible.
const useDiscoveryStateKey = getFunctionKeyBySource(
ApplicationCommandIndexStoreMod,
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,
applicationCommandIndexStoreMod,
useDiscoveryStateKey,
([, , { commandType }], res) => {
([, , { commandTypes }], res) => {
const commandAndSectionsArray = Array.from(commandAndSections.values()).filter(
(commandAndSection) => commandAndSection.commands.size,
);
if (!res || !commandAndSectionsArray.length || commandType !== 1) return res;
if (!commandAndSectionsArray.length || !commandTypes.includes(1)) return res;
if (
!Array.isArray(res.descriptors) ||
!commandAndSectionsArray.every((commandAndSection) =>
Expand Down Expand Up @@ -212,11 +165,9 @@ async function injectApplicationCommandIndexStore(): Promise<void> {
},
);

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

// Slash command indexing patched to return our slash commands too
// only those which match tho
Expand All @@ -234,7 +185,7 @@ async function injectApplicationCommandIndexStore(): Promise<void> {
),
}))
.filter((commandAndSection) => commandAndSection.commands.length);
if (!res || !commandAndSectionsArray.length) return res;
if (!commandAndSectionsArray.length) return res;

if (
!Array.isArray(res.descriptors) ||
Expand Down Expand Up @@ -286,27 +237,25 @@ async function injectApplicationCommandIndexStore(): Promise<void> {
}

async function injectProfileFetch(): Promise<void> {
//const mod = await waitForProps<{
// fetchProfile: (id: string) => Promise<void>;
//}>("fetchProfile");
const mod = await waitForModule<Record<string, (id: string) => Promise<void>>>(
const userActionCreatorsMod = await waitForModule<Record<string, FetchProfile>>(
filters.bySource("fetchProfile"),
);
const fetchProfileKey = getFunctionKeyBySource(mod, "fetchProfile")!;
injector.instead(mod, fetchProfileKey, (args, res) => {
if (args[0] === REPLUGGED_CLYDE_ID) {
return;
}
return res(...args);
const fetchProfileKey = getFunctionKeyBySource(userActionCreatorsMod, "fetchProfile")!;

injector.instead(userActionCreatorsMod, fetchProfileKey, (args, orig) => {
if (args[0] === REPLUGGED_CLYDE_ID) return;
return orig(...args);
});
}

export async function start(): Promise<void> {
await injectRepluggedBotIcon();
await injectRepluggedSectionIcon();
await injectApplicationCommandIndexStore();
await injectProfileFetch();
loadCommands();
}

export function stop(): void {
injector.uninjectAll();
unloadCommands();
Expand Down
14 changes: 0 additions & 14 deletions src/renderer/coremods/commands/plaintextPatches.ts

This file was deleted.

3 changes: 2 additions & 1 deletion src/renderer/coremods/language/plaintextPatches.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ export default [
},
{
match: /children:\[(.+?\.localeName[^\]]*?)]/,
replace: (_, ogChild) => `children:[${coremodStr}?.Percentage(${ogChild}) ?? ${ogChild}]`,
replace: (_, ogChild) =>
`children:${coremodStr}?.Percentage?${coremodStr}.Percentage(${ogChild}):[${ogChild}]`,
},
],
},
Expand Down
Loading

0 comments on commit e0b9ed3

Please sign in to comment.