[ 지난 이야기 ] 디스코드 알림봇 만들기 + Github Actions
며칠 전에 디스코드 웹훅으로 3시에 유튜브 링크를 올려주는 알림 봇을 만들었다.
그러자 클라이언트(친구) 왈,
소리 나오는 줄 알았는데 왜 안 나와
소리 나오도록 진짜 봇을 만들기로 했다.
해당 포스트는 유튜브 노래 플레이리스트 재생 봇이 아닌 특정 유튜브 주소만 재생시키는 기능을 구현하는 것을 목표로 하고 있다.
전체 소스코드는 하단 링크 참고
https://github.com/HYK-Nov/3oclock-hamburger-bot
디스코드 봇을 만들기 위해서는 보통 Discord.py나 Discord.js를 사용한다.
나는 node.js를 이용하는 Discord.js를 사용했다.
Discord.js 공식문서: https://discordjs.guide/
https://discord.com/developers/applications 에 들어가서 봇을 생성해야 한다.
New Application을 눌러주자
애플리케이션 이름은 나중에 바꿀 수 있으니 생각나는대로 적어주자
애플리케이션의 Bot 탭에서 Reset Token을 누르고 나온 토큰 값을 따로 보관해두자
그리고 밑으로 내려서 비활성화되어있는 기능을 전부 허용시켜주자
OAuth2 탭으로 가서 URL Generator에서 bot을 체크해주자.
디스코드 서버에 봇을 추가하기 위해 필요하다.
밑으로 내려서 봇에게 권한을 부여해주자.
여기서는 음성채널에 접속하고 오디오를 재생하기만 할 것이기 때문에 Connect와 Speak만 필요하다.
귀찮다 싶으면 Administrator 하면 되긴 하다.
그 밑에 나온 URL은 복사해두고 서버에 붙여넣기 해서 봇을 초대하자.
이제 디스코드 서버에서 필요한 것은 서버ID와 음성 채널 ID다.
우선 사용자 설정 - 고급 - 개발자 모드를 활성화 해주자
그러면 서버를 우클릭 하면 서버ID를 복사할 수 있다.
음성 채널도 마찬가지다.
둘 다 복사해서 토큰 값이랑 같이 보관해두자.
npm install -D @types/node typescript ts-node nodemon
타입스크립트를 사용하는 경우에 설정해주자. js로 할 경우 생략한다.
npm install discord.js @discordjs/voice @discordjs/opus ytdl-core libsodium-wrappers ffmpeg-static
discord.js
: 디스코드 봇을 만들기 위해 필요@discordjs/voice
: discord.js에서 오디오를 재생시키기 위해 필요@discordjs/opus
: Opus 인코딩ytdl-core
: 유튜브를 음원으로 바꾸기 위함libsodium-wrappers
: @discordjs/voice를 사용하기 위해서는 암호화 패키지 하나 이상을 설치해줘야 함. 공식 문서대로 libsodium-wrappers를 설치함ffmpeg-static
: ffmpeg 인코딩tsconfig.json를 생성해주자
tsc init
tsconfig.json 설정은 이와 같다
{
"compilerOptions": {
"target": "ES6",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
}
}
package.json에 아래와 같이 설정해주자
"scripts": {
"start": "ts-node src/index.ts"
}
js의 경우는 이렇다
"scripts": {
"start": "node src/index.ts"
}
루트에 src 디렉토리를 만들어서 index.ts 파일을 만들고 작성해주자.
const BOT_TOKEN = process.env["BOT_TOKEN"]!;
const GUILD_ID = process.env["GUILD_ID"]!;
const CHANNEL_ID = process.env["VOICE_CHANNEL_ID"]!;
const YOUTUBE_URL = process.env["YOUTUBE_URL"]!;
// .env
BOT_TOKEN="봇 토큰"
GUILD_ID="디스코드 서버 ID"
VOICE_CHANNEL_ID="음성 채널 ID"
YOUTUBE_URL="유튜브 링크"
Github 같이 공개된 저장소에 올릴 예정이라면 상단에 보관해두라고 한 값들을 .env 파일에 환경변수로 선언해두자.
dotenv를 설치하면 사용할 수 있다.
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildVoiceStates,
],
});
클라이언트는 봇이 작동하는데 필요한 이벤트를 수신하기 위해 설정해주어야 한다.
const connection = joinVoiceChannel({
channelId: CHANNEL_ID,
guildId: GUILD_ID,
adapterCreator: client.guilds.cache.get(GUILD_ID)?.voiceAdapterCreator!,
});
봇이 접속할 정보를 만들어주자.
channelId
: 음성 채널 IdguildId
: 서버 IdadapterCreator
: 공식 문서에는 channel.guild.voiceAdapterCreator
라고 나와있는데 서버 정보에서 voiceAdapterCreator
할 수 있으면 되는 것 같다.const job = () => {
const player = createAudioPlayer();
connection.subscribe(player);
const stream = ytdl(YOUTUBE_URL, {filter: "audioonly"});
stream.on("error", (err) => console.error(err));
const resource = createAudioResource(stream);
player.play(resource);
};
Discord.js v14 기준 connection.play()
가 안된다.
따로 AudioPlayer
를 만들어서 subscribe
를 해줘야 한다.
player에 재생할 오디오를 만들어야 한다.
stream
은 ytdl을 이용해서 유튜브 영상을 가져온다. filter에서 audioonly
를 해줘야 오디오만 나온다.
그리고 AudioResource
를 만들어서 player에 넣어주자.
client.once(Events.ClientReady, (c) => {
console.log(`Ready! Logged in as ${c.user.tag}`);
job();
})
job 함수를 실행시켜주자.
client.once(Events.ClientReady, () => {})
는 클라이언트가 생성되면 한번만 실행한다.
c
로 클라이언트 정보를 확인할 수 있다.
client.login(BOT_TOKEN);
마지막에 봇 토큰을 통해 클라이언트를 로그인 시켜주면 끝난다.
작동은 된다. 그런데 오디오를 재생하고 봇이 한참동안 남아있다.
오디오 재생이 끝나면 바로 채널에서 나가게 하고 싶다.
setTimeout(() => connection.destroy(), 15_000);
try-catch-finally를 이용해 finally에 타임아웃을 설정해주자.
connection.destroy()
는 연결을 끊어버리는 것이므로 15초 뒤에 채널을 나간다.
채널 나가는 것도 잘 작동된다. 이제 3시에 작동되도록 해야하는데 계속 로컬 서버를 돌릴 수는 없지 않은가.
클라우드 서버가 필요하긴 하다.
혹시나 무수한 청구서의 악수를 피하기 위해 신용카드를 등록하지 않아도 되는 것을 찾아봤다.
그래서 찾은 것이 replit인데 이게 단점이 UptimeRobot을 써도 cron이 안된다는 것이다.
결과적으로 다시 Github Actions로 돌아왔다.
어차피 Actions도 node.js 를 사용하긴 하지 않는가.
나처럼 24시간으로 돌리지 않고 단발적으로만 사용 할 경우에만 사용하길 바란다.
그 외에는 클라우드 서버를 쓰는게 여러모로 좋을 것이다.
name: 3 o'clock auto play
on:
schedule: # UTC+9 기준 3시 실행
- cron: "0 18 * * *"
jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Setting .env
run: |
echo "BOT_TOKEN=${{ secrets.BOT_TOKEN }}" >> .env
echo "GUILD_ID=${{ secrets.GUILD_ID }}" >> .env
echo "VOICE_CHANNEL_ID=${{ secrets.VOICE_CHANNEL_ID }}" >> .env
echo "YOUTUBE_URL=${{ secrets.YOUTUBE_URL }}" >> .env
- run: npm ci
- run: npm run start
timeout-minutes
를 설정해줘야한다. 안그러면 workflow가 안 끝난다.
여기서는 적당히 5분으로 설정해줬다.
위에서 환경변수를 설정해줬으니 여기서도 설정해줘야한다.
Github 설정
여기서 환경변수 설정하는 과정이 나오므로 참고 바란다.
Setting .env는 .env 파일을 생성하는 과정이므로 해줘야 한다.
에러나긴 했는데 강제로 종료한거니 당연하다. 일단 console.log가 실행됐으니 됐다.
클라이언트 曰
아 깜짝이야. 소리가 너무 커
볼륨을 줄이기로 했다.
그러려면 resource
을 수정해야 한다.
const resource = createAudioResource(stream, {inlineVolume: true});
resource.volume?.setVolume(0.2);
createAudioResource
에서 inlineVolume
옵션을 true
로 설정해줘야 한다.
그리고 setVolume을 1 = 100% 라고 생각하고 알아서 설정해주면 된다.
여기서는 0.2로 설정해주었다.
클라이언트 曰
그냥 저러고 나감?
그렇다. 자기 할말만 하고 나가는 봇이 되었다.