diff --git a/client.js b/client.js index 317c6a4..f064eb7 100644 --- a/client.js +++ b/client.js @@ -1,4 +1,6 @@ + const { Client, GatewayIntentBits, REST, Routes } = require("discord.js"); +const { voiceStateUpdate } = require("./events/voiceStateUpdate"); const client = new Client({ intents: [ @@ -11,139 +13,25 @@ const client = new Client({ -const { - initUserState, - // endDuration, - userState, - // saveDuration, -} = require("./duration"); + client.once("ready", () => { console.log("Bot is ready!"); }); -function isChannelToTrack(channelId){ - // YOUR_VOICE_CHANNEL_ID - const channelToTrack = process.env.VOICE_CHANNEL; - if(channelToTrack===channelId){ - return true - } - else{ - return false - } - -} - - - -function checkJoinLeft(user, oldState, newState){ - - if (user && newState.channelId === channelToTrack) { - // 이벤트 발생이 최초로 입장한 유저의 경우 - if (userDurations.has(user.id) === false) { - // User joined the specified voice channel - console.log( - `${user.user.tag} joined ${newState.channel?.name}: ${new Date()}` - ); - startDuration(user.id); - } - } else if ( - user && - oldState.channelId === channelToTrack && - newState.channel === null - ) { - // User left the specified voice channel - - const { - id: userUId, - tag: userTag, - globalName: userGlobalName, - } = user.user; - console.log( - `${userGlobalName} left ${oldState.channel?.name}: ${new Date()}` - ); - - if (userDurations.has(userUId)) { - const userDurationInfo = userDurations.get(userUId); - endDuration(userDurationInfo); - const { startTime, endTime, totalDuration } = userDurationInfo; - console.log( - `${userUId} left ${userGlobalName}: ${totalDuration} need to be saved on DB` - ); - - /*saveDuration( - userUId, - userGlobalName, - userTag, - startTime, - endTime, - totalDuration - );*/ - - // Remove the user from the tracking map - userDurations.delete(user.id); - } - } - -} - -client.on("voiceStateUpdate", (oldState, newState) => { - const user = newState.member.user; - - - - if (user && oldState && newState) { - const userInfo = { - id: user.id, - name: user.username, - globalName: user.globalName, - }; - const newChannel = newState.channelId; - const oldChannel = oldState.channelId; - const newState_ = { - selfDeaf: newState.selfDeaf, - selfMute: newState.selfMute, - selfVideo: newState.selfVideo, - streaming: newState.streaming, - }; - const oldState_ = { - selfDeaf: oldState.selfDeaf, - selfMute: oldState.selfMute, - selfVideo: oldState.selfVideo, - streaming: oldState.streaming, - }; - // 추적하는 채널 내에서 이벤트 발생한 경우 - if (isChannelToTrack(newChannel)) { - // 처음 입장한 경우 - if (!userState.has(userInfo.id)) { - initUserState(userInfo, newState_); - } else { - // 기타 마이크, 화면 공유, 이어폰 해제, 소리 끄기 이벤트 발생 여부 확인 - } - } - else{ - if(isChannelToTrack(oldChannel) && userState.has(user.id)){ - console.log("퇴장 처리~~~") - userState.delete(userInfo.id); - } - else{ - console.log("그냥 다른 채널에서 발생한 일") - } - } - } - - - - // checkJoinLeft(user, oldState, newState); - -}); + + +client.on("voiceStateUpdate", voiceStateUpdate); + client.login(process.env.DISCORD_TOKEN).catch((error) => { console.log(error); process.exit(1); }); + + module.exports = client; diff --git a/events/voiceStateUpdate.js b/events/voiceStateUpdate.js new file mode 100644 index 0000000..9498ae7 --- /dev/null +++ b/events/voiceStateUpdate.js @@ -0,0 +1,186 @@ +const { initUserState, userState } = require("../model/userState"); + +const { + isEventHappened, + setUserInfo, + setChannelInfo, + setEventState, + getTime, + getDuration, +} = require("../utils"); + + +function voiceStateUpdate(oldState, newState){ + const { + member: { user }, + } = newState; + if (!user || !oldState || !newState) return; + + const userInfo = setUserInfo(user); + + const channelInfo = setChannelInfo( + oldState.channelId, + newState.channelId + ); + const newState_ = setEventState(newState); + const oldState_ = setEventState(oldState); + +/* + if (user && channelInfo.new) { + + + if (!userState.has(userInfo.id)) { + initUserState(userInfo, newState_); + console.log(userState.get(userInfo.id)); + } else { + const { selfDeaf, selfMute, selfVideo, streaming } = userState.get( + userInfo.id + ); + handleEventStateChange( + newState_.selfDeaf, + oldState_.selfDeaf, + selfDeaf, + "selfDeaf" + ); + handleEventStateChange( + newState_.selfMute, + oldState_.selfMute, + selfMute, + "selfMute 발생" + ); + handleEventStateChange( + newState_.selfVideo, + oldState_.selfVideo, + selfVideo, + "selfVideo" + ); + handleEventStateChange( + newState_.streaming, + oldState_.streaming, + streaming, + "streaming" + ); + console.log("이벤트 처리 완료", userState.get(userInfo.id)); + } + } else if (channelInfo.old && userState.has(userInfo.id)) { + console.log("퇴장 처리~~~"); + userState.delete(userInfo.id); + } else { + console.log("그냥 다른 채널에서 발생한 일"); + }*/ + + // 추적하는 채널 내에서 이벤트 발생한 경우 + if (user && channelInfo.new) { + // 처음 입장한 경우 + if (!userState.has(userInfo.id)) { + initUserState(userInfo, newState_); + console.log(userState.get(userInfo.id)); + } else { + const { selfDeaf, selfMute, selfVideo, streaming } = userState.get( + userInfo.id + ); + //console.log("이벤트 발생 전", userState.get(userInfo.id)) + // 기타 마이크, 화면 공유, 이어폰 해제, 소리 끄기 이벤트 발생 여부 확인 + + if (isEventHappened(newState_.selfDeaf, oldState_.selfDeaf)) { + // 이어폰 + if (oldState_.selfDeaf) { + // 꺼짐 => 켜짐 + selfDeaf.time = getTime(); + } else { + // 켜짐 => 꺼짐 + console.log( + "self Deaf 발생: 이어폰 DB 저장 필요", + getDuration(selfDeaf.time, getTime()) + ); + selfDeaf.time = null; + } + selfDeaf.state = newState_.selfDeaf; + console.log("self Deaf 발생: 이어폰"); + } + if (isEventHappened(newState_.selfMute, oldState_.selfMute)) { + // 마이크 + if (oldState_.selfMute) { + // 꺼짐 => 켜짐 + selfMute.time = getTime(); + } else { + // 켜짐 => 꺼짐 + console.log( + "self Mute 발생: 마이크 DB 저장 필요", + getDuration(selfMute.time, getTime()) + ); + selfMute.time = null; + } + selfMute.state = newState_.selfMute; + console.log("self Mute 발생: 마이크"); + } + if (isEventHappened(newState_.selfVideo, oldState_.selfVideo)) { + // 카메라 + + if (newState_.selfVideo) { + // 꺼짐 -> 켜짐 + selfVideo.time = getTime(); + } else { + // 켜짐 -> 꺼짐 + console.log( + "self Video 발생: 카메라 DB 저장 필요", + getDuration(selfVideo.time, getTime()) + ); + selfVideo.time = null; + } + selfVideo.state = newState_.selfVideo; + console.log("self Video 발생 : 카메라"); + } + if (isEventHappened(newState_.streaming, oldState_.streaming)) { + // 화면 공유 + + if (newState_.streaming) { + streaming.time = getTime(); + } else { + console.log( + "streaming 발생: 화면 공유 DB 저장 필요", + getDuration(streaming.time, getTime()) + ); + streaming.time = null; + } + console.log("streaming 발생: 화면 공유"); + streaming.state = newState_.streaming; + } + console.log("이벤트 처리 완료", userState.get(userInfo.id)); + } + } else { + // console.log(oldChannelTrack, userState.has(user.id)) + if (channelInfo.old && userState.has(user.id)) { + console.log("퇴장 처리~~~"); + userState.delete(userInfo.id); + } else { + console.log("그냥 다른 채널에서 발생한 일"); + } + } + + // checkJoinLeft(user, oldState, newState); + + + +} + + +function handleEventStateChange(newState, oldState, state, type) { + if (isEventHappened(newState, oldState)) { + + if(newState){ + const duration = getDuration(state.time, getTime()); + console.log( + `${type}: ${state.state ? "켜짐" : "꺼짐"}, DB 저장 필요`, + duration + ); + } + + state.time = state.state ? getTime() : null; + state.state = newState; + } +} + +module.exports = { + voiceStateUpdate, +}; diff --git a/duration.js b/model/userState.js similarity index 68% rename from duration.js rename to model/userState.js index 4781dea..39217ab 100644 --- a/duration.js +++ b/model/userState.js @@ -1,5 +1,7 @@ // const supabase = require("./supabase"); +const { getTime } = require("../utils"); + const userState = new Map(); function initUserState(userInfo, state) { @@ -7,8 +9,23 @@ function initUserState(userInfo, state) { const { id, name, globalName } = userInfo; // Record the start time userState.set(id, { - enter: new Date(), -...state + enter: getTime(), + selfDeaf: { + state: state.selfDeaf, + time: state.selfDeaf ? null : getTime(), + }, + selfMute: { + state: state.selfMute, + time: state.selfMute ? null : getTime(), + }, + selfVideo: { + state: state.selfVideo, + time: state.selfVideo ? getTime() : null, + }, + streaming: { + state: state.streaming, + time: state.streaming ? getTime() : null, + }, }); console.log(`${id}:${name} joined`); // console.log(userState.get(id)) @@ -16,6 +33,7 @@ function initUserState(userInfo, state) { } + function endDuration(durationInfo) { durationInfo.endTime = new Date(); durationInfo.totalDuration = Math.floor( @@ -43,7 +61,5 @@ async function saveDuration(dsUId, dsGlobalName, dsTag, start, end, duration) { module.exports = { initUserState, - // endDuration, - // saveDuration, userState, }; diff --git a/package-lock.json b/package-lock.json index ce66ef3..1ab373b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "discord-study-v2", + "name": "Discord-Study-BE", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "discord-study-v2", + "name": "Discord-Study-BE", "version": "1.0.0", "license": "MIT", "dependencies": { @@ -13,6 +13,7 @@ "@discordjs/rest": "^2.2.0", "@supabase/supabase-js": "^2.38.4", "cross-env": "^7.0.3", + "dayjs": "^1.11.10", "discord-api-types": "^0.37.64", "discord.js": "^14.14.1", "dotenv": "^16.3.1" @@ -288,6 +289,11 @@ "node": ">= 8" } }, + "node_modules/dayjs": { + "version": "1.11.10", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" + }, "node_modules/discord-api-types": { "version": "0.37.73", "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.73.tgz", diff --git a/package.json b/package.json index f3a4fca..e2e3176 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "@discordjs/rest": "^2.2.0", "@supabase/supabase-js": "^2.38.4", "cross-env": "^7.0.3", + "dayjs": "^1.11.10", "discord-api-types": "^0.37.64", "discord.js": "^14.14.1", "dotenv": "^16.3.1" diff --git a/utils/dayjs.js b/utils/dayjs.js new file mode 100644 index 0000000..649d512 --- /dev/null +++ b/utils/dayjs.js @@ -0,0 +1,13 @@ +const dayjs = require("dayjs"); +require("dayjs/locale/ko"); +const utc = require("dayjs/plugin/utc"); +const timezone = require("dayjs/plugin/timezone"); +const duration = require("dayjs/plugin/duration"); + +dayjs.locale("kor"); +dayjs.extend(utc); +dayjs.extend(timezone); +dayjs.extend(duration) +dayjs.tz.setDefault("Asia/Seoul"); + +module.exports = dayjs; diff --git a/utils/discord.js b/utils/discord.js new file mode 100644 index 0000000..d68425d --- /dev/null +++ b/utils/discord.js @@ -0,0 +1,46 @@ +function isChannelToTrack(channelId) { + // YOUR_VOICE_CHANNEL_ID + const channelToTrack = process.env.VOICE_CHANNEL; + + return channelToTrack === channelId? true:false; +} + +function isEventHappened(prev, curr) { + const stateChanged = !(curr === prev); + return stateChanged; +} + +function setUserInfo(user){ + return { + id: user.id, + name: user.username, + globalName: user.globalName, + }; + + +} + +function setChannelInfo(prev, curr){ + return { + new: isChannelToTrack(curr), + old:isChannelToTrack(prev) + } + +} + + +function setEventState(state){ + return { + selfDeaf: state.selfDeaf, + selfMute: state.selfMute, + selfVideo: state.selfVideo, + streaming: state.streaming, + }; +} + +module.exports = { + isEventHappened, + setUserInfo, + setChannelInfo, + setEventState, +}; \ No newline at end of file diff --git a/utils/index.js b/utils/index.js new file mode 100644 index 0000000..55765a2 --- /dev/null +++ b/utils/index.js @@ -0,0 +1,16 @@ +const { + isEventHappened, + setUserInfo, + setChannelInfo, + setEventState, +} = require("./discord"); +const { getTime, getDuration } = require("./time"); + +module.exports = { + isEventHappened, + setUserInfo, + setChannelInfo, + setEventState, + getTime, + getDuration +}; diff --git a/utils/time.js b/utils/time.js new file mode 100644 index 0000000..90c5acc --- /dev/null +++ b/utils/time.js @@ -0,0 +1,20 @@ +const dayjs = require("./dayjs") + +function getTime() { + const now = dayjs().format("YYYY-MM-DD HH:mm:ss"); + return now; +} + + +function getDuration(start, end){ + const startTime = dayjs(start) + const endTime = dayjs(end) + const diff = endTime.diff(startTime, "second"); + return diff; +} + + +module.exports = { + getTime, + getDuration +};