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

Adds support for global commands and makes /help a global command #92

Merged
merged 1 commit into from
May 26, 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
10 changes: 9 additions & 1 deletion src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,15 @@ import trackerCommand from "./commands/tracker.js";
import trailerCommand from "./commands/trailer.js";
import updateCommand from "./commands/update.js";
import verifyCommand from "./commands/verify.js";
type ApplicationCommandData = ChatInputApplicationCommandData | MessageApplicationCommandData;
type ApplicationCommandData = (ChatInputApplicationCommandData | MessageApplicationCommandData) & (
{
default_permission?: true,
dmPermission: false,
global: true;
} | {
global?: false;
}
);
type ApplicationUserInteraction = AutocompleteInteraction<"cached"> | ChatInputCommandInteraction<"cached"> | MessageContextMenuCommandInteraction<"cached">;
type Command = {
register(): ApplicationCommandData;
Expand Down
1 change: 0 additions & 1 deletion src/commands/apply.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ const applyCommand: Command = {
description: commandDescription["en-US"],
descriptionLocalizations: commandDescription,
defaultMemberPermissions: [],
dmPermission: false,
};
},
async interact(interaction: ApplicationUserInteraction): Promise<void> {
Expand Down
1 change: 0 additions & 1 deletion src/commands/approve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ const approveCommand: Command = {
name: commandName,
nameLocalizations: commandDescription,
defaultMemberPermissions: [],
dmPermission: false,
};
},
async interact(interaction: ApplicationUserInteraction): Promise<void> {
Expand Down
1 change: 0 additions & 1 deletion src/commands/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,6 @@ const chatCommand: Command = {
},
],
defaultMemberPermissions: [],
dmPermission: false,
};
},
async interact(interaction: ApplicationUserInteraction): Promise<void> {
Expand Down
1 change: 0 additions & 1 deletion src/commands/emoji.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ const emojiCommand: Command = {
}).flat<ApplicationCommandOptionData[][]>(),
],
defaultMemberPermissions: [],
dmPermission: false,
};
},
async interact(interaction: ApplicationUserInteraction): Promise<void> {
Expand Down
1 change: 0 additions & 1 deletion src/commands/gate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,6 @@ const gateCommand: Command = {
},
],
defaultMemberPermissions: [],
dmPermission: false,
};
},
async interact(interaction: ApplicationUserInteraction): Promise<void> {
Expand Down
70 changes: 47 additions & 23 deletions src/commands/help.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,12 @@ const features: Feature[] = [
};
}),
];
let globalFetchedTimestamp: number = Number.NEGATIVE_INFINITY;
let globalApplicationCommandPermissions: Collection<string, ApplicationCommandPermissions[]> | null = null;
const guildFetchedTimestamps: Collection<Snowflake, number> = new Collection<Snowflake, number>();
const guildApplicationCommandPermissions: Collection<Snowflake, Collection<string, ApplicationCommandPermissions[]> | undefined> = new Collection<Snowflake, Collection<string, ApplicationCommandPermissions[]> | undefined>();
const guildWebhooks: Collection<Snowflake, Collection<Snowflake, Webhook> | undefined> = new Collection<Snowflake, Collection<Snowflake, Webhook> | undefined>();
const guildAutoModerationRules: Collection<Snowflake, Collection<Snowflake, AutoModerationRule> | undefined> = new Collection<Snowflake, Collection<Snowflake, AutoModerationRule> | undefined>();
const guildApplicationCommandPermissions: Collection<Snowflake, Collection<string, ApplicationCommandPermissions[]>> = new Collection<Snowflake, Collection<string, ApplicationCommandPermissions[]>>();
const guildWebhooks: Collection<Snowflake, Collection<Snowflake, Webhook>> = new Collection<Snowflake, Collection<Snowflake, Webhook>>();
const guildAutoModerationRules: Collection<Snowflake, Collection<Snowflake, AutoModerationRule>> = new Collection<Snowflake, Collection<Snowflake, AutoModerationRule>>();
function naiveStream(content: string): string[] {
content = content.replace(/^\n+|\n+$/g, "").replace(/\n+/g, "\n");
if (content.length === 0) {
Expand Down Expand Up @@ -237,19 +239,43 @@ const helpCommand: Command = {
autocomplete: true,
},
],
dmPermission: false,
global: true,
};
},
async interact(interaction: ApplicationUserInteraction): Promise<void> {
const {client, createdTimestamp, member, guild}: ApplicationUserInteraction = interaction;
const fetchedTimestamp: number = guildFetchedTimestamps.get(guild.id) ?? Number.NEGATIVE_INFINITY;
const elapsedTime: number = createdTimestamp - fetchedTimestamp;
const forceFetch: boolean = elapsedTime >= 60000;
if (forceFetch) {
const globalElapsedTime: number = createdTimestamp - globalFetchedTimestamp;
const globalForceFetch: boolean = globalElapsedTime >= 60000;
if (globalForceFetch) {
globalFetchedTimestamp = createdTimestamp;
}
const guildFetchedTimestamp: number = guildFetchedTimestamps.get(guild.id) ?? Number.NEGATIVE_INFINITY;
const guildElapsedTime: number = createdTimestamp - guildFetchedTimestamp;
const guildForceFetch: boolean = guildElapsedTime >= 60000;
if (guildForceFetch) {
guildFetchedTimestamps.set(guild.id, createdTimestamp);
}
const applicationCommands: Collection<string, ApplicationCommand> = guild.commands.cache;
const permissions: Collection<string, ApplicationCommandPermissions[]> | undefined = await (async (): Promise<Collection<string, ApplicationCommandPermissions[]> | undefined> => {
if (forceFetch) {
const globalApplicationCommands: Collection<string, ApplicationCommand> = client.application.commands.cache;
const guildApplicationCommands: Collection<string, ApplicationCommand> = guild.commands.cache;
const applicationCommands: Collection<string, ApplicationCommand> = globalApplicationCommands.concat(guildApplicationCommands);
const globalPermissions: Collection<string, ApplicationCommandPermissions[]> | null = await (async (): Promise<Collection<string, ApplicationCommandPermissions[]> | null> => {
if (globalForceFetch) {
try {
globalApplicationCommandPermissions = await client.application.commands.permissions.fetch({
guild,
});
} catch {
globalApplicationCommandPermissions = null;
}
}
return globalApplicationCommandPermissions;
})();
if (globalPermissions == null) {
return;
}
const guildPermissions: Collection<string, ApplicationCommandPermissions[]> | undefined = await (async (): Promise<Collection<string, ApplicationCommandPermissions[]> | undefined> => {
if (guildForceFetch) {
try {
guildApplicationCommandPermissions.set(guild.id, await guild.commands.permissions.fetch({}));
} catch {
Expand All @@ -258,11 +284,12 @@ const helpCommand: Command = {
}
return guildApplicationCommandPermissions.get(guild.id);
})();
if (permissions == null) {
if (guildPermissions == null) {
return;
}
const permissions: Collection<string, ApplicationCommandPermissions[]> = globalPermissions.concat(guildPermissions);
const webhooks: Collection<string, Webhook> | undefined = await (async (): Promise<Collection<string, Webhook> | undefined> => {
if (forceFetch) {
if (guildForceFetch) {
try {
guildWebhooks.set(guild.id, await guild.fetchWebhooks());
} catch {
Expand All @@ -275,7 +302,7 @@ const helpCommand: Command = {
return;
}
const autoModerationRules: Collection<string, AutoModerationRule> | undefined = await (async (): Promise<Collection<string, AutoModerationRule> | undefined> => {
if (forceFetch) {
if (guildForceFetch) {
try {
guildAutoModerationRules.set(guild.id, await guild.autoModerationRules.fetch());
} catch {
Expand Down Expand Up @@ -309,7 +336,7 @@ const helpCommand: Command = {
await interaction.respond([]);
return;
}
const results: Feature[] = nearest<Feature>(value.toLocaleLowerCase(resolvedLocale), features, 7, (feature: Feature): string => {
const results: Feature[] = nearest<Feature>(value.replace(/ \((command|hook|rule)\)$/u, "").toLocaleLowerCase(resolvedLocale), features, 7, (feature: Feature): string => {
const {name}: Feature = feature;
return name.toLocaleLowerCase(resolvedLocale);
});
Expand All @@ -325,9 +352,6 @@ const helpCommand: Command = {
if (applicationCommand == null) {
return null;
}
if (applicationCommand.guild == null) {
return null;
}
if (applicationCommand.type !== ApplicationCommandType.ChatInput && applicationCommand.type !== ApplicationCommandType.Message) {
return null;
}
Expand Down Expand Up @@ -423,9 +447,6 @@ const helpCommand: Command = {
if (applicationCommand == null) {
return null;
}
if (applicationCommand.guild == null) {
return null;
}
if (applicationCommand.type !== ApplicationCommandType.ChatInput && applicationCommand.type !== ApplicationCommandType.Message) {
return null;
}
Expand Down Expand Up @@ -578,9 +599,6 @@ const helpCommand: Command = {
if (applicationCommand == null) {
return null;
}
if (applicationCommand.guild == null) {
return null;
}
if (applicationCommand.type !== ApplicationCommandType.ChatInput && applicationCommand.type !== ApplicationCommandType.Message) {
return null;
}
Expand Down Expand Up @@ -664,13 +682,19 @@ const helpCommand: Command = {
}
await interaction.reply({
content: formatMessage("en-US"),
allowedMentions: {
users: [],
},
});
if (resolvedLocale === "en-US") {
return;
}
await interaction.followUp({
content: formatMessage(resolvedLocale),
ephemeral: true,
allowedMentions: {
users: [],
},
});
return;
}
Expand Down
1 change: 0 additions & 1 deletion src/commands/patch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ const patchCommand: Command = {
name: commandName,
nameLocalizations: commandDescription,
defaultMemberPermissions: [],
dmPermission: false,
};
},
async interact(interaction: ApplicationUserInteraction): Promise<void> {
Expand Down
1 change: 0 additions & 1 deletion src/commands/refuse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ const refuseCommand: Command = {
name: commandName,
nameLocalizations: commandDescription,
defaultMemberPermissions: [],
dmPermission: false,
};
},
async interact(interaction: ApplicationUserInteraction): Promise<void> {
Expand Down
1 change: 0 additions & 1 deletion src/commands/verify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ const verifyCommand: Command = {
description: commandDescription["en-US"],
descriptionLocalizations: commandDescription,
defaultMemberPermissions: [],
dmPermission: false,
};
},
async interact(interaction: ApplicationUserInteraction): Promise<void> {
Expand Down
35 changes: 29 additions & 6 deletions src/shicka.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import type {
ApplicationCommandDataResolvable,
AutoModerationActionMetadataOptions,
AutoModerationActionOptions,
AutoModerationRuleCreateOptions,
ClientApplication,
ClientEvents,
Collection,
ForumChannel,
Expand Down Expand Up @@ -35,16 +37,26 @@ import schedule from "node-schedule";
import * as commands from "./commands.js";
import * as hooks from "./hooks.js";
import * as rules from "./rules.js";
type ApplicationCommandCreateOptionsResolvable = ApplicationCommandData;
type GlobalApplicationCommandCreateOptionsResolvable = ApplicationCommandCreateOptionsResolvable & {global: true};
type GuildApplicationCommandCreateOptionsResolvable = ApplicationCommandCreateOptionsResolvable & {global?: false};
type WebhookCreateOptionsResolvable = WebhookData["hookOptions"];
type WebjobEvent = WebjobInvocation["event"];
type AutoModerationRuleCreateOptionsResolvable = AutoModerationRuleData;
const {
SHICKA_DISCORD_TOKEN,
}: NodeJS.ProcessEnv = process.env;
const discordToken: string = SHICKA_DISCORD_TOKEN ?? "";
async function submitGuildCommands(guild: Guild, commandRegistry: ApplicationCommandData[]): Promise<boolean> {
async function submitCommands(applicationOrGuild: ClientApplication | Guild, globalOrGuildCommandRegistry: ApplicationCommandCreateOptionsResolvable[]): Promise<boolean> {
const commandRegistry: ApplicationCommandDataResolvable[] = globalOrGuildCommandRegistry.map<ApplicationCommandDataResolvable>((commandOptionsResolvable: ApplicationCommandCreateOptionsResolvable): ApplicationCommandDataResolvable => {
const {
global,
...commandOptions
}: ApplicationCommandCreateOptionsResolvable = commandOptionsResolvable;
return commandOptions;
})
try {
await guild.commands.set(commandRegistry);
await applicationOrGuild.commands.set(commandRegistry);
} catch (error: unknown) {
console.warn(error);
return false;
Expand Down Expand Up @@ -267,9 +279,23 @@ client.once("ready", async (client: Client<true>): Promise<void> => {
const command: Command = commands[commandName as keyof typeof commands];
return command.register();
});
const globalCommandRegistry: GlobalApplicationCommandCreateOptionsResolvable[] = commandRegistry.filter<GlobalApplicationCommandCreateOptionsResolvable>((commandData: ApplicationCommandData): commandData is GlobalApplicationCommandCreateOptionsResolvable => {
return commandData.global ?? false;
});
try {
const submitted: boolean = await submitCommands(client.application, globalCommandRegistry);
if (submitted == false) {
throw new Error();
}
} catch (error: unknown) {
console.error(error);
}
const guildCommandRegistry: GuildApplicationCommandCreateOptionsResolvable[] = commandRegistry.filter<GuildApplicationCommandCreateOptionsResolvable>((commandData: ApplicationCommandData): commandData is GuildApplicationCommandCreateOptionsResolvable => {
return !(commandData.global ?? false);
});
for (const guild of client.guilds.cache.values()) {
try {
const submitted: boolean = await submitGuildCommands(guild, commandRegistry);
const submitted: boolean = await submitCommands(guild, guildCommandRegistry);
if (submitted == false) {
throw new Error();
}
Expand Down Expand Up @@ -433,9 +459,6 @@ client.on("interactionCreate", async (interaction: Interaction): Promise<void> =
if (command == null) {
return;
}
if (command.guild == null) {
return;
}
if (command.type !== ApplicationCommandType.ChatInput && command.type !== ApplicationCommandType.Message) {
return;
}
Expand Down