검색해서 재생기능 제작

검색하고 재생 누르면 재생됨
플레이리스트 재생 기능(주소 전달해서 재생하는 방식)
This commit is contained in:
tkrmagid-desktop
2026-04-09 01:53:27 +09:00
parent cefe37e2a8
commit 2e014e9b34
11 changed files with 238 additions and 21 deletions

View File

@@ -3,6 +3,11 @@ 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 });
@@ -21,18 +26,50 @@ export class RedisClient {
Logger.log(`[Redis Sub] 'bot-commands' 채널 구독 중... (현재 구독 채널 수: ${count})`);
});
this.sub.on("message", async (ch, msg) => {
this.sub.on("message", async (ch, msg): Promise<any> => {
if (ch !== "site-bot") return;
Logger.log(`[Redis Sub] [Message] 수신: {\n 채널: ${ch}\n 내용: ${msg}\n}`);
try {
const data = JSON.parse(msg) as { action: "search"; requestId: string; [key: string]: any; };
const data = JSON.parse(msg) as { action: SubAction; requestId: string; userId?: string; [key: string]: any; };
if (data.action === "search") {
const resultKey = `search:result:${data.requestId}`;
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)}`);
}

View File

@@ -1,7 +1,8 @@
export interface SongItem {
videoId: string;
url: string;
title: string;
artist: string;
videoId: string;
thumbnail: string; // 썸네일 URL
duration: number; // 재생시간 (ms 단위)
}

View File

@@ -109,10 +109,11 @@ export const Spotify = {
return data.tracks.items.map((track) => ({
videoId: track.id,
url: `https://open.spotify.com/track/${track.id}`,
title: track.name,
artist: track.artists.map(artist => artist.name).join(", "),
duration: track.duration_ms,
thumbnail: track.album.images[2]?.url,
thumbnail: track.album.images[0]?.url,
}));
} catch (err) {
Logger.error(`스포티파이 검색 실패: ${err}`);
@@ -123,8 +124,7 @@ export const Spotify = {
const lowerQuery = query.toLocaleLowerCase().trim();
if (searchCache.has(lowerQuery)) return searchCache.get(lowerQuery) ?? null;
const track = (await this.getSearchFull(query) ?? [])?.[0];
const url = track?.videoId ? `https://open.spotify.com/track/${track.videoId}` : null;
if (url) searchCache.set(lowerQuery, url);
return url;
if (track.url) searchCache.set(lowerQuery, track.url);
return track.url;
}
}

View File

@@ -146,9 +146,10 @@ export const YoutubeMusic = {
if (videoId && title) {
results.push({
videoId,
url: `https://music.youtube.com/watch?v=${videoId}`,
title,
artist,
videoId,
thumbnail,
duration: parseDurationToMs(durationStr)
});
@@ -187,9 +188,10 @@ export const YoutubeMusic = {
if (videoId && title) {
results.push({
videoId,
url: `https://music.youtube.com/watch?v=${videoId}`,
title,
artist,
videoId,
thumbnail,
duration: parseDurationToMs(durationStr || "")
});
@@ -209,8 +211,7 @@ export const YoutubeMusic = {
const lowerQuery = query.toLocaleLowerCase().trim();
if (searchCache.has(lowerQuery)) return searchCache.get(lowerQuery) ?? null;
const video = (await this.getSearchFull(query) ?? [])?.[0];
const url = video?.videoId ? `https://music.youtube.com/watch?v=${video.videoId}` : null;
if (url) searchCache.set(lowerQuery, url);
return url;
if (video.url) searchCache.set(lowerQuery, video.url);
return video.url;
}
};

View File

@@ -3,6 +3,19 @@ import { DB } from "../Database";
import { Config } from "../Config";
import { Logger } from "../Logger";
import { clearAllMsg } from "./Utils";
import { client } from "../../index";
export const getGuildById = async (guildId: string): Promise<Guild | null> => {
const guild = client.guilds.cache.get(guildId)?.fetch();
if (!guild) return null;
return guild;
}
export const getMemberById = async (guild: Guild, userId: string): Promise<GuildMember | null> => {
const member = await guild.members.cache.get(userId)?.fetch(true);
if (!member) return null;
return member;
}
export const getVoiceChannel = (member: GuildMember): VoiceChannel | null => {
if (member.voice.channel?.type === ChannelType.GuildVoice) return member.voice.channel;
@@ -10,7 +23,7 @@ export const getVoiceChannel = (member: GuildMember): VoiceChannel | null => {
return null;
}
export const getVoiceChannelById = async(guild: Guild, userId: string): Promise<VoiceChannel | null> => {
export const getVoiceChannelById = async (guild: Guild, userId: string): Promise<VoiceChannel | null> => {
if (!guild) return null;
const member = await guild.members.cache.get(userId)?.fetch(true);
if (!member) return null;