본문 바로가기

디스코드봇

[JS] 디스코드 노래 봇 5

반응형

노래봇에 재생목록, 삭제, 넘기기, 반복재생 등등 기능을 만들었는데 중요한 문제가 생겼다.

유튜브 정책 변경으로 기존에 사용중이던 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