You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
importJoifrom'joi';importdotenvfrom'dotenv';dotenv.config();constenvListSchema=Joi.object({APP_ID: Joi.string().required().description('discord bot application id'),PUBLIC_KEY: Joi.string().required().description('discord bot public key'),CLIENT_ID: Joi.string().required().description('discord bot client id'),CLIENT_SECRET: Joi.string().required().description('discord bot client secrete credential'),GUILD_ID: Joi.string().required().description("a guild's id to run this bot"),DISCORD_TOKEN: Joi.string().required().description('discord bot application token'),DISCORD_API_VERSION: Joi.string().required().description('discord API version number as string').default('10'),PORT: Joi.number().required().default(3000),}).unknown();constvalidateEnvList=()=>{const{ error, value }=envListSchema.validate(process.env);if(error){thrownewError(`Validation error: ${error.message}`);}returnvalue;};constafterValidate=validateEnvList();exportconstenvList={APP_ID: afterValidate.APP_IDasstring,PUBLIC_KEY: afterValidate.PUBLIC_KEYasstring,CLIENT_ID: afterValidate.CLIENT_IDasstring,CLIENT_SECRET: afterValidate.CLIENT_SECRETasstring,GUILD_ID: afterValidate.GUILD_IDasstring,DISCORD_TOKEN: afterValidate.DISCORD_TOKENasstring,DISCORD_API_VERSION: afterValidate.DISCORD_API_VERSIONasstring,PORT: afterValidate.PORTasnumber,};
이런 식으로 type-safe 하게 environment variable validation 을 진행합니다.
/Users/pravda/development/personal/discord-hotdeal-scrapper-typescript/infra/env-config.ts:30
throw new Error(`Validation error: ${error.message}`);
^
Error: Validation error: "DISCORD_API_VERSION" is required
at validateEnvList (/Users/pravda/development/personal/discord-hotdeal-scrapper-typescript/infra/env-config.ts:30:15)
at Object.<anonymous> (/Users/pravda/development/personal/discord-hotdeal-scrapper-typescript/infra/env-config.ts:36:23)
at Module._compile (node:internal/modules/cjs/loader:1226:14)
at Module.m._compile (/Users/pravda/development/personal/discord-hotdeal-scrapper-typescript/node_modules/ts-node/src/index.ts:1618:23)
at Module._extensions..js (node:internal/modules/cjs/loader:1280:10)
at Object.require.extensions.<computed> [as .ts] (/Users/pravda/development/personal/discord-hotdeal-scrapper-typescript/node_modules/ts-node/src/index.ts:1621:12)
at Module.load (node:internal/modules/cjs/loader:1089:32)
at Function.Module._load (node:internal/modules/cjs/loader:930:12)
at Module.require (node:internal/modules/cjs/loader:1113:19)
at require (node:internal/modules/cjs/helpers:103:18)
[nodemon] app crashed - waiting for file changes before starting...
만약 위의 부분들을 모두 채우지 않는 경우 오류가 발생합니다.
How it works?
// app.tsimport{Client,Events,GatewayIntentBits}from'discord.js';const{ Guilds, GuildMessages }=GatewayIntentBits;import{Command,SlashCommand}from'./types';import{CommandHandler}from'./infra/discord/command-handler';import{TestDogCommand}from'./src/commands/slash/test-dog';import{envList}from'./infra/config';constslashCommandList: SlashCommand[]=[TestDogCommand];constgeneralCommandList: Command[]=[];constcommandHandler=newCommandHandler(slashCommandList,generalCommandList,newClient({intents: [Guilds,GuildMessages],}));commandHandler.enrollCommandToDiscordInfra();constclient=commandHandler.enrollCommandsToLocalClient();client.once(Events.ClientReady,(c)=>{console.log(`Ready! logged in as ${c.user.tag}`);});client.on(Events.InteractionCreate,async(interaction)=>{if(!interaction.isChatInputCommand())return;constcommand=client.slashCommands.get(interaction.commandName);if(!command){console.error(`No command matching ${interaction.commandName} was found`);}try{awaitcommand.execute(interaction);}catch(e){console.error(e);awaitinteraction.reply({content: `An error is occurred!`,});}});client.login(envList.DISCORD_TOKEN);
local 에 존재하는 discord client 엔 commands 와 slashCommands 라는 Collection<string, *Command> 형태의 key-value storage 를 통해 등록한 명령어를 불러올 수 있도록 저장합니다.
instance 생성 시 주입해주었던 *Command[] type 의 의존성들을 forEach 로 순회를 하며 client 에 등록한 뒤, 등록이 완료된 해당 client 를 결과값으로 반환합니다.
client.on(Events.InteractionCreate,async(interaction)=>{if(!interaction.isChatInputCommand())return;constcommand=client.slashCommands.get(interaction.commandName);if(!command){console.error(`No command matching ${interaction.commandName} was found`);}try{awaitcommand.execute(interaction);}catch(e){console.error(e);awaitinteraction.reply({content: `An error is occurred!`,});}});
이런 식으로 명령어 등록이 완료된 후엔, client 가 InteractionCreate라는 event 를 감지한 경우 event emitter 방식으로 해당 command 들과 그 처리방법을 자기가 가지고 있는 interaction handler 에서 찾아 처리합니다.
import{SlashCommandBuilder}from'discord.js';import{SlashCommand}from'../../../types';exportconstTestDogCommand: SlashCommand={command: newSlashCommandBuilder().setName('테스트').setDescription(`귀여운 강아지 사진을 출력합니다.`),execute: async(interaction)=>{awaitinteraction.reply('https://imgur.com/a/db2ZEyp');},};