디스코드 음악봇을 만들기 위해 인터넷을 찾아보면
대표적으로 3가지의 선택지를 발견하실 수 있습니다.
1. 다른 사람이 개발한 youtube player 라이브러리 사용하기
2. Lavalink 또는 ytdl-core를 사용하여 만들기
대부분 쉬운 방법을 선택하신다면
다른 개발자분들이 만든 라이브러리를 사용합니다.
하지만 이러한 방법은 오류가 발생하였을 때
수정하기 매우 까다로우며 / 자신이 원하는 방향으로 개발하기 힘들 수 있기에
저는 YTDL-CORE를 사용하는 것을 적극적으로 추천합니다.
그래서 YTDL-CORE가 뭐야?
YTDL-CORE는 유튜브 영상 다운로드 라이브러리인 youtube-dl를 업그레이드하여 제작한 라이브러리입니다.
해당 라이브러리는 유튜브를 이용할 시 얻을 수 있는 모든 정보를
가져올 수 있으며 대표적으로 추천영상, 좋아요, 아티스트 프로필, 영상정보... 등등 있습니다.
또한 유튜브 서버를 이용하여 영상 오디오를 받아오기에
끊김이 거의 없으며 안정적입니다.
그러면 유튜브에 있는 정보를 허락 없이 이용하니까 저작권위반이지 않아?
유튜브 썸네일 링크, 제목, 추천영상을 이용하거나
유튜브 서버를 이용하여 영상의 오디오를 재생하는 것은 법적인 문제가 되지 않습니다. (이해 자료)
하지만
영리 목적으로 이용한다면 법적인 문제가 발생할 수 있습니다.
(해당 문제는 모든 음악봇이 겪는 문제이며 유튜브 측에서 영리 목적으로 이용하는 봇은
이용을 하지 못하도록 하기에 이점은 크게 문제가 되지 않습니다.)
1. ytdl-core를 이용하여 음악봇 만들기
빠르게 코드를 보시려면 아래의 블로그를 이용해 주세요!
이동하기!
디스코드 봇 라이브러리 | npm install discord.js |
음악 재생 라이브러리 | npm install ytdl-core |
디지털 음성 스트림 변환 라이브러리 | npm install ffmpeg-static |
유튜브 음성채널을 이용할수있게 하는 라이브러리와 디지털 음성 스트림 변환 라이브러리 |
npm install @discordjs/voice libsodium-wrappers |
파일 구조
.
└── discord Bot
├── index.js
└── command
├── play.js
├── stream
│ ├── resource.js
│ ├── connection.js
│ └── stream.js
│ └── streamEvent.js
├── skip.js
├── pause.js
├── resume.js
└── clear.js
slash commands 봇을 만드는 자세한 내용은 아래 블로그를 참조하시기 바랍니다!
connection.js
디스코드 통화방에 연결시킵니다.
const { joinVoiceChannel } = require('@discordjs/voice');
module.export = function connection(interaction) {
return joinVoiceChannel({
channelId: interaction.member.voice.channel.id,
guildId: interaction.member.voice.channel.guild.id,
adapterCreator: interaction.member.voice.channel.guild.voiceAdapterCreator,
selfMute: false,
selfDeaf: true,
});
}
resource.js
재생할 음악의 오디오 데이터를 가져옵니다.
const { createAudioResource,StreamType } = require(`@discordjs/voice`);
const ytdl = require('ytdl-core');
module.export = function resource(url) {
return createAudioResource(
ytdl(
url,
{
highWaterMark: 1 << 25,
quality: 'highestaudio',
liveBuffer: 4900,
filter:'audioonly',
}
)
}
stream.js
음악을 재생하고 콜백함수로 player, connection을 넘겨줍니다.
const createResource = require('./resource.js');
const createConnection = require('./connection.js');
const queue = require('../index.js');
module.export = function stream(interaction,url, run) {
let connection;
let resource;
let player;
if(queue.length == 0) {
connection = createConnection(interaction);
player = createAudioPlayer({
behaviors: {
noSubscriber: NoSubscriberBehavior.Stop,
},
})
};
player = queue[0].player;
connection = queue[0].connection;
resource = createResource(url);
player.play(resource);
connection.subscribe(player);
run(player, connection);
}
streamEvent.js
음악이 끝났을 때 만약 queue에 1 초과의 목록 있을 경우
재생했던 목록을 제거 후 stream함수를 이용해 다음음악을 재생합니다.
만약 queue에 하나의 목록만 존재할 경우 제거한 다음
"모든 음악이 재생되었습니다."라고 메시지를 보냅니다.
const stream = require('./stream.js')
module.export = function event(interaction) {
player.on('stateChange', async (oldState, newState) => {
if (oldState.status === 'playing' && newState.status === 'idle') {
if(queue[0].isClear == true) {
queue = [];
await interaction.reply('모든 음악이 재생되었습니다!');
} else if(queue.size > 1) {
queue.shift();
await interaction.reply('음악이 끝났습니다. 다음음악이 재생됩니다.');
stream(interaction,queue[0].url, (({player, connection}) => {
queue.shift();
queue.push({
url: url,
player: player,
connection: connection,
isClear: false
})
} else {
queue = [];
await interaction.reply('모든 음악이 재생되었습니다!');
}
}
})
}
index.js
전역변수로 queue(재생목록)을 생성합니다.
const queue = new Array();
module.export = {
queue
}
...
play.js
string 옵션으로 받은 url을 stream 함수에 넘겨줍니다.
또한 첫 번째로 입력된 url과 player, connection 정보는
기존에 url만 존재하던 queue의 목록을 제거하고 추가해 줍니다.
const { SlashCommandBuilder } = require('discord.js');
const { queue } = require('../index.js');
module.exports = {
data: new SlashCommandBuilder()
.setName('play')
.setDescription('음악을 재생합니다.')
.addStringOption((otpion) => {
return otpion.setName('url')
.setDescription('유튜브 링크를 입력하세요.')
.setRequired(true);
}),
async execute(interaction) {
if(interaction.member.voice.channel) {
const url = interaction.options.data[0].value;
queue.push({
url: url
})
if(queue.length == 0) {
stream(interaction,url, (({player, connection}) => {
queue.shift();
queue.push({
url: url,
player: player,
connection: connection,
isClear: false
})
await interaction.reply('음악이 추가되었습니다.');
});
}
} else {
await interaction.reply('음성채널에 접속해주세요.');
}
},
};
위와 같은 형식으로 skip 또는 resume, pause를 만들 수 있습니다!
EX. skip.js
const {queue} = require('../index.js');
...
async execute(interaction) {
if(queue.length > 0) {
queue[0].player.stop();
await interaction.reply('음악이 스킵되었습니다.');
} else {
await interaction.reply('음성채널에 접속해주세요.');
}
}
}
나머지 명령어는 아래의 함수를 참고하여 만드시면 됩니다.
음악 일시정지 | player.pause() |
일시정지 해제 | player.unpause() |
통화방 연결 삭제 | connection.destroy() |
추가적으로 clear 명령어를 만드실 때에는
queue [0]. isClear = true라는 구문과 connection.destroy() 또는 player.stop()이 꼭 들어가야 합니다!
(음악을 종료 후 streamEvent.js에서 이벤트를 받게 되는데
이때 isClear 프로퍼티를 통해 초기화를 할 것인지 확인하기 때문입니다!)
위의 코드는 ytdl-core로 만든 간단한 음악봇 소스코드입니다.
변수와 구조는 이해하기 쉽게 임의로 지정한 것이며 복붙이 아닌 참고하는 용도로 이용하시는 것을 추천합니다!
'discord developer' 카테고리의 다른 글
디스코드 레니 봇 개발 일지_(비트레이트 높이는 방법,@distube/ytdl-core 사용, many request 해결) (0) | 2024.01.19 |
---|---|
디스코드 레니 봇 개발 일지_ ( 로그 편 ) (5) | 2023.11.20 |
interaction & message 올바른 type(Interface) 지정하는 법 (discord.js / typescript) (0) | 2023.07.28 |
how to fix 'node-pre-gyp error' in linux or ubuntu (@discordjs/opu installing issue) (0) | 2023.07.16 |
slash commands에 쿨타임 적용하기 [discord.js] (0) | 2023.06.30 |