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 various coremods patches and app crashes #648

Merged
merged 8 commits into from
Oct 17, 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
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