반응형
노래봇에 재생목록, 삭제, 넘기기, 반복재생 등등 기능을 만들었는데 중요한 문제가 생겼다.
유튜브 정책 변경으로 기존에 사용중이던 ytdl-core 라이브러리가 사용이 불가능해졌다.
다른 방법을 찾아야하는 상황이다.
일단 아래는 지금까지 작업했었던 코드다.
const { Client, GatewayIntentBits } = require('discord.js');
const { joinVoiceChannel, createAudioPlayer, createAudioResource, AudioPlayerStatus } = require('@discordjs/voice');
const ytdl = require('ytdl-core'); //수정예정
const { token, youtubeApiKey } = require('./discordConfig.js');
const sodium = require('libsodium-wrappers'); // 추가된 부분
const search = require('youtube-search'); // 유튜브 검색 추가
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildVoiceStates,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent
]
});
const opts = {
maxResults: 1,
key: youtubeApiKey,
type: 'video'
};
// 준비
client.on('ready', () => console.log(`${client.user.tag} 에 로그인됨`));
// 봇 명령어 구분 문자
const prefix = '!아토믹';
let connection;
let voiceChannel;
let player;
let playList = [];
let playListName = [];
let isPlaying = false;
let playRepeat = false;
let currentIndex = 0;
//명령어가 !재생인경우
function prefixPlay(message){
const query = message.content.replace('!재생', '').trim();
if (!query) {
return message.reply('재생할 노래 제목이나 URL을 입력하세요.');
}
voiceChannel = message.member.voice.channel;
if (!voiceChannel) {
return message.reply('채널에 먼저 선 입장 필요');
}
const permissions = voiceChannel.permissionsFor(message.client.user);
if (!permissions.has('CONNECT') || !permissions.has('SPEAK')) {
return message.reply('권한이 없습니다.');
}
search(query, opts, async (err, results) => {
if (err) return console.error(err);
if (results.length === 0) {
return message.reply('검색 결과가 없습니다.');
}
const playUrl = results[0].link;
playList.push(playUrl);
playListName.push(results[0].title);
if (!isPlaying) {
playNext(voiceChannel, message.guild);
} else {
message.reply(`"${results[0].title}"이(가) 재생 목록에 추가됨`);
}
});
}
async function playNext(voiceChannel, guild) {
if (playList.length === 0) {
isPlaying = false;
//if (connection) connection.disconnect(); //플레이 리스트가 없다면 자동퇴장
return;
}
if(currentIndex >= playList.length){
if(playRepeat){
currentIndex=0;
}else{
isPlaying = false;
/*
if (connection) {
leaveChannel(message)
}
*/
return;
}
}
isPlaying = true;
//const playUrl = playList.shift();
const playUrl = playList[currentIndex]; // 재생목록의 현재인덱스로 재생
try {
if (!connection) {
connection = joinVoiceChannel({
channelId: voiceChannel.id,
guildId: guild.id,
adapterCreator: guild.voiceAdapterCreator,
});
}
// 원본
const stream = ytdl(playUrl, {
filter: 'audioonly',
fmt: 'mp3',
highWaterMark: 1 << 62,
liveBuffer: 1 << 62,
dlChunkSize: 0,
bitrate: 128,
quality: 'lowestaudio'
});
const resource = createAudioResource(stream);
if (!player) {
player = createAudioPlayer();
//현재 플레이되는 곡의 재생 종료 이벤트를 받는 구간
player.on(AudioPlayerStatus.Idle, () => {
currentIndex++; //인덱스 증가하여 다음 재생목록 실행
playNext(voiceChannel, guild);
});
connection.subscribe(player);
}else{
player.play(resource);
}
} catch (error) {
console.error(error);
}
}
function leaveChannel(message){
if(!connection) return message.channel.send("I'm not in a voice channel!")
connection.destroy()
connection = "" // connection 을 null로 만들어야지 다음 join이 됨...
//채널 나갈시 모두 초기화 시킴
playList = [];
playListName = [];
isPlaying = false;
playRepeat = false;
currentIndex = 0;
}
client.on('messageCreate', async message => {
if (message.author.bot) return;
if (message.content === '!재생목록'){
if (playList.length === 0) {
return message.reply('재생 목록 없음');
}
let response = '현재 재생 목록:\n';
playListName.forEach((item, index) => {
response += `[${index}] = ${item}\n`;
});
message.reply(response);
}
else if (message.content.startsWith('!삭제')) {
const index = parseInt(message.content.replace('!삭제', '').trim(), 10);
if (isNaN(index) || index < 0 || index >= playList.length) {
return message.reply('잘못된 인덱스');
}
let removed = playList.splice(index, 1);
removed = playListName.splice(index, 1);
if (index === currentIndex && index === playList.length) {
currentIndex = 0;
} else if (index <= currentIndex) {
currentIndex--;
}
message.reply(`"${removed[0]}" 재생 목록에서 삭제`);
}
else if(message.content === '!넘기기'){
if (player) {
player.stop();
}
}
else if (message.content.startsWith("!재생")) prefixPlay(message);
else if (message.content === '!반복재생'){
if(playRepeat==false){
playRepeat = true;
message.reply('반복재생 켜짐');
}
else if(playRepeat==true){
playRepeat = false;
message.reply('반복재생 꺼짐');
}
}
else if(message.content === '!초기화'){
playList = [];
playListName = [];
message.reply('재생목록 초기화');
}
else if(message.content === '!컷'){
isPlaying = false;
if (player) {
player.stop();
}
if (connection) {
leaveChannel(message)
}
}
if (!message.content.startsWith(prefix)) return;
const args = message.content.slice(prefix.length).trim().split(/ +/);
const command = args.shift();
if (command === '붐') {
message.reply(`Now playing: 아토믹 붐 임시정검`);
return;
const playUrl = 'https://www.youtube.com/watch?v=pZa6sTu8hLM&list=PLUQNSfSPIlNyUQi2tUdCBrtMl58wpWXfY&index=6';
playList.push(playUrl);
playListName.push("아토믹붐");
// if (!args[0]) {
// return message.reply('Please provide a YouTube link!');
// }
voiceChannel = message.member.voice.channel;
if (!voiceChannel) {
return message.reply('채널에 선 입장');
}
const permissions = voiceChannel.permissionsFor(message.client.user);
if (!permissions.has('CONNECT') || !permissions.has('SPEAK')) {
return message.reply('권한내놔');
}
try {
connection = joinVoiceChannel({
channelId: voiceChannel.id,
guildId: message.guild.id,
adapterCreator: message.guild.voiceAdapterCreator,
});
//const stream = ytdl(args[0], { filter: 'audioonly' });
const stream = ytdl(playUrl, {
filter: 'audioonly',
fmt: "mp3",
highWaterMark: 1 << 62,
liveBuffer: 1 << 62,
dlChunkSize: 0, //disabling chunking is recommended in discord bot
bitrate: 128,
quality: "lowestaudio"
});
const resource = createAudioResource(stream);
if (!player) {
player = createAudioPlayer();
//현재 플레이되는 곡의 재생 종료 이벤트를 받는 구간
player.on(AudioPlayerStatus.Idle, () => {
currentIndex++; //인덱스 증가하여 다음 재생목록 실행
playNext(voiceChannel, message.guild);
});
connection.subscribe(player);
}
player.play(resource);
/*
player = createAudioPlayer();
player.play(resource);
connection.subscribe(player);
player.on(AudioPlayerStatus.Idle, () => {
connection.destroy();
});
*/
//message.reply(`Now playing: ${args[0]}`);
message.reply(`Now playing: 아토믹 붐 임시정검`);
} catch (error) {
console.error(error);
message.reply('에러');
}
}
else if (command.indexOf('https://www.youtube.com') != -1) {
const playUrl = command;
voiceChannel = message.member.voice.channel;
if (!voiceChannel) {
return message.reply('채널에 먼저 들가라');
}
const permissions = voiceChannel.permissionsFor(message.client.user);
if (!permissions.has('CONNECT') || !permissions.has('SPEAK')) {
return message.reply('권한내놔');
}
try {
connection = joinVoiceChannel({
channelId: voiceChannel.id,
guildId: message.guild.id,
adapterCreator: message.guild.voiceAdapterCreator,
});
const playStream = () => {
const stream = ytdl(playUrl, {
filter: 'audioonly',
fmt: "mp3",
highWaterMark: 1 << 62,
liveBuffer: 1 << 62,
dlChunkSize: 0, //disabling chunking is recommended in discord bot
bitrate: 128,
quality: "lowestaudio"
});
const resource = createAudioResource(stream);
player.play(resource);
}
player = createAudioPlayer();
playStream();
connection.subscribe(player);
player.on(AudioPlayerStatus.Idle, () => {
playStream();
});
} catch (error) {
console.error(error);
message.reply('ㅅㅂ에러');
}
}
else if (command === '중지') {
isPlaying = false;
if (player) {
player.stop();
}
if (connection) {
leaveChannel(message)
}
}
});
client.login(token);
이제 뭘 써야하지...
반응형
'디스코드봇' 카테고리의 다른 글
[JS] 디스코드 노래 봇 6 (1) | 2024.10.17 |
---|---|
[JS] 디스코드 노래 봇 4 (1) | 2024.07.14 |
[JS] 디스코드 노래 봇 3 (0) | 2024.06.30 |
[JS] 디스코드 노래 봇 2 (1) | 2024.06.16 |
[JS] 디스코드 노래 봇 (1) | 2024.06.16 |