diff --git a/app.js b/app.js index b5789d9..c47726f 100644 --- a/app.js +++ b/app.js @@ -1,28 +1,31 @@ import dotenv from 'dotenv'; -import * as util from './utils.js' +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: 'Replies with Pong!', -// }, -// ]; +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); @@ -51,11 +54,37 @@ const client = new Client({ ] }); + 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; @@ -66,7 +95,7 @@ client.on('interactionCreate', async interaction => { client.on('messageCreate', async message => { - const urlPattern = /https?:\/\/(www\.)?gall\.dcinside\.com\/[^\s]*/g; + const urlPattern = /https?:\/\/(www\.)?(m\.)?gall\.dcinside\.com\/[^\s]*/g; const urls = message.content.match(urlPattern); if (urls && urls.length > 0) { @@ -109,30 +138,32 @@ client.on('messageCreate', async message => { } }); +client.on('interactionCreate', async interaction => { + if (!interaction.isChatInputCommand()) return; + if (interaction.commandName === 'color') { + const sentMessage = await interaction.reply({ + content: '아이디로 보여질 색상을 선택하세요.', + fetchReply: true, // 메시지 ID 저장을 위해 reply 객체 반환 + }); -// 'send-reactions' 커맨드를 받았을 때 리액션이 포함된 메시지를 보냄 -client.on('messageCreate', async message => { - if (message.content === '!색상') { - const sentMessage = await message.channel.send('색상을 선택하세요.'); botMessageId = sentMessage.id; // 메시지 ID 저장 - // 'roles' 객체에서 이모지 키들을 추출하고, 각 이모지에 대해 리액션 추가 - Object.keys(roles).forEach(async emoji => { - if (emoji !== '🔁') { // '리셋' 이모지는 마지막에 추가하기 위해 여기서는 제외 + // 이모지 추가 (🔄 포함) + await Promise.all( + Object.keys(roles).map(async emoji => { await sentMessage.react(emoji); - } - }); - await sentMessage.react('🔁'); + }) + ); } }); + // 리액션 추가 이벤트 처리 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.id === botMessageId) { if (reaction.message.author.bot) { await reaction.users.remove(user.id); // 리액션 바로 원복 @@ -154,11 +185,21 @@ client.on('messageReactionAdd', async (reaction, user) => { const role = reaction.message.guild.roles.cache.get(roleId); if (role) { await member.roles.add(role); - console.log(`${user.username}에게 ${role.name}역할이 부여되었습니다.`); + 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/feature/utility.js b/feature/utility.js new file mode 100644 index 0000000..6033217 --- /dev/null +++ b/feature/utility.js @@ -0,0 +1,32 @@ +import axios from 'axios'; +import cheerio from 'cheerio'; + +export async function getMetaData(url) { + try { + const response = await axios.get(url); + const html = response.data; + const $ = cheerio.load(html); + + const title = $('meta[property="og:title"]').attr('content'); + const description = $('meta[property="og:description"]').attr('content'); + const image = $('meta[property="og:image"]').attr('content'); + + return { + title: title || 'No title', + description: description || 'No description', + image: image || '' + }; + } catch (error) { + console.error('Error fetching URL:', error.message); + return {}; + } +} + + +export function isDaylightSavingTime() { + const now = new Date(); + const jan = new Date(now.getFullYear(), 0, 1); + const jul = new Date(now.getFullYear(), 6, 1); + const standardOffset = Math.max(jan.getTimezoneOffset(), jul.getTimezoneOffset()); + return now.getTimezoneOffset() < standardOffset; // 서머타임 여부 반환 +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 2d02667..e3eedea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "axios": "^1.6.8", "cheerio": "^1.0.0-rc.12", + "cron": "^3.5.0", "discord.js": "^14.14.1", "dotenv": "^16.4.5" } @@ -151,6 +152,11 @@ "npm": ">=7.0.0" } }, + "node_modules/@types/luxon": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.4.2.tgz", + "integrity": "sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==" + }, "node_modules/@types/node": { "version": "20.11.30", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.30.tgz", @@ -243,6 +249,15 @@ "node": ">= 0.8" } }, + "node_modules/cron": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/cron/-/cron-3.5.0.tgz", + "integrity": "sha512-0eYZqCnapmxYcV06uktql93wNWdlTmmBFP2iYz+JPVcQqlyFYcn1lFuIk4R54pkOmE7mcldTAPZv6X5XA4Q46A==", + "dependencies": { + "@types/luxon": "~3.4.0", + "luxon": "~3.5.0" + } + }, "node_modules/css-select": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", @@ -444,6 +459,14 @@ "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==" }, + "node_modules/luxon": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.5.0.tgz", + "integrity": "sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==", + "engines": { + "node": ">=12" + } + }, "node_modules/magic-bytes.js": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.10.0.tgz", diff --git a/package.json b/package.json index 9e33230..9794bd4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "discord_allnight", - "version": "1.0.0", + "version": "1.0.1", "main": "app.js", "type": "module", "scripts": { @@ -11,6 +11,7 @@ "dependencies": { "axios": "^1.6.8", "cheerio": "^1.0.0-rc.12", + "cron": "^3.5.0", "discord.js": "^14.14.1", "dotenv": "^16.4.5" } diff --git a/utils.js b/utils.js index 8a676ff..e69de29 100644 --- a/utils.js +++ b/utils.js @@ -1,25 +0,0 @@ -import axios from 'axios'; -import cheerio from 'cheerio'; - -async function getMetaData(url) { - try { - const response = await axios.get(url); - const html = response.data; - const $ = cheerio.load(html); - - const title = $('meta[property="og:title"]').attr('content'); - const description = $('meta[property="og:description"]').attr('content'); - const image = $('meta[property="og:image"]').attr('content'); - - return { - title: title || 'No title', - description: description || 'No description', - image: image || '' - }; - } catch (error) { - console.error('Error fetching URL:', error.message); - return {}; - } - } - -export { getMetaData }; \ No newline at end of file