mise au bonne endroit des type

This commit is contained in:
Puechberty Arthur
2026-04-13 20:23:49 +02:00
parent 3eb23eb27a
commit 7ff76cdaba
34 changed files with 298 additions and 236 deletions
+1 -1
View File
@@ -11,7 +11,7 @@ import { EmbedBuilder } from "discord.js";
import { buildPrefixUsage, buildSlashUsage, resolvePrefixTrigger, resolveSlashName } from "../framework/commands/usage.js"; import { buildPrefixUsage, buildSlashUsage, resolvePrefixTrigger, resolveSlashName } from "../framework/commands/usage.js";
import { defineCommand } from "../framework/commands/defineCommand.js"; import { defineCommand } from "../framework/commands/defineCommand.js";
import type { BotCommand, CommandExecutionContext } from "../framework/types/command.js"; import type { BotCommand, CommandExecutionContext } from "../types/command.js";
const categoryName = (command: BotCommand): string => command.meta.category; const categoryName = (command: BotCommand): string => command.meta.category;
+1 -1
View File
@@ -11,7 +11,7 @@ import { presenceCommand } from "./presence.js";
import { pingCommand } from "./ping.js"; import { pingCommand } from "./ping.js";
import { welcomeCommand } from "./welcome.js"; import { welcomeCommand } from "./welcome.js";
import type { BotCommand } from "../framework/types/command.js"; import type { BotCommand } from "../types/command.js";
/** CommandList: tableau ordonné des commandes disponibles. */ /** CommandList: tableau ordonné des commandes disponibles. */
export const commandList: BotCommand[] = [ export const commandList: BotCommand[] = [
+9 -22
View File
@@ -29,32 +29,19 @@ import { getMemberMessageStore } from "../framework/memberMessages/memberMessage
import { import {
MEMBER_MESSAGE_RENDER_TYPES, MEMBER_MESSAGE_RENDER_TYPES,
isMemberMessageRenderTypeValue, isMemberMessageRenderTypeValue,
type MemberMessageConfig,
type MemberMessageKind,
type MemberMessageRenderType,
} from "../framework/memberMessages/memberMessageTypes.js"; } from "../framework/memberMessages/memberMessageTypes.js";
import type { CommandExecutionContext } from "../framework/types/command.js"; import type { CommandExecutionContext } from "../types/command.js";
import type {
MemberMessageConfig,
MemberMessageCustomIds,
MemberMessageKind,
MemberMessagePanelSession,
MemberMessagePanelUiState,
MemberMessageRenderType,
} from "../types/memberMessages.js";
const memberMessageI18n = new I18nService(env.DEFAULT_LANG); const memberMessageI18n = new I18nService(env.DEFAULT_LANG);
interface MemberMessageCustomIds {
toggleButton: string;
channelButton: string;
channelCancelButton: string;
typeSelect: string;
channelSelect: string;
testButton: string;
}
interface MemberMessagePanelUiState {
channelPickerOpen: boolean;
}
interface MemberMessagePanelSession {
collector: ReturnType<Message["createMessageComponentCollector"]>;
disable: () => Promise<void>;
}
const activePanelsByUser = new Map<string, MemberMessagePanelSession>(); const activePanelsByUser = new Map<string, MemberMessagePanelSession>();
const panelSessionKey = (kind: MemberMessageKind, guildId: string, userId: string): string => { const panelSessionKey = (kind: MemberMessageKind, guildId: string, userId: string): string => {
+10 -29
View File
@@ -23,6 +23,15 @@ import {
} from "discord.js"; } from "discord.js";
import { defineCommand } from "../framework/commands/defineCommand.js"; import { defineCommand } from "../framework/commands/defineCommand.js";
import { env } from "../framework/config/env.js"; import { env } from "../framework/config/env.js";
import type {
DiscordPresenceStatus,
PresenceActivityTypeValue,
PresenceCustomIds,
PresencePanelSession,
PresenceRuntimeState,
PresenceState,
PresenceStatusValue,
} from "../types/presence.js";
import { getPresenceStore } from "../framework/presence/presenceStore.js"; import { getPresenceStore } from "../framework/presence/presenceStore.js";
import { import {
PRESENCE_TEMPLATE_REFRESH_INTERVAL_MS, PRESENCE_TEMPLATE_REFRESH_INTERVAL_MS,
@@ -42,34 +51,8 @@ import {
sanitizeActivityText, sanitizeActivityText,
sanitizeActivityTexts, sanitizeActivityTexts,
sanitizePresenceRotationIntervalSeconds, sanitizePresenceRotationIntervalSeconds,
type PresenceActivityTypeValue,
type PresenceState,
type PresenceStatusValue,
} from "../framework/presence/presenceTypes.js"; } from "../framework/presence/presenceTypes.js";
import type { CommandExecutionContext } from "../framework/types/command.js"; import type { CommandExecutionContext } from "../types/command.js";
interface PresenceCustomIds {
statusSelect: string;
activitySelect: string;
textButton: string;
intervalButton: string;
textModal: string;
textInput: string;
intervalModal: string;
intervalInput: string;
}
interface PresencePanelSession {
collector: ReturnType<Message["createMessageComponentCollector"]>;
disable: () => Promise<void>;
}
interface PresenceRuntimeState {
dynamicPresenceRefreshTimer: NodeJS.Timeout | null;
presenceRotationTimer: NodeJS.Timeout | null;
activePresenceTextIndex: number;
activePanelsByUserId: Map<string, PresencePanelSession>;
}
const presenceRuntimeByBotId = new Map<string, PresenceRuntimeState>(); const presenceRuntimeByBotId = new Map<string, PresenceRuntimeState>();
@@ -129,8 +112,6 @@ const DISCORD_ACTIVITY_TYPES: Record<PresenceActivityTypeValue, ActivityType> =
CUSTOM: ActivityType.Custom, CUSTOM: ActivityType.Custom,
}; };
type DiscordPresenceStatus = "online" | "idle" | "dnd" | "invisible";
const resolveDiscordStatus = (status: PresenceStatusValue): DiscordPresenceStatus => const resolveDiscordStatus = (status: PresenceStatusValue): DiscordPresenceStatus =>
status === "streaming" ? "online" : status; status === "streaming" ? "online" : status;
+2 -11
View File
@@ -1,16 +1,7 @@
import type { ChatInputCommandInteraction, GuildBasedChannel, Message } from "discord.js"; import type { ChatInputCommandInteraction, GuildBasedChannel, Message } from "discord.js";
import type { CommandArgValue, CommandArgument, TranslationVars } from "../types/command.js"; import type { ArgumentParseError, ParsedArgumentsResult } from "../../types/argParser.js";
import type { CommandArgValue, CommandArgument } from "../../types/command.js";
export interface ArgumentParseError {
key: string;
vars?: TranslationVars;
}
export interface ParsedArgumentsResult {
values: Record<string, CommandArgValue>;
errors: ArgumentParseError[];
}
const USER_MENTION_PATTERN = /^<@!?(\d{16,22})>$/; const USER_MENTION_PATTERN = /^<@!?(\d{16,22})>$/;
const CHANNEL_MENTION_PATTERN = /^<#(\d{16,22})>$/; const CHANNEL_MENTION_PATTERN = /^<#(\d{16,22})>$/;
+1 -1
View File
@@ -1,4 +1,4 @@
import type { BotCommand, BotCommandInput } from "../types/command.js"; import type { BotCommand, BotCommandInput } from "../../types/command.js";
const assertRequiredArgsBeforeOptional = (input: BotCommandInput): void => { const assertRequiredArgsBeforeOptional = (input: BotCommandInput): void => {
const args = input.args ?? []; const args = input.args ?? [];
+1 -15
View File
@@ -1,21 +1,7 @@
import { REST, Routes } from "discord.js"; import { REST, Routes } from "discord.js";
import { buildSlashPayload } from "./slashBuilder.js"; import { buildSlashPayload } from "./slashBuilder.js";
import type { CommandRegistry } from "./registry.js"; import type { DeployCommandsOptions, DeployCommandsResult } from "../../types/deploy.js";
import type { I18nService } from "../../i18n/index.js";
export interface DeployCommandsOptions {
token: string;
clientId: string;
guildId?: string;
registry: CommandRegistry;
i18n: I18nService;
}
export interface DeployCommandsResult {
scope: "guild" | "global";
count: number;
}
export const deployApplicationCommands = async (options: DeployCommandsOptions): Promise<DeployCommandsResult> => { export const deployApplicationCommands = async (options: DeployCommandsOptions): Promise<DeployCommandsResult> => {
const body = buildSlashPayload(options.registry.getAll(), options.i18n); const body = buildSlashPayload(options.registry.getAll(), options.i18n);
+2 -2
View File
@@ -1,5 +1,5 @@
import type { BotCommand, PrefixTriggerMatch } from "../types/command.js"; import type { BotCommand, PrefixTriggerMatch } from "../../types/command.js";
import { SUPPORTED_LANGS } from "../types/command.js"; import { SUPPORTED_LANGS } from "../../types/command.js";
import type { I18nService } from "../../i18n/index.js"; import type { I18nService } from "../../i18n/index.js";
const normalize = (value: string): string => value.trim().toLowerCase(); const normalize = (value: string): string => value.trim().toLowerCase();
+1 -1
View File
@@ -4,7 +4,7 @@ import {
type RESTPostAPIChatInputApplicationCommandsJSONBody, type RESTPostAPIChatInputApplicationCommandsJSONBody,
} from "discord.js"; } from "discord.js";
import type { BotCommand, CommandArgument, SupportedLang } from "../types/command.js"; import type { BotCommand, CommandArgument, SupportedLang } from "../../types/command.js";
import type { I18nService } from "../../i18n/index.js"; import type { I18nService } from "../../i18n/index.js";
const LANG_TO_DISCORD_LOCALE: Record<SupportedLang, string> = { const LANG_TO_DISCORD_LOCALE: Record<SupportedLang, string> = {
+1 -1
View File
@@ -1,4 +1,4 @@
import type { BotCommand, CommandI18nTools, SupportedLang } from "../types/command.js"; import type { BotCommand, CommandI18nTools, SupportedLang } from "../../types/command.js";
const formatArgToken = (name: string, required: boolean): string => required ? `<${name}>` : `[${name}]`; const formatArgToken = (name: string, required: boolean): string => required ? `<${name}>` : `[${name}]`;
+1 -1
View File
@@ -1,7 +1,7 @@
import { config as loadEnv } from "dotenv"; import { config as loadEnv } from "dotenv";
import { z } from "zod"; import { z } from "zod";
import { SUPPORTED_LANGS } from "../types/command.js"; import { SUPPORTED_LANGS } from "../../types/command.js";
loadEnv(); loadEnv();
+1 -1
View File
@@ -5,7 +5,7 @@ import {
type PermissionResolvable, type PermissionResolvable,
} from "discord.js"; } from "discord.js";
import type { BotCommand, CommandExecutionContext } from "../types/command.js"; import type { BotCommand, CommandExecutionContext } from "../../types/command.js";
const COOLDOWN_SWEEP_INTERVAL_MS = 60_000; const COOLDOWN_SWEEP_INTERVAL_MS = 60_000;
const COOLDOWN_SWEEP_MIN_ENTRIES = 512; const COOLDOWN_SWEEP_MIN_ENTRIES = 512;
@@ -1,14 +1,6 @@
import { createCanvas, loadImage, type SKRSContext2D } from "@napi-rs/canvas"; import { createCanvas, loadImage, type SKRSContext2D } from "@napi-rs/canvas";
import type { MemberMessageKind } from "./memberMessageTypes.js"; import type { MemberMessageImageInput } from "../../types/memberMessages.js";
interface MemberMessageImageInput {
kind: MemberMessageKind;
title: string;
subtitle: string;
username: string;
avatarUrl: string;
}
const CARD_WIDTH = 1024; const CARD_WIDTH = 1024;
const CARD_HEIGHT = 320; const CARD_HEIGHT = 320;
@@ -5,48 +5,27 @@ import {
MessageFlags, MessageFlags,
PermissionFlagsBits, PermissionFlagsBits,
TextDisplayBuilder, TextDisplayBuilder,
type Client,
type Guild, type Guild,
type MessageCreateOptions, type MessageCreateOptions,
type User, type User,
} from "discord.js"; } from "discord.js";
import type { I18nService } from "../../i18n/index.js"; import type { I18nService } from "../../i18n/index.js";
import type { SupportedLang } from "../types/command.js"; import type { SupportedLang } from "../../types/command.js";
import type {
DispatchMemberMessageInput,
DispatchMemberMessageResult,
MemberMessageKind,
MemberMessageRenderType,
SendableChannel,
TemplateSuffix,
} from "../../types/memberMessages.js";
import { renderMemberMessageImage } from "./memberMessageImage.js"; import { renderMemberMessageImage } from "./memberMessageImage.js";
import { getMemberMessageStore } from "./memberMessageStore.js"; import { getMemberMessageStore } from "./memberMessageStore.js";
import { export type {
type MemberMessageKind, DispatchMemberMessageFailureReason,
type MemberMessageRenderType, DispatchMemberMessageResult,
} from "./memberMessageTypes.js"; } from "../../types/memberMessages.js";
export type DispatchMemberMessageFailureReason =
| "bot_not_ready"
| "disabled"
| "missing_channel"
| "channel_not_found"
| "channel_not_sendable"
| "missing_permissions"
| "send_failed";
export interface DispatchMemberMessageResult {
sent: boolean;
reason: DispatchMemberMessageFailureReason | "sent";
channelId: string | null;
}
interface DispatchMemberMessageInput {
client: Client;
i18n?: I18nService;
guild: Guild;
user: User;
kind: MemberMessageKind;
ignoreEnabled?: boolean;
}
interface SendableChannel {
send: (payload: string | MessageCreateOptions) => Promise<unknown>;
}
const hasSendMethod = (value: unknown): value is SendableChannel => { const hasSendMethod = (value: unknown): value is SendableChannel => {
if (!value || typeof value !== "object") { if (!value || typeof value !== "object") {
@@ -74,15 +53,6 @@ const messageColor = (kind: MemberMessageKind): number => {
return kind === "welcome" ? 0x57f287 : 0xed4245; return kind === "welcome" ? 0x57f287 : 0xed4245;
}; };
type TemplateSuffix =
| "simple"
| "embedTitle"
| "embedDescription"
| "containerTitle"
| "containerDescription"
| "imageTitle"
| "imageDescription";
const defaultTemplate = ( const defaultTemplate = (
kind: MemberMessageKind, kind: MemberMessageKind,
suffix: TemplateSuffix, suffix: TemplateSuffix,
@@ -1,19 +1,16 @@
import { Pool } from "pg"; import { Pool } from "pg";
import { env } from "../config/env.js"; import { env } from "../config/env.js";
import type {
MemberMessageConfig,
MemberMessageKind,
MemberMessageRow,
} from "../../types/memberMessages.js";
import { import {
createDefaultMemberMessageConfig, createDefaultMemberMessageConfig,
isMemberMessageRenderTypeValue, isMemberMessageRenderTypeValue,
type MemberMessageConfig,
type MemberMessageKind,
} from "./memberMessageTypes.js"; } from "./memberMessageTypes.js";
interface MemberMessageRow {
enabled: boolean;
channel_id: string | null;
message_type: string;
}
const tableSql = ` const tableSql = `
CREATE TABLE IF NOT EXISTS bot_member_message_configs ( CREATE TABLE IF NOT EXISTS bot_member_message_configs (
bot_id TEXT NOT NULL, bot_id TEXT NOT NULL,
@@ -1,17 +1,17 @@
export const MEMBER_MESSAGE_KINDS = ["welcome", "goodbye"] as const; import type {
export type MemberMessageKind = (typeof MEMBER_MESSAGE_KINDS)[number]; MemberMessageConfig,
MemberMessageKind,
MemberMessageRenderType,
} from "../../types/memberMessages.js";
export const MEMBER_MESSAGE_RENDER_TYPES = ["simple", "embed", "container", "image"] as const; export type { MemberMessageConfig, MemberMessageKind, MemberMessageRenderType } from "../../types/memberMessages.js";
export type MemberMessageRenderType = (typeof MEMBER_MESSAGE_RENDER_TYPES)[number];
export const MEMBER_MESSAGE_KINDS: readonly MemberMessageKind[] = ["welcome", "goodbye"];
export const MEMBER_MESSAGE_RENDER_TYPES: readonly MemberMessageRenderType[] = ["simple", "embed", "container", "image"];
export const DEFAULT_MEMBER_MESSAGE_RENDER_TYPE: MemberMessageRenderType = "simple"; export const DEFAULT_MEMBER_MESSAGE_RENDER_TYPE: MemberMessageRenderType = "simple";
export interface MemberMessageConfig {
enabled: boolean;
channelId: string | null;
messageType: MemberMessageRenderType;
}
export const createDefaultMemberMessageConfig = (): MemberMessageConfig => ({ export const createDefaultMemberMessageConfig = (): MemberMessageConfig => ({
enabled: false, enabled: false,
channelId: null, channelId: null,
+6 -11
View File
@@ -1,6 +1,12 @@
import { Pool } from "pg"; import { Pool } from "pg";
import { env } from "../config/env.js"; import { env } from "../config/env.js";
import type {
PresenceActivityTypeValue,
PresenceRow,
PresenceState,
PresenceStatusValue,
} from "../../types/presence.js";
import { import {
DEFAULT_ACTIVITY_ROTATION_INTERVAL_SECONDS, DEFAULT_ACTIVITY_ROTATION_INTERVAL_SECONDS,
createDefaultPresenceState, createDefaultPresenceState,
@@ -9,19 +15,8 @@ import {
sanitizeActivityText, sanitizeActivityText,
sanitizeActivityTexts, sanitizeActivityTexts,
sanitizePresenceRotationIntervalSeconds, sanitizePresenceRotationIntervalSeconds,
type PresenceActivityTypeValue,
type PresenceState,
type PresenceStatusValue,
} from "./presenceTypes.js"; } from "./presenceTypes.js";
interface PresenceRow {
status: string;
activity_type: string;
activity_text: string;
activity_texts: string | null;
rotation_interval_seconds: number | null;
}
const tableSql = ` const tableSql = `
CREATE TABLE IF NOT EXISTS bot_presence_states ( CREATE TABLE IF NOT EXISTS bot_presence_states (
bot_id TEXT PRIMARY KEY, bot_id TEXT PRIMARY KEY,
+11 -18
View File
@@ -1,15 +1,18 @@
export const PRESENCE_STATUSES = ["online", "idle", "dnd", "invisible", "streaming"] as const; import type {
export const PRESENCE_ACTIVITY_TYPES = [ PresenceActivityTypeValue,
PresenceState,
PresenceStatusValue,
} from "../../types/presence.js";
export const PRESENCE_STATUSES: readonly PresenceStatusValue[] = ["online", "idle", "dnd", "invisible", "streaming"];
export const PRESENCE_ACTIVITY_TYPES: readonly PresenceActivityTypeValue[] = [
"PLAYING", "PLAYING",
"STREAMING", "STREAMING",
"WATCHING", "WATCHING",
"LISTENING", "LISTENING",
"COMPETING", "COMPETING",
"CUSTOM", "CUSTOM",
] as const; ];
export type PresenceStatusValue = (typeof PRESENCE_STATUSES)[number];
export type PresenceActivityTypeValue = (typeof PRESENCE_ACTIVITY_TYPES)[number];
export const DEFAULT_ACTIVITY_TEXT = "Ready to help"; export const DEFAULT_ACTIVITY_TEXT = "Ready to help";
export const DEFAULT_ACTIVITY_ROTATION_INTERVAL_SECONDS = 30; export const DEFAULT_ACTIVITY_ROTATION_INTERVAL_SECONDS = 30;
@@ -17,16 +20,6 @@ export const MIN_ACTIVITY_ROTATION_INTERVAL_SECONDS = 5;
export const MAX_ACTIVITY_ROTATION_INTERVAL_SECONDS = 3_600; export const MAX_ACTIVITY_ROTATION_INTERVAL_SECONDS = 3_600;
export const MAX_ACTIVITY_ROTATION_TEXTS = 10; export const MAX_ACTIVITY_ROTATION_TEXTS = 10;
export interface PresenceState {
status: PresenceStatusValue;
activity: {
type: PresenceActivityTypeValue;
text: string;
texts: string[];
rotationIntervalSeconds: number;
};
}
export const createDefaultPresenceState = (): PresenceState => ({ export const createDefaultPresenceState = (): PresenceState => ({
status: "online", status: "online",
activity: { activity: {
@@ -38,10 +31,10 @@ export const createDefaultPresenceState = (): PresenceState => ({
}); });
export const isPresenceStatusValue = (value: string): value is PresenceStatusValue => export const isPresenceStatusValue = (value: string): value is PresenceStatusValue =>
(PRESENCE_STATUSES as readonly string[]).includes(value); PRESENCE_STATUSES.includes(value as PresenceStatusValue);
export const isPresenceActivityTypeValue = (value: string): value is PresenceActivityTypeValue => export const isPresenceActivityTypeValue = (value: string): value is PresenceActivityTypeValue =>
(PRESENCE_ACTIVITY_TYPES as readonly string[]).includes(value); PRESENCE_ACTIVITY_TYPES.includes(value as PresenceActivityTypeValue);
export const sanitizeActivityText = (value: string): string => { export const sanitizeActivityText = (value: string): string => {
const trimmed = value.trim(); const trimmed = value.trim();
+1 -4
View File
@@ -1,7 +1,4 @@
export interface TemplateRenderOptions { import type { TemplateRenderOptions } from "../../types/templateVariables.js";
aliases?: Record<string, string>;
keepUnknown?: boolean;
}
const createTemplateTokenRegex = (): RegExp => /\{\{([a-zA-Z0-9_]+)\}\}/g; const createTemplateTokenRegex = (): RegExp => /\{\{([a-zA-Z0-9_]+)\}\}/g;
@@ -1,32 +1,10 @@
import type { CommandRegistry } from "../commands/registry.js";
import type { I18nService } from "../../i18n/index.js";
import type { import type {
BotCommand,
CommandExecutionContext, CommandExecutionContext,
CommandSource,
SupportedLang, SupportedLang,
TranslationVars, TranslationVars,
} from "../types/command.js"; } from "../types/command.js";
import type { BuildExecutionContextInput, HandlerExecutionDeps } from "../types/handlers.js";
export interface HandlerExecutionDeps { import type { I18nService } from "../i18n/index.js";
registry: CommandRegistry;
i18n: I18nService;
prefix: string;
defaultLang: SupportedLang;
}
interface BuildExecutionContextInput {
command: BotCommand;
source: CommandSource;
lang: SupportedLang;
args: CommandExecutionContext["args"];
client: CommandExecutionContext["client"];
user: CommandExecutionContext["user"];
guild: CommandExecutionContext["guild"];
channel: CommandExecutionContext["channel"];
raw: CommandExecutionContext["raw"];
reply: CommandExecutionContext["reply"];
}
export const createTranslator = ( export const createTranslator = (
i18n: I18nService, i18n: I18nService,
@@ -1,20 +1,15 @@
import type { Message } from "discord.js"; import type { Message } from "discord.js";
import type { BotCommand, SupportedLang } from "../types/command.js"; import type { BotCommand, SupportedLang } from "../types/command.js";
import type { PrefixHandlerDeps } from "../types/handlers.js";
import { parsePrefixArgs } from "../commands/argParser.js"; import { parsePrefixArgs } from "../framework/commands/argParser.js";
import { buildPrefixUsage } from "../commands/usage.js"; import { buildPrefixUsage } from "../framework/commands/usage.js";
import { CommandExecutor } from "../execution/CommandExecutor.js";
import { import {
buildCommandExecutionContext, buildCommandExecutionContext,
createTranslator, createTranslator,
type HandlerExecutionDeps,
} from "./commandExecutionContext.js"; } from "./commandExecutionContext.js";
import { createPrefixReply } from "./replyAdapter.js"; import { createPrefixReply } from "./replyAdapter.js";
interface PrefixHandlerDeps extends HandlerExecutionDeps {
executor: CommandExecutor;
}
const resolvePrefixLang = ( const resolvePrefixLang = (
deps: PrefixHandlerDeps, deps: PrefixHandlerDeps,
command: BotCommand, command: BotCommand,
@@ -6,6 +6,7 @@ import type {
} from "discord.js"; } from "discord.js";
import type { ReplyPayload } from "../types/command.js"; import type { ReplyPayload } from "../types/command.js";
import type { PrefixReplyObject } from "../types/reply.js";
const PREFIX_EPHEMERAL_DELETE_DELAY_MS = 10_000; const PREFIX_EPHEMERAL_DELETE_DELAY_MS = 10_000;
@@ -18,8 +19,6 @@ const SLASH_ALLOWED_MENTIONS_DEFAULT: NonNullable<InteractionReplyOptions["allow
parse: [], parse: [],
}; };
type PrefixReplyObject = Exclude<ReplyPayload, string>;
const EPHEMERAL_FLAG = 64n; const EPHEMERAL_FLAG = 64n;
const FLAG_NAME_TO_BITS: Record<string, bigint> = { const FLAG_NAME_TO_BITS: Record<string, bigint> = {
Ephemeral: 64n, Ephemeral: 64n,
@@ -1,19 +1,14 @@
import { MessageFlags, type ChatInputCommandInteraction } from "discord.js"; import { MessageFlags, type ChatInputCommandInteraction } from "discord.js";
import type { SlashHandlerDeps } from "../types/handlers.js";
import { parseSlashArgs } from "../commands/argParser.js"; import { parseSlashArgs } from "../framework/commands/argParser.js";
import { buildSlashUsage } from "../commands/usage.js"; import { buildSlashUsage } from "../framework/commands/usage.js";
import { CommandExecutor } from "../execution/CommandExecutor.js";
import { import {
buildCommandExecutionContext, buildCommandExecutionContext,
createTranslator, createTranslator,
type HandlerExecutionDeps,
} from "./commandExecutionContext.js"; } from "./commandExecutionContext.js";
import { createSlashReply } from "./replyAdapter.js"; import { createSlashReply } from "./replyAdapter.js";
interface SlashHandlerDeps extends HandlerExecutionDeps {
executor: CommandExecutor;
}
export const createSlashHandler = (deps: SlashHandlerDeps) => { export const createSlashHandler = (deps: SlashHandlerDeps) => {
return async (interaction: ChatInputCommandInteraction): Promise<void> => { return async (interaction: ChatInputCommandInteraction): Promise<void> => {
const command = deps.registry.findBySlashTrigger(interaction.commandName); const command = deps.registry.findBySlashTrigger(interaction.commandName);
+2 -3
View File
@@ -2,9 +2,8 @@ import { existsSync, readFileSync } from "node:fs";
import path from "node:path"; import path from "node:path";
import { fileURLToPath } from "node:url"; import { fileURLToPath } from "node:url";
import { SUPPORTED_LANGS, type SupportedLang, type TranslationVars } from "../framework/types/command.js"; import { SUPPORTED_LANGS, type SupportedLang, type TranslationVars } from "../types/command.js";
import type { JsonObject } from "../types/i18n.js";
type JsonObject = Record<string, unknown>;
const DISCORD_LOCALE_MAP: Record<string, SupportedLang> = { const DISCORD_LOCALE_MAP: Record<string, SupportedLang> = {
en: "en", en: "en",
+2 -2
View File
@@ -16,8 +16,8 @@ import { registerEvents } from "./events/index.js";
import { CommandRegistry } from "./framework/commands/registry.js"; import { CommandRegistry } from "./framework/commands/registry.js";
import { env } from "./framework/config/env.js"; import { env } from "./framework/config/env.js";
import { CommandExecutor } from "./framework/execution/CommandExecutor.js"; import { CommandExecutor } from "./framework/execution/CommandExecutor.js";
import { createPrefixHandler } from "./framework/handlers/prefixHandler.js"; import { createPrefixHandler } from "./handlers/prefixHandler.js";
import { createSlashHandler } from "./framework/handlers/slashHandler.js"; import { createSlashHandler } from "./handlers/slashHandler.js";
import { I18nService } from "./i18n/index.js"; import { I18nService } from "./i18n/index.js";
import { import {
initMemberMessageStore, initMemberMessageStore,
+11
View File
@@ -0,0 +1,11 @@
import type { CommandArgValue, TranslationVars } from "./command.js";
export interface ArgumentParseError {
key: string;
vars?: TranslationVars;
}
export interface ParsedArgumentsResult {
values: Record<string, CommandArgValue>;
errors: ArgumentParseError[];
}
+15
View File
@@ -0,0 +1,15 @@
import type { CommandRegistry } from "../framework/commands/registry.js";
import type { I18nService } from "../i18n/index.js";
export interface DeployCommandsOptions {
token: string;
clientId: string;
guildId?: string;
registry: CommandRegistry;
i18n: I18nService;
}
export interface DeployCommandsResult {
scope: "guild" | "global";
count: number;
}
+37
View File
@@ -0,0 +1,37 @@
import type { CommandRegistry } from "../framework/commands/registry.js";
import type { CommandExecutor } from "../framework/execution/CommandExecutor.js";
import type { I18nService } from "../i18n/index.js";
import type {
BotCommand,
CommandExecutionContext,
CommandSource,
SupportedLang,
} from "./command.js";
export interface HandlerExecutionDeps {
registry: CommandRegistry;
i18n: I18nService;
prefix: string;
defaultLang: SupportedLang;
}
export interface BuildExecutionContextInput {
command: BotCommand;
source: CommandSource;
lang: SupportedLang;
args: CommandExecutionContext["args"];
client: CommandExecutionContext["client"];
user: CommandExecutionContext["user"];
guild: CommandExecutionContext["guild"];
channel: CommandExecutionContext["channel"];
raw: CommandExecutionContext["raw"];
reply: CommandExecutionContext["reply"];
}
export interface PrefixHandlerDeps extends HandlerExecutionDeps {
executor: CommandExecutor;
}
export interface SlashHandlerDeps extends HandlerExecutionDeps {
executor: CommandExecutor;
}
+1
View File
@@ -0,0 +1 @@
export type JsonObject = Record<string, unknown>;
+87
View File
@@ -0,0 +1,87 @@
import type {
Client,
Guild,
Message,
MessageCreateOptions,
User,
} from "discord.js";
import type { I18nService } from "../i18n/index.js";
export type MemberMessageKind = "welcome" | "goodbye";
export type MemberMessageRenderType = "simple" | "embed" | "container" | "image";
export interface MemberMessageConfig {
enabled: boolean;
channelId: string | null;
messageType: MemberMessageRenderType;
}
export type DispatchMemberMessageFailureReason =
| "bot_not_ready"
| "disabled"
| "missing_channel"
| "channel_not_found"
| "channel_not_sendable"
| "missing_permissions"
| "send_failed";
export interface DispatchMemberMessageResult {
sent: boolean;
reason: DispatchMemberMessageFailureReason | "sent";
channelId: string | null;
}
export interface DispatchMemberMessageInput {
client: Client;
i18n?: I18nService;
guild: Guild;
user: User;
kind: MemberMessageKind;
ignoreEnabled?: boolean;
}
export interface SendableChannel {
send: (payload: string | MessageCreateOptions) => Promise<unknown>;
}
export type TemplateSuffix =
| "simple"
| "embedTitle"
| "embedDescription"
| "containerTitle"
| "containerDescription"
| "imageTitle"
| "imageDescription";
export interface MemberMessageImageInput {
kind: MemberMessageKind;
title: string;
subtitle: string;
username: string;
avatarUrl: string;
}
export interface MemberMessageRow {
enabled: boolean;
channel_id: string | null;
message_type: string;
}
export interface MemberMessageCustomIds {
toggleButton: string;
channelButton: string;
channelCancelButton: string;
typeSelect: string;
channelSelect: string;
testButton: string;
}
export interface MemberMessagePanelUiState {
channelPickerOpen: boolean;
}
export interface MemberMessagePanelSession {
collector: ReturnType<Message["createMessageComponentCollector"]>;
disable: () => Promise<void>;
}
+49
View File
@@ -0,0 +1,49 @@
import type { Message } from "discord.js";
export type PresenceStatusValue = "online" | "idle" | "dnd" | "invisible" | "streaming";
export type PresenceActivityTypeValue = "PLAYING" | "STREAMING" | "WATCHING" | "LISTENING" | "COMPETING" | "CUSTOM";
export interface PresenceActivityState {
type: PresenceActivityTypeValue;
text: string;
texts: string[];
rotationIntervalSeconds: number;
}
export interface PresenceState {
status: PresenceStatusValue;
activity: PresenceActivityState;
}
export interface PresenceRow {
status: string;
activity_type: string;
activity_text: string;
activity_texts: string | null;
rotation_interval_seconds: number | null;
}
export interface PresenceCustomIds {
statusSelect: string;
activitySelect: string;
textButton: string;
intervalButton: string;
textModal: string;
textInput: string;
intervalModal: string;
intervalInput: string;
}
export interface PresencePanelSession {
collector: ReturnType<Message["createMessageComponentCollector"]>;
disable: () => Promise<void>;
}
export interface PresenceRuntimeState {
dynamicPresenceRefreshTimer: NodeJS.Timeout | null;
presenceRotationTimer: NodeJS.Timeout | null;
activePresenceTextIndex: number;
activePanelsByUserId: Map<string, PresencePanelSession>;
}
export type DiscordPresenceStatus = "online" | "idle" | "dnd" | "invisible";
+3
View File
@@ -0,0 +1,3 @@
import type { ReplyPayload } from "./command.js";
export type PrefixReplyObject = Exclude<ReplyPayload, string>;
+4
View File
@@ -0,0 +1,4 @@
export interface TemplateRenderOptions {
aliases?: Record<string, string>;
keepUnknown?: boolean;
}