From ee007f5737a01ea7ccab114f745efb30ab1d43cb Mon Sep 17 00:00:00 2001 From: space2lim Date: Wed, 29 Jan 2025 00:28:01 +0900 Subject: [PATCH] Revision whole project structure --- app.js | 201 ++-------------------------------- app_backup.js | 205 +++++++++++++++++++++++++++++++++++ commands/color.js | 26 +++++ commands/ping.js | 7 ++ config/cronJobs.js | 30 +++++ events/interactionCreate.js | 16 +++ events/messageCreate.js | 12 ++ events/messageReactionAdd.js | 41 +++++++ events/ready.js | 7 ++ feature/utility.js | 1 - 10 files changed, 356 insertions(+), 190 deletions(-) create mode 100644 app_backup.js create mode 100644 commands/color.js create mode 100644 commands/ping.js create mode 100644 config/cronJobs.js create mode 100644 events/interactionCreate.js create mode 100644 events/messageCreate.js create mode 100644 events/messageReactionAdd.js create mode 100644 events/ready.js diff --git a/app.js b/app.js index c47726f..f07ebe6 100644 --- a/app.js +++ b/app.js @@ -1,48 +1,13 @@ import dotenv from 'dotenv'; -import * as util from './feature/utility.js'; -import { REST, Routes } from 'discord.js'; import { Client, GatewayIntentBits } from 'discord.js'; -import { EmbedBuilder } from 'discord.js'; -import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js'; -import { CronJob } from 'cron'; - +import readyEvent from './events/ready.js'; +import interactionCreateEvent from './events/interactionCreate.js'; +import messageCreateEvent from './events/messageCreate.js'; +import messageReactionAddEvent from './events/messageReactionAdd.js'; dotenv.config(); + const TOKEN = process.env.TOKEN; -const CLIENT_ID = process.env.CLI_ID; - -const commands = [ - { - name: 'ping', - description: 'Pong! 으로 대답합니다.', - }, - { - name: 'color', - description: '아이디 색상을 변경합니다.', - }, -]; - -// const rest = new REST({ version: '10' }).setToken(TOKEN); -// try { -// console.log('Started refreshing application (/) commands.'); -// await rest.put(Routes.applicationCommands(CLIENT_ID), { body: commands }); -// console.log('Successfully reloaded application (/) commands.'); -// } catch (error) { -// console.error(error); -// } - -const roles = { - '❤️': '1221079597962104872', - '🧡': '1221091257665720360', - '💛': '1221091363475423374', - '💚': '1221080091371765812', - '🩵': '1221091149796610088', - '💙': '1221080047469858826', - '💜': '1221095844288270387', - '🔁': 'reset', -}; -let botMessageId = ''; - const client = new Client({ intents: [ @@ -50,156 +15,14 @@ const client = new Client({ GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent, GatewayIntentBits.GuildMessageReactions, - GatewayIntentBits.GuildMembers, // 역할을 부여하기 위해 필요 - ] -}); - - -client.on('ready', () => { - console.log(`Logged in as ${client.user.tag}!`); - - // 서머타임 여부에 따라 시간 설정 - console.log(util.isDaylightSavingTime() ? '서머타임 적용 중' : '서머타임 미적용 중'); - const openTime = util.isDaylightSavingTime() ? '30 22 * * *' : '30 23 * * *'; // 거래 시작 시간 - const closeTime = util.isDaylightSavingTime() ? '0 5 * * *' : '0 6 * * *'; // 거래 종료 시간 - - // 거래 시작 - const job1 = new CronJob(openTime, async () => { - const channel = client.channels.cache.get(process.env.CH_FINANCE); - if (channel) { - await channel.send('미국 주식 시장이 열렸습니다.'); - } - }, null, true, 'Asia/Seoul'); - - // 거래 종료 - const job2 = new CronJob(closeTime, async () => { - const channel = client.channels.cache.get(process.env.CH_FINANCE); - if (channel) { - await channel.send('미국 주식 시장이 닫혔습니다.'); - } - }, null, true, 'Asia/Seoul'); - - job1.start(); - job2.start(); -}); - - - -client.on('interactionCreate', async interaction => { - if (!interaction.isChatInputCommand()) return; - - if (interaction.commandName === 'ping') { - await interaction.reply('Pong!'); - } -}); - - -client.on('messageCreate', async message => { - const urlPattern = /https?:\/\/(www\.)?(m\.)?gall\.dcinside\.com\/[^\s]*/g; - const urls = message.content.match(urlPattern); - - if (urls && urls.length > 0) { - const metaData = await util.getMetaData(urls[0]); // 첫 번째 URL의 메타데이터를 추출합니다. - - const embed = new EmbedBuilder() - .setColor(0x0099FF) - .setTitle(metaData.title) - .setURL(urls[0]) - // .setAuthor({ name: 'Some name', iconURL: 'https://i.imgur.com/AfFp7pu.png', url: 'https://discord.js.org' }) - .setDescription(metaData.description) - .setImage(metaData.image) - .setThumbnail(metaData.image) - // .setThumbnail('https://i.imgur.com/AfFp7pu.png') - // .addFields( - // { name: 'Regular field title', value: 'Some value here' }, - // { name: '\u200B', value: '\u200B' }, - // { name: 'Inline field title', value: 'Some value here', inline: true }, - // { name: 'Inline field title', value: 'Some value here', inline: true }, - // ) - // .addFields({ name: '인라인 필드 제목', value: '값', inline: true }) - // .setImage('https://i.imgur.com/AfFp7pu.png') - // .setTimestamp() - // .setFooter({ text: 'footer', iconURL: 'https://i.imgur.com/AfFp7pu.png' }); - // if (metaData.image) { - // console.log(metaData.image); - // embed.setImage(metaData.image); - // } - - message.channel.send({ embeds: [embed] }); - } -}); - -client.on('messageCreate', async message => { - const badLang = ['권태웅', '태웅', '웅태']; - - if (badLang.some(word => message.content.includes(word))) { - const str = `어허 나쁜말 쓰지 마세요`; - message.channel.send(str); - } -}); - -client.on('interactionCreate', async interaction => { - if (!interaction.isChatInputCommand()) return; - if (interaction.commandName === 'color') { - const sentMessage = await interaction.reply({ - content: '아이디로 보여질 색상을 선택하세요.', - fetchReply: true, // 메시지 ID 저장을 위해 reply 객체 반환 - }); - - botMessageId = sentMessage.id; // 메시지 ID 저장 - - // 이모지 추가 (🔄 포함) - await Promise.all( - Object.keys(roles).map(async emoji => { - await sentMessage.react(emoji); - }) - ); - } -}); - - -// 리액션 추가 이벤트 처리 -client.on('messageReactionAdd', async (reaction, user) => { - if (reaction.message.partial) await reaction.message.fetch(); - if (reaction.partial) await reaction.fetch(); - if (user.bot) return; // 봇의 리액션 무시 - - if (reaction.message.author.bot) { - await reaction.users.remove(user.id); // 리액션 바로 원복 - - const { emoji } = reaction; - const member = reaction.message.guild.members.cache.get(user.id); - - const roleId = roles[emoji.name]; - - if (roleId === 'reset') { - // 모든 색상 역할 제거를 병렬로 처리 - const removeRolesPromises = Object.values(roles).filter(id => id !== 'reset' && member.roles.cache.has(id)).map(id => member.roles.remove(id)); - await Promise.all(removeRolesPromises); - } else if (roleId) { - // 먼저 모든 색상 역할 제거 후 선택한 역할 추가 - const allRoleIds = Object.values(roles).filter(id => id !== 'reset'); - const removeRolesPromises = allRoleIds.filter(id => member.roles.cache.has(id)).map(id => member.roles.remove(id)); - await Promise.all(removeRolesPromises); - - const role = reaction.message.guild.roles.cache.get(roleId); - if (role) { - await member.roles.add(role); - console.log(`${user.username}에게 ${role.name} 역할이 부여되었습니다.`); - } - } - } -}); - - -client.on('messageCreate', async message => { - // '!역할확인' 명령어를 통해 역할과 ID 출력 - if (message.content === '!역할확인') { - const roles = message.guild.roles.cache.map(role => `${role.name}: ${role.id}`); - console.log(roles.join('\n')); - message.channel.send(`서버 역할 목록:\n${roles.join('\n')}`); - } + GatewayIntentBits.GuildMembers, + ], }); +// 이벤트 등록 +client.once('ready', readyEvent); +client.on('interactionCreate', interactionCreateEvent); +client.on('messageCreate', messageCreateEvent); +client.on('messageReactionAdd', messageReactionAddEvent); client.login(TOKEN); \ No newline at end of file diff --git a/app_backup.js b/app_backup.js new file mode 100644 index 0000000..c47726f --- /dev/null +++ b/app_backup.js @@ -0,0 +1,205 @@ +import dotenv from 'dotenv'; +import * as util from './feature/utility.js'; +import { REST, Routes } from 'discord.js'; +import { Client, GatewayIntentBits } from 'discord.js'; +import { EmbedBuilder } from 'discord.js'; +import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js'; +import { CronJob } from 'cron'; + + +dotenv.config(); +const TOKEN = process.env.TOKEN; +const CLIENT_ID = process.env.CLI_ID; + +const commands = [ + { + name: 'ping', + description: 'Pong! 으로 대답합니다.', + }, + { + name: 'color', + description: '아이디 색상을 변경합니다.', + }, +]; + +// const rest = new REST({ version: '10' }).setToken(TOKEN); +// try { +// console.log('Started refreshing application (/) commands.'); +// await rest.put(Routes.applicationCommands(CLIENT_ID), { body: commands }); +// console.log('Successfully reloaded application (/) commands.'); +// } catch (error) { +// console.error(error); +// } + +const roles = { + '❤️': '1221079597962104872', + '🧡': '1221091257665720360', + '💛': '1221091363475423374', + '💚': '1221080091371765812', + '🩵': '1221091149796610088', + '💙': '1221080047469858826', + '💜': '1221095844288270387', + '🔁': 'reset', +}; +let botMessageId = ''; + + +const client = new Client({ + intents: [ + GatewayIntentBits.Guilds, + GatewayIntentBits.GuildMessages, + GatewayIntentBits.MessageContent, + GatewayIntentBits.GuildMessageReactions, + GatewayIntentBits.GuildMembers, // 역할을 부여하기 위해 필요 + ] +}); + + +client.on('ready', () => { + console.log(`Logged in as ${client.user.tag}!`); + + // 서머타임 여부에 따라 시간 설정 + console.log(util.isDaylightSavingTime() ? '서머타임 적용 중' : '서머타임 미적용 중'); + const openTime = util.isDaylightSavingTime() ? '30 22 * * *' : '30 23 * * *'; // 거래 시작 시간 + const closeTime = util.isDaylightSavingTime() ? '0 5 * * *' : '0 6 * * *'; // 거래 종료 시간 + + // 거래 시작 + const job1 = new CronJob(openTime, async () => { + const channel = client.channels.cache.get(process.env.CH_FINANCE); + if (channel) { + await channel.send('미국 주식 시장이 열렸습니다.'); + } + }, null, true, 'Asia/Seoul'); + + // 거래 종료 + const job2 = new CronJob(closeTime, async () => { + const channel = client.channels.cache.get(process.env.CH_FINANCE); + if (channel) { + await channel.send('미국 주식 시장이 닫혔습니다.'); + } + }, null, true, 'Asia/Seoul'); + + job1.start(); + job2.start(); +}); + + + +client.on('interactionCreate', async interaction => { + if (!interaction.isChatInputCommand()) return; + + if (interaction.commandName === 'ping') { + await interaction.reply('Pong!'); + } +}); + + +client.on('messageCreate', async message => { + const urlPattern = /https?:\/\/(www\.)?(m\.)?gall\.dcinside\.com\/[^\s]*/g; + const urls = message.content.match(urlPattern); + + if (urls && urls.length > 0) { + const metaData = await util.getMetaData(urls[0]); // 첫 번째 URL의 메타데이터를 추출합니다. + + const embed = new EmbedBuilder() + .setColor(0x0099FF) + .setTitle(metaData.title) + .setURL(urls[0]) + // .setAuthor({ name: 'Some name', iconURL: 'https://i.imgur.com/AfFp7pu.png', url: 'https://discord.js.org' }) + .setDescription(metaData.description) + .setImage(metaData.image) + .setThumbnail(metaData.image) + // .setThumbnail('https://i.imgur.com/AfFp7pu.png') + // .addFields( + // { name: 'Regular field title', value: 'Some value here' }, + // { name: '\u200B', value: '\u200B' }, + // { name: 'Inline field title', value: 'Some value here', inline: true }, + // { name: 'Inline field title', value: 'Some value here', inline: true }, + // ) + // .addFields({ name: '인라인 필드 제목', value: '값', inline: true }) + // .setImage('https://i.imgur.com/AfFp7pu.png') + // .setTimestamp() + // .setFooter({ text: 'footer', iconURL: 'https://i.imgur.com/AfFp7pu.png' }); + // if (metaData.image) { + // console.log(metaData.image); + // embed.setImage(metaData.image); + // } + + message.channel.send({ embeds: [embed] }); + } +}); + +client.on('messageCreate', async message => { + const badLang = ['권태웅', '태웅', '웅태']; + + if (badLang.some(word => message.content.includes(word))) { + const str = `어허 나쁜말 쓰지 마세요`; + message.channel.send(str); + } +}); + +client.on('interactionCreate', async interaction => { + if (!interaction.isChatInputCommand()) return; + if (interaction.commandName === 'color') { + const sentMessage = await interaction.reply({ + content: '아이디로 보여질 색상을 선택하세요.', + fetchReply: true, // 메시지 ID 저장을 위해 reply 객체 반환 + }); + + botMessageId = sentMessage.id; // 메시지 ID 저장 + + // 이모지 추가 (🔄 포함) + await Promise.all( + Object.keys(roles).map(async emoji => { + await sentMessage.react(emoji); + }) + ); + } +}); + + +// 리액션 추가 이벤트 처리 +client.on('messageReactionAdd', async (reaction, user) => { + if (reaction.message.partial) await reaction.message.fetch(); + if (reaction.partial) await reaction.fetch(); + if (user.bot) return; // 봇의 리액션 무시 + + if (reaction.message.author.bot) { + await reaction.users.remove(user.id); // 리액션 바로 원복 + + const { emoji } = reaction; + const member = reaction.message.guild.members.cache.get(user.id); + + const roleId = roles[emoji.name]; + + if (roleId === 'reset') { + // 모든 색상 역할 제거를 병렬로 처리 + const removeRolesPromises = Object.values(roles).filter(id => id !== 'reset' && member.roles.cache.has(id)).map(id => member.roles.remove(id)); + await Promise.all(removeRolesPromises); + } else if (roleId) { + // 먼저 모든 색상 역할 제거 후 선택한 역할 추가 + const allRoleIds = Object.values(roles).filter(id => id !== 'reset'); + const removeRolesPromises = allRoleIds.filter(id => member.roles.cache.has(id)).map(id => member.roles.remove(id)); + await Promise.all(removeRolesPromises); + + const role = reaction.message.guild.roles.cache.get(roleId); + if (role) { + await member.roles.add(role); + console.log(`${user.username}에게 ${role.name} 역할이 부여되었습니다.`); + } + } + } +}); + + +client.on('messageCreate', async message => { + // '!역할확인' 명령어를 통해 역할과 ID 출력 + if (message.content === '!역할확인') { + const roles = message.guild.roles.cache.map(role => `${role.name}: ${role.id}`); + console.log(roles.join('\n')); + message.channel.send(`서버 역할 목록:\n${roles.join('\n')}`); + } +}); + + +client.login(TOKEN); \ No newline at end of file diff --git a/commands/color.js b/commands/color.js new file mode 100644 index 0000000..73be9f0 --- /dev/null +++ b/commands/color.js @@ -0,0 +1,26 @@ +const roles = { + '❤️': '1221079597962104872', + '🧡': '1221091257665720360', + '💛': '1221091363475423374', + '💚': '1221080091371765812', + '🩵': '1221091149796610088', + '💙': '1221080047469858826', + '💜': '1221095844288270387', + '🔁': 'reset', +}; + + +export default { + name: 'color', + description: '아이디 색상을 변경합니다.', + async execute(interaction) { + const sentMessage = await interaction.reply({ + content: '아이디로 보여질 색상을 선택하세요.', + fetchReply: true, + }); + + for (const emoji of Object.keys(roles)) { + await sentMessage.react(emoji); + } + }, +}; \ No newline at end of file diff --git a/commands/ping.js b/commands/ping.js new file mode 100644 index 0000000..a83d9fd --- /dev/null +++ b/commands/ping.js @@ -0,0 +1,7 @@ +export default { + name: 'ping', + description: 'Pong! 으로 대답합니다.', + async execute(interaction) { + await interaction.reply('Pong!'); + }, +}; \ No newline at end of file diff --git a/config/cronJobs.js b/config/cronJobs.js new file mode 100644 index 0000000..ca1ac55 --- /dev/null +++ b/config/cronJobs.js @@ -0,0 +1,30 @@ +import { CronJob } from 'cron'; +import { isDaylightSavingTime } from '../feature/utility.js'; + +export function initializeCronJobs(client) { + const channel = client.channels.cache.get(process.env.CH_FINANCE); + + // 미국 주식 시장 개장 시간 + const openTime = isDaylightSavingTime() ? '30 22 * * *' : '30 23 * * *'; + const openJob = new CronJob(openTime, async () => { + if (channel) { + const isDST = isDaylightSavingTime(); + const dstMessage = isDST + ? '현재 서머타임이 적용 중입니다.' + : '현재 서머타임이 적용되지 않았습니다.'; + await channel.send(`미국 주식 시장이 열렸습니다.\n${dstMessage}`); + } + }, null, true, 'Asia/Seoul'); + + // 미국 주식 시장 폐장 시간 + const closeTime = isDaylightSavingTime() ? '0 5 * * *' : '0 6 * * *'; + const closeJob = new CronJob(closeTime, async () => { + if (channel) { + await channel.send('미국 주식 시장이 닫혔습니다.'); + } + }, null, true, 'Asia/Seoul'); + + // 작업 시작 + openJob.start(); + closeJob.start(); +} diff --git a/events/interactionCreate.js b/events/interactionCreate.js new file mode 100644 index 0000000..9009f60 --- /dev/null +++ b/events/interactionCreate.js @@ -0,0 +1,16 @@ +import pingCommand from '../commands/ping.js'; +import colorCommand from '../commands/color.js'; + +const commands = { + ping: pingCommand, + color: colorCommand, +}; + +export default async function interactionCreateEvent(interaction) { + if (!interaction.isChatInputCommand()) return; + + const command = commands[interaction.commandName]; + if (command) { + await command.execute(interaction); + } +} \ No newline at end of file diff --git a/events/messageCreate.js b/events/messageCreate.js new file mode 100644 index 0000000..771e603 --- /dev/null +++ b/events/messageCreate.js @@ -0,0 +1,12 @@ +export default async function messageCreateEvent(message) { + const badWords = ['권태웅', '태웅', '웅태']; + + if (badWords.some(word => message.content.includes(word))) { + await message.channel.send('어허 나쁜말 쓰지 마세요'); + } + + if (message.content === '!역할확인') { + const roles = message.guild.roles.cache.map(role => `${role.name}: ${role.id}`); + await message.channel.send(`서버 역할 목록:\n${roles.join('\n')}`); + } +} \ No newline at end of file diff --git a/events/messageReactionAdd.js b/events/messageReactionAdd.js new file mode 100644 index 0000000..df2d2f7 --- /dev/null +++ b/events/messageReactionAdd.js @@ -0,0 +1,41 @@ +export default async function messageReactionAddEvent(reaction, user) { + try { + // 메시지와 리액션 데이터 불러오기 + if (reaction.message.partial) await reaction.message.fetch(); + if (reaction.partial) await reaction.fetch(); + if (user.bot) return; // 봇의 리액션 무시 + + // 봇이 보낸 메시지인지 확인 + if (reaction.message.author.bot) { + await reaction.users.remove(user.id); // 리액션 원복 + + const { emoji } = reaction; + const member = reaction.message.guild.members.cache.get(user.id); + + const roleId = roles[emoji.name]; + + if (roleId === 'reset') { + // 모든 색상 역할 제거를 병렬로 처리 + const removeRolesPromises = Object.values(roles) + .filter(id => id !== 'reset' && member.roles.cache.has(id)) + .map(id => member.roles.remove(id)); + await Promise.all(removeRolesPromises); + } else if (roleId) { + // 먼저 모든 색상 역할 제거 후 선택한 역할 추가 + const allRoleIds = Object.values(roles).filter(id => id !== 'reset'); + const removeRolesPromises = allRoleIds + .filter(id => member.roles.cache.has(id)) + .map(id => member.roles.remove(id)); + await Promise.all(removeRolesPromises); + + const role = reaction.message.guild.roles.cache.get(roleId); + if (role) { + await member.roles.add(role); + console.log(`${user.username}에게 ${role.name} 역할이 부여되었습니다.`); + } + } + } + } catch (error) { + console.error('Error in messageReactionAddEvent:', error); + } +} \ No newline at end of file diff --git a/events/ready.js b/events/ready.js new file mode 100644 index 0000000..105227f --- /dev/null +++ b/events/ready.js @@ -0,0 +1,7 @@ +import { initializeCronJobs } from '../config/cronJobs.js'; + +export default function readyEvent(client) { + console.log(`Logged in as ${client.user.tag}!`); + + initializeCronJobs(client); // 주식시장 개/폐장 알림 +} \ No newline at end of file diff --git a/feature/utility.js b/feature/utility.js index 6033217..5347f9f 100644 --- a/feature/utility.js +++ b/feature/utility.js @@ -22,7 +22,6 @@ export async function getMetaData(url) { } } - export function isDaylightSavingTime() { const now = new Date(); const jan = new Date(now.getFullYear(), 0, 1);