import { Redis } from "ioredis"; import { Config } from "../utils/Config"; import { Logger } from "../utils/Logger"; import { YoutubeMusic } from "../utils/api/YoutubeMusic"; import { Spotify } from "../utils/api/Spotify"; import { lavalinkManager } from "../index"; import { getGuildById, getVoiceChannelById } from "../utils/music/Channel"; import { channelJoin } from "../commands/join"; type SubAction = "search" | "player_play" | "player_playlist"; export class RedisClient { public pub: Redis = new Redis({ host: Config.redis.host, port: Config.redis.port }); public sub: Redis = new Redis({ host: Config.redis.host, port: Config.redis.port }); constructor() { this.pub.on("connect", () => { Logger.ready(`[Redis Pub] 연결 완료 (말하는 입)`); }); this.sub.on("connect", () => { Logger.ready(`[Redis Sub] 연결 완료 (듣는 귀)`); }); this.sub.subscribe("site-bot", (err, count) => { if (err) return Logger.error(`[Redis Sub] 구독 실패: ${err.message}`); Logger.log(`[Redis Sub] 'bot-commands' 채널 구독 중... (현재 구독 채널 수: ${count})`); }); this.sub.on("message", async (ch, msg): Promise => { if (ch !== "site-bot") return; Logger.log(`[Redis Sub] [Message] 수신: {\n 채널: ${ch}\n 내용: ${msg}\n}`); try { const data = JSON.parse(msg) as { action: SubAction; requestId: string; userId?: string; [key: string]: any; }; if (data.action === "search") { const resultKey = `search:${data.requestId}`; const results = await Spotify.getSearchFull(data.query) ?? await YoutubeMusic.getSearchFull(data.query) ?? []; await this.pub.setex(resultKey, 60, JSON.stringify(results)); Logger.log(`[Redis Pub] [setex] 결과 저장: (${resultKey})`); } else if (data.action === "player_play") { const resultKey = `player:play:${data.requestId}`; if (!data.serverId) return await this.pub.setex(resultKey, 60, JSON.stringify({ success: false, message: "serverId를 찾을수 없습니다." })); if (!data.userId) return await this.pub.setex(resultKey, 60, JSON.stringify({ success: false, message: "userId를 찾을수 없습니다." })); const guild = await getGuildById(data.serverId); if (!guild) return await this.pub.setex(resultKey, 60, JSON.stringify({ success: false, message: "guild를 찾을수 없습니다." })); let player = lavalinkManager.getPlayer(guild.id); const voiceChannel = await getVoiceChannelById(guild, data.userId); if (!player) { if (!voiceChannel) return await this.pub.setex(resultKey, 60, JSON.stringify({ success: false, message: "음성채널에 들어가서 이용해주세요." })); player = (await channelJoin(guild, voiceChannel.id)).player; } if (!player) return await this.pub.setex(resultKey, 60, JSON.stringify({ success: false, message: "세션을 찾을수 없습니다." })); await lavalinkManager.search(guild.id, data.track.url, data.userId, player); // await this.pub.setex(resultKey, 60, JSON.stringify({ success: true, message: "노래 추가 완료" })); } else if (data.action === "player_playlist") { const resultKey = `player:play:${data.requestId}`; if (!data.serverId) return await this.pub.setex(resultKey, 60, JSON.stringify({ success: false, message: "serverId를 찾을수 없습니다." })); if (!data.userId) return await this.pub.setex(resultKey, 60, JSON.stringify({ success: false, message: "userId를 찾을수 없습니다." })); const guild = await getGuildById(data.serverId); if (!guild) return await this.pub.setex(resultKey, 60, JSON.stringify({ success: false, message: "guild를 찾을수 없습니다." })); let player = lavalinkManager.getPlayer(guild.id); const voiceChannel = await getVoiceChannelById(guild, data.userId); if (!player) { if (!voiceChannel) return await this.pub.setex(resultKey, 60, JSON.stringify({ success: false, message: "음성채널에 들어가서 이용해주세요." })); player = (await channelJoin(guild, voiceChannel.id)).player; } if (!player) return await this.pub.setex(resultKey, 60, JSON.stringify({ success: false, message: "세션을 찾을수 없습니다." })); await lavalinkManager.search(guild.id, data.playlistUrl, data.userId, player); // await this.pub.setex(resultKey, 60, JSON.stringify({ success: true, message: "플레이리스트 추가 완료" })); } } catch (err) { Logger.error(`명령어 처리 중 에러: ${String(err)}`); } }); this.pub.on("error", (err) => { Logger.error(`[Redis Pub] [Error] ${err.message}`); }); this.sub.on("error", (err) => { Logger.error(`[Redis Sub] [Error] ${err.message}`); }); } public publishState(event: string, data: any) { const payload = JSON.stringify({ event, timestamp: Date.now(), ...data, }); this.pub.publish("bot-site", payload); Logger.log(`[Redis Pub] bot -> site 전송: ${event}`); } public runTest() { Logger.debug(`[Redis Test] 3초 뒤에 테스트 통신 시작...`); setTimeout(() => { // 1. 봇 -> 사이트(웹) 방향 전송 테스트 this.publishState("TRACK_START", { author: "테스트", title: "제목", duration: 196000, }); // 2. 사이트(웹) -> 봇 방향 수신 테스트 (가짜 명령을 쏴서 스스로 수신하는지 확인) setTimeout(() => { const mockCommand = JSON.stringify({ action: "skip", userId: "12345" }); // 테스트를 위해 본인이 site-bot 채널로 발행해 봅니다. this.pub.publish("site-bot", mockCommand); }, 1000); }, 3000); } }