Skip to content

Commit

Permalink
feat(commands): use interaction context types
Browse files Browse the repository at this point in the history
This adds user-installable app support
  • Loading branch information
kyranet committed Sep 4, 2024
1 parent 34b18c2 commit 07c4848
Show file tree
Hide file tree
Showing 21 changed files with 278 additions and 250 deletions.
14 changes: 7 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@
"update": "yarn upgrade-interactive"
},
"dependencies": {
"@discordjs/builders": "^1.8.2",
"@discordjs/collection": "^2.1.0",
"@discordjs/builders": "^1.9.0",
"@discordjs/collection": "^2.1.1",
"@napi-rs/canvas": "^0.1.54",
"@napi-rs/image": "^1.9.2",
"@prisma/client": "^5.19.0",
"@prisma/client": "^5.19.1",
"@sapphire/async-queue": "^1.5.3",
"@sapphire/duration": "^1.1.2",
"@sapphire/result": "^2.6.6",
Expand All @@ -48,7 +48,7 @@
"@vladfrangu/async_event_emitter": "^2.4.6",
"canvas-constructor": "^7.0.2",
"culori": "^4.0.1",
"discord-api-types": "^0.37.98",
"discord-api-types": "^0.37.99",
"gradient-string": "^2.0.2",
"he": "^1.2.0",
"ioredis": "^5.4.1",
Expand All @@ -67,17 +67,17 @@
"@types/gradient-string": "^1.1.6",
"@types/he": "^1.2.3",
"@types/luxon": "^3.4.2",
"@types/node": "^20.16.2",
"@types/node": "^20.16.4",
"@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0",
"@vitest/coverage-v8": "^2.0.5",
"cz-conventional-changelog": "^3.3.0",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.2.1",
"lint-staged": "^15.2.9",
"lint-staged": "^15.2.10",
"prettier": "^3.3.3",
"prisma": "^5.19.0",
"prisma": "^5.19.1",
"rimraf": "^6.0.1",
"tar": "^7.4.3",
"typescript": "~5.4.5",
Expand Down
5 changes: 3 additions & 2 deletions src/commands/choice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@ import { escapeInlineCode } from '#lib/common/escape';
import { LanguageKeys } from '#lib/i18n/LanguageKeys';
import { Command, RegisterCommand } from '@skyra/http-framework';
import { applyLocalizedBuilder, resolveKey, resolveUserKey } from '@skyra/http-framework-i18n';
import { MessageFlags } from 'discord-api-types/v10';
import { InteractionContextType, MessageFlags } from 'discord-api-types/v10';

@RegisterCommand((builder) =>
applyLocalizedBuilder(builder, LanguageKeys.Commands.Choice.RootName, LanguageKeys.Commands.Choice.RootDescription) //
.setContexts(InteractionContextType.Guild, InteractionContextType.BotDM, InteractionContextType.PrivateChannel)
.addStringOption((builder) => applyLocalizedBuilder(builder, LanguageKeys.Commands.Choice.OptionsValues).setMinLength(3).setRequired(true))
)
export class UserCommand extends Command {
public override chatInputRun(interaction: Command.ChatInputInteraction, args: Options) {
const possibles = args.values
.split(/[,\|\-]+/)
.split(/[,|-]+/)
.map((s) => s.trim())
.filter((s) => s.length > 0);
if (possibles.length === 1) {
Expand Down
5 changes: 3 additions & 2 deletions src/commands/color.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ import { Command, RegisterCommand } from '@skyra/http-framework';
import { applyLocalizedBuilder, resolveUserKey } from '@skyra/http-framework-i18n';
import { Json, safeTimedFetch } from '@skyra/safe-fetch';
import { formatHex, formatHex8, hsl, oklch, p3, parse, rgb, type Color } from 'culori';
import { MessageFlags } from 'discord-api-types/v10';
import { InteractionContextType, MessageFlags } from 'discord-api-types/v10';

const Root = LanguageKeys.Commands.Color;

@RegisterCommand((builder) =>
applyLocalizedBuilder(builder, Root.RootName, Root.RootDescription) //
.setContexts(InteractionContextType.Guild, InteractionContextType.BotDM, InteractionContextType.PrivateChannel)
.addStringOption((builder) => applyLocalizedBuilder(builder, Root.Input).setRequired(true))
)
export class UserCommand extends Command {
Expand Down Expand Up @@ -87,7 +88,7 @@ export class UserCommand extends Command {
}

private formatOklch(color: Color) {
const parsed = oklch(color)!;
const parsed = oklch(color);
const l = round6(parsed.l * 100);
const c = round6(parsed.c);
const h = round6(parsed.h ?? 0);
Expand Down
12 changes: 9 additions & 3 deletions src/commands/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,21 @@ import { codeBlock } from '@discordjs/builders';
import type { RawFile } from '@discordjs/rest';
import { Command, RegisterMessageCommand, RegisterUserCommand, type TransformedArguments } from '@skyra/http-framework';
import { applyNameLocalizedBuilder } from '@skyra/http-framework-i18n';
import { MessageFlags } from 'discord-api-types/v10';
import { InteractionContextType, MessageFlags } from 'discord-api-types/v10';

export class UserCommand extends Command {
@RegisterMessageCommand((builder) => applyNameLocalizedBuilder(builder, LanguageKeys.Commands.Content.MessageJsonName))
@RegisterMessageCommand((builder) =>
applyNameLocalizedBuilder(builder, LanguageKeys.Commands.Content.MessageJsonName) //
.setContexts(InteractionContextType.Guild, InteractionContextType.BotDM, InteractionContextType.PrivateChannel)
)
public message(interaction: Command.MessageInteraction, options: TransformedArguments.Message) {
return this.shared(interaction, options.message, options.message.id);
}

@RegisterUserCommand((builder) => applyNameLocalizedBuilder(builder, LanguageKeys.Commands.Content.UserJsonName))
@RegisterUserCommand((builder) =>
applyNameLocalizedBuilder(builder, LanguageKeys.Commands.Content.UserJsonName) //
.setContexts(InteractionContextType.Guild, InteractionContextType.BotDM, InteractionContextType.PrivateChannel)
)
public user(interaction: Command.UserInteraction, options: TransformedArguments.User) {
const data = options.member ? { ...options.member, user: options.user } : options.user;
return this.shared(interaction, data, options.user.id);
Expand Down
3 changes: 2 additions & 1 deletion src/commands/convert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ import { Units, renderUnit, sanitizeUnit, searchUnits } from '#lib/utilities/con
import { isNullish } from '@sapphire/utilities';
import { Command, RegisterCommand, type AutocompleteInteractionArguments } from '@skyra/http-framework';
import { applyLocalizedBuilder, getSupportedUserLanguageT, resolveUserKey } from '@skyra/http-framework-i18n';
import { MessageFlags } from 'discord-api-types/v10';
import { InteractionContextType, MessageFlags } from 'discord-api-types/v10';

const Root = LanguageKeys.Commands.Convert;

@RegisterCommand((builder) =>
applyLocalizedBuilder(builder, Root.RootName, Root.RootDescription) //
.setContexts(InteractionContextType.Guild, InteractionContextType.BotDM, InteractionContextType.PrivateChannel)
.addNumberOption((builder) => applyLocalizedBuilder(builder, LanguageKeys.Commands.Convert.Amount).setRequired(true))
.addStringOption((builder) => applyLocalizedBuilder(builder, Root.From).setAutocomplete(true).setRequired(true).setMaxLength(100))
.addStringOption((builder) => applyLocalizedBuilder(builder, Root.To).setAutocomplete(true).setRequired(true).setMaxLength(100))
Expand Down
11 changes: 4 additions & 7 deletions src/commands/create-emoji.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { Command, RegisterCommand } from '@skyra/http-framework';
import { applyLocalizedBuilder, createSelectMenuChoiceName, resolveKey, resolveUserKey } from '@skyra/http-framework-i18n';
import { safeTimedFetch, type FetchResult } from '@skyra/safe-fetch';
import {
InteractionContextType,
MessageFlags,
PermissionFlagsBits,
RESTJSONErrorCodes,
Expand Down Expand Up @@ -46,7 +47,7 @@ const EmojiRoot = LanguageKeys.Commands.Emoji;
createSelectMenuChoiceName(EmojiRoot.OptionsVariantWhatsApp, { value: EmojiSource.WhatsApp })
)
)
.setDMPermission(false)
.setContexts(InteractionContextType.Guild)
.setDefaultMemberPermissions(PermissionFlagsBits.ManageGuildExpressions)
)
export class UserCommand extends Command {
Expand Down Expand Up @@ -214,11 +215,7 @@ export class UserCommand extends Command {
}

private sanitizeName(name: string) {
return name
.split('.', 1)[0]
.replaceAll(/[^\d\w]/g, '_')
.padEnd(2, '_')
.slice(0, 32);
return name.split('.', 1)[0].replaceAll(/[^\w]/g, '_').padEnd(2, '_').slice(0, 32);
}

private getOptimalUrl(file: NonNullable<Options['file']>) {
Expand Down Expand Up @@ -278,7 +275,7 @@ export class UserCommand extends Command {
return image.byteLength > UserCommand.MaximumUploadSize ? err(Root.ContentLengthTooBig) : ok({ image, contentType: 'image/webp' });
}

private static readonly NameValidatorRegExp = /^[0-9a-zA-Z_]{2,32}$/;
private static readonly NameValidatorRegExp = /^\w{2,32}$/;
private static readonly MaximumUploadSize = 256 * 1024;
}

Expand Down
35 changes: 19 additions & 16 deletions src/commands/dictionary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import { Result, none, some } from '@sapphire/result';
import { Command, RegisterCommand, type MessageResponseOptions } from '@skyra/http-framework';
import { applyLocalizedBuilder, getSupportedLanguageT, type TFunction } from '@skyra/http-framework-i18n';
import { Json, isAbortError, safeTimedFetch, type FetchError } from '@skyra/safe-fetch';
import { MessageFlags } from 'discord-api-types/v10';
import { InteractionContextType, MessageFlags } from 'discord-api-types/v10';
import { readFile } from 'node:fs/promises';

const Root = LanguageKeys.Commands.Dictionary;

@RegisterCommand((builder) =>
applyLocalizedBuilder(builder, LanguageKeys.Commands.Dictionary.RootName, LanguageKeys.Commands.Dictionary.RootDescription).addStringOption(
(builder) => applyLocalizedBuilder(builder, LanguageKeys.Commands.Dictionary.OptionsInput).setMinLength(2).setMaxLength(45).setRequired(true)
)
applyLocalizedBuilder(builder, Root.RootName, Root.RootDescription) //
.setContexts(InteractionContextType.Guild, InteractionContextType.BotDM, InteractionContextType.PrivateChannel)
.addStringOption((builder) => applyLocalizedBuilder(builder, Root.OptionsInput).setMinLength(2).setMaxLength(45).setRequired(true))
)
export class UserCommand extends Command {
public override async chatInputRun(interaction: Command.ChatInputInteraction, args: Options) {
Expand All @@ -36,9 +38,9 @@ export class UserCommand extends Command {
}

private handleOk(t: TFunction, input: string, result: DictionaryAPIResult) {
let lines = [t(LanguageKeys.Commands.Dictionary.ContentTitle, { value: escapeInlineCode(result.word) })];
let lines = [t(Root.ContentTitle, { value: escapeInlineCode(result.word) })];

if (result.phonetic) lines.push(t(LanguageKeys.Commands.Dictionary.ContentPhonetic, { value: result.phonetic }));
if (result.phonetic) lines.push(t(Root.ContentPhonetic, { value: result.phonetic }));

let escapeOutOfLoop = false;
for (const [index, meaning] of result.meanings.entries()) {
Expand All @@ -51,7 +53,8 @@ export class UserCommand extends Command {
return;
}

return (lines = newLines);
lines = newLines;
return lines;
});
}

Expand All @@ -61,7 +64,7 @@ export class UserCommand extends Command {
private handleError(t: TFunction, input: string, error: FetchError | DictionaryNoResultsError) {
if (error instanceof DictionaryNoResultsError) {
return {
content: t(LanguageKeys.Commands.Dictionary.FetchNoResults, { value: escapeInlineCode(input) }),
content: t(Root.FetchNoResults, { value: escapeInlineCode(input) }),
flags: MessageFlags.Ephemeral
} satisfies MessageResponseOptions;
}
Expand All @@ -73,37 +76,37 @@ export class UserCommand extends Command {
}

private getErrorKey(error: FetchError) {
if (isAbortError(error)) return LanguageKeys.Commands.Dictionary.FetchAbort;
if (error.code === 404) return LanguageKeys.Commands.Dictionary.FetchNoResults;
if (isAbortError(error)) return Root.FetchAbort;
if (error.code === 404) return Root.FetchNoResults;
if (error.code === 429) {
this.container.logger.error('[DICTIONARYAPI] 429: Request surpassed its rate limit');
return LanguageKeys.Commands.Dictionary.FetchRateLimited;
return Root.FetchRateLimited;
}
if (error.code >= 500) {
this.container.logger.warn('[DICTIONARYAPI] %d: Received a server error', error.code);
return LanguageKeys.Commands.Dictionary.FetchServerError;
return Root.FetchServerError;
}

this.container.logger.warn('[DICTIONARYAPI] %d: Received an unknown error status code', error.code);
return LanguageKeys.Commands.Dictionary.FetchUnknownError;
return Root.FetchUnknownError;
}

private makeContent(t: TFunction, meaning: DictionaryAPIMeaning, index: number) {
const meaningParts: string[] = [];

meaningParts.push(t(LanguageKeys.Commands.Dictionary.ContentLexicalCategory, { value: meaning.partOfSpeech, index: index + 1 }));
meaningParts.push(t(Root.ContentLexicalCategory, { value: meaning.partOfSpeech, index: index + 1 }));

for (const [subIndex, definition] of meaning.definitions.entries()) {
meaningParts.push(
t(LanguageKeys.Commands.Dictionary.ContentDefinition, {
t(Root.ContentDefinition, {
value: definition.definition,
index: index + 1,
subIndex: subIndex + 1
})
);

if (definition.example) {
meaningParts.push('', t(LanguageKeys.Commands.Dictionary.ContentExample, { value: definition.example }));
meaningParts.push('', t(Root.ContentExample, { value: definition.example }));
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/commands/emoji.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ import { isNullish, isNullishOrEmpty } from '@sapphire/utilities';
import { Command, RegisterCommand, RegisterMessageCommand, type TransformedArguments } from '@skyra/http-framework';
import { applyLocalizedBuilder, applyNameLocalizedBuilder, createSelectMenuChoiceName, resolveKey, resolveUserKey } from '@skyra/http-framework-i18n';
import { safeTimedFetch } from '@skyra/safe-fetch';
import { MessageFlags } from 'discord-api-types/v10';
import { InteractionContextType, MessageFlags } from 'discord-api-types/v10';

const Root = LanguageKeys.Commands.Emoji;

@RegisterCommand((builder) =>
applyLocalizedBuilder(builder, Root.RootName, Root.RootDescription)
.setContexts(InteractionContextType.Guild, InteractionContextType.BotDM, InteractionContextType.PrivateChannel)
.addStringOption((builder) => applyLocalizedBuilder(builder, Root.OptionsEmoji).setRequired(true))
.addStringOption((builder) =>
applyLocalizedBuilder(builder, Root.OptionsVariant).setChoices(
Expand Down
Loading

0 comments on commit 07c4848

Please sign in to comment.