[취미생활] 디스코드 봇 만들어보기

sangyong·2023년 3월 27일
1

취미생활

목록 보기
1/2
post-thumbnail

나는 게임을 좋아한다.
공부를 한다고 2년 정도 안하다가 최근 다시 시작했다.
주로 하는 게임은 원피스 랜덤 디펜스(약칭 원랜디), 에이펙스 레전드 이 중 원랜디의 디스코드 봇을 만들어보고 싶어졌다.

큰 기능은 2가지로 생각했다.
1. 뽑기 기능 - 특정 캐릭터를 뽑는 기능
2. 코드 세이브/로드 기능 - 친구의 요청으로 넣게 된 기능

코드 세이브/로드 기능의 경우 OracleCloud의 DB와 연결하고 싶었지만 잘 되지 않아 notion API를 사용해보기로 하고, 배포는 OracleCloud free tier를 사용하기로 결정했다.

1. Discord bot 등록

먼저 https://discord.com/developers/applications 로 들어가서 Application을 등록한다.
Bot 메뉴에서 bot을 추가해주고 View Token 버튼을 눌러 토큰값을 어딘가에 잘 저장해두고 유출되지 않게 조심하자.

그 후 아래 사진과 같이 OAuth2/URL Generator 메뉴에서 권한을 선택하고 하단의 url을 생성하여 링크로 들어가면 봇을 서버에 추가할 수 있다.

2. Discord 코드 작성

먼저 npm init을 하여 프로젝트를 시작해주고 기본 정보를 작성해주고 npm install을 해준다.
npm i discord.js@12.5.3을 입력하여 discord.js를 설치해준다.
index.js 파일을 만들어주고

const Discord = require('discord.js');	// discord.js 라이브러리 호출
const client = new Discord.Client({ intents: ["GUILDS", "GUILD_MESSAGES"] })	// Client 객체 생성

const adCho = ['루피', '조로', '우솝', '쵸파', '로빈', '시라호시', '도플라밍고', '사보', '후지토라', '타시기', '바질 호킨스', '징베', '야마토', '료쿠규'];
const apCho = ['나미', '상디', '프랑키', '브룩', '샹크스', '시라호시', '검은수염', '아오키지', '아카이누', '키자루', '로우', '타시기', '루치', '스네이크맨', '키드'];
const adBool = ['로져', '레일리', '스코퍼 가반', '흰수염', '거프', '시키', '카이도'];
const apBool = ['센고쿠', '시키', '드래곤', '제트', '빅맘'];
const adYoung = ['핸콕', '카번딧슈', '버기', '니카'];
const apYoung = ['에이스', '핸콕', '비비', '우타'];
const adZe = ['크로커다일', '레베카', '카타쿠리', '킹'];
const apZe = ['에넬', '아인', '시노부', '레드필드'];

// discord 봇이 실행될 때 딱 한 번 실행할 코드를 적는 부분
client.on('ready', () => {
    console.log(`Logged in as ${client.user.tag}!`);
  
});
  
client.on('message', msg => {
    try {
        let arr = [];
        if (msg.content.indexOf('!뽑기') === 0) {
            const detail = msg.content.split(' ')[1];
            if(detail.indexOf('초') > -1){
                arr = [...arr, ...adCho, ...apCho];
            }
            if(detail.indexOf('불') > -1){
                arr = [...arr, ...adBool, ...apBool];
            }
            if(detail.indexOf('영') > -1){
                arr = [...arr, ...adYoung, ...apYoung];
            }
            if(detail.indexOf('제') > -1){
                arr = [...arr, ...adZe, ...apZe];
            }
            const set = new Set(arr);
            const result = [...set];
            msg.reply(set.size > 0 ? result[getRandom(set.size)] : '결과가 없습니다. 명령어를 확인해주세요.');
        }
        else if (msg.content.indexOf('!물뎀') === 0) {
            const detail = msg.content.split(' ')[1];
            if(detail.indexOf('초') > -1){
                arr = [...arr, ...adCho];
            }
            if(detail.indexOf('불') > -1){
                arr = [...arr, ...adBool];
            }
            if(detail.indexOf('영') > -1){
                arr = [...arr, ...adYoung];
            }
            if(detail.indexOf('제') > -1){
                arr = [...arr, ...adZe];
            }
            const set = new Set(arr);
            const result = [...set];
            msg.reply(set.size > 0 ? result[getRandom(set.size)] : '결과가 없습니다. 명령어를 확인해주세요.');
        }
        else if (msg.content.indexOf('!마뎀') === 0) {
            const detail = msg.content.split(' ')[1];
            if(detail.indexOf('초') > -1){
                arr = [...arr, ...apCho];
            }
            if(detail.indexOf('불') > -1){
                arr = [...arr, ...apBool];
            }
            if(detail.indexOf('영') > -1){
                arr = [...arr, ...apYoung];
            }
            if(detail.indexOf('제') > -1){
                arr = [...arr, ...apZe];
            }
            const set = new Set(arr);
            const result = [...set];
            msg.reply(set.size > 0 ? result[getRandom(set.size)] : '결과가 없습니다. 명령어를 확인해주세요.');
        }
        else if (msg.content.indexOf('!악몽') === 0) {
            
            const cho = new Set([...adCho, ...apCho]);
            let arrCho = [...cho];
            const bool = new Set([...adBool, ...apBool]);
            let arrBool = [...bool];
            const young = new Set([...adYoung, ...apYoung]);
            let arrYoung = [...young];
            const ze = new Set([...adZe, ...apZe]);
            let arrZe = [...ze];

            for(let i = 0; i<7; i++){
                arrCho.splice(getRandom(arrCho.length),1);
                if(i <= 1){
                    arrBool.splice(getRandom(arrBool.length),1);
                    arrZe.splice(getRandom(arrZe.length),1);
                }
                if(i == 0) arrYoung.splice(getRandom(arrYoung.length),1);
            }

            msg.reply(`
악몽 룰을 적용합니다.
초월은 ${arrCho.join(', ')}
불멸은 ${arrBool.join(', ')}
영원은 ${arrYoung.join(', ')}
제한은 ${arrZe.join(', ')}
만 갈 수 있습니다.`);
        }
    }catch (e) {
        console.log(e);
    }
});
const getRandom = (max) => Math.floor(Math.random() * max);
// 봇과 서버를 연결해주는 부분
client.login('${discord token 값을 넣어주세요}');

위와 같이 간단한 코드를 작성하여 뽑기 코드를 작성해주었습니다.

3. Notion API 연결

https://www.notion.so/my-integrations 로 이동하여 새 API 통합을 진행합니다. api 키 값을 잘 복사해둡니다.

그 후 DB 페이지를 만들고 API와 연결을 해줍니다.

이후 링크 복사 시 https://www.notion.so/____?v=299c8&pvs=4
___ 위치의 값이 DB ID에 해당하는 값입니다. 잘 복사해둡니다.
index.js 위치로 다시 이동하여 npm install @notionhq/clientnpm install dotenv --save하여 notion api에 필요한 라이브러리를 설치해줍니다.
touch .env를 입력하여 .env 파일을 만들어주고

NOTION_API_KEY=""
NOTION_DATABASE_ID=""

형태로 편집해줍니다.

다시 index.js로 돌아가서 notion 과 연결해주는 코드를 추가하고 save, load 기능을 추가해 주었습니다.

const Discord = require('discord.js');	// discord.js 라이브러리 호출
const client = new Discord.Client({ intents: ["GUILDS", "GUILD_MESSAGES"] })	// Client 객체 생성
const { Client } = require('@notionhq/client');
const dotenv = require('dotenv');
dotenv.config();

const notion = new Client({ auth: process.env.NOTION_API_KEY });
const adCho = ['루피', '조로', '우솝', '쵸파', '로빈', '시라호시', '도플라밍고', '사보', '후지토라', '타시기', '바질 호킨스', '징베', '야마토', '료쿠규'];
const apCho = ['나미', '상디', '프랑키', '브룩', '샹크스', '시라호시', '검은수염', '아오키지', '아카이누', '키자루', '로우', '타시기', '루치', '스네이크맨', '키드'];
const adBool = ['로져', '레일리', '스코퍼 가반', '흰수염', '거프', '시키', '카이도'];
const apBool = ['센고쿠', '시키', '드래곤', '제트', '빅맘'];
const adYoung = ['핸콕', '카번딧슈', '버기', '니카'];
const apYoung = ['에이스', '핸콕', '비비', '우타'];
const adZe = ['크로커다일', '레베카', '카타쿠리', '킹'];
const apZe = ['에넬', '아인', '시노부', '레드필드'];

// discord 봇이 실행될 때 딱 한 번 실행할 코드를 적는 부분
client.on('ready', () => {
    console.log(`Logged in as ${client.user.tag}!`);
  
});
  
client.on('message', msg => {
    try {
        let arr = [];
        if (msg.content.indexOf('!뽑기') === 0) {
            const detail = msg.content.split(' ')[1];
            if(detail.indexOf('초') > -1){
                arr = [...arr, ...adCho, ...apCho];
            }
            if(detail.indexOf('불') > -1){
                arr = [...arr, ...adBool, ...apBool];
            }
            if(detail.indexOf('영') > -1){
                arr = [...arr, ...adYoung, ...apYoung];
            }
            if(detail.indexOf('제') > -1){
                arr = [...arr, ...adZe, ...apZe];
            }
            const set = new Set(arr);
            const result = [...set];
            msg.reply(set.size > 0 ? result[getRandom(set.size)] : '결과가 없습니다. 명령어를 확인해주세요.');
        }
        else if (msg.content.indexOf('!물뎀') === 0) {
            const detail = msg.content.split(' ')[1];
            if(detail.indexOf('초') > -1){
                arr = [...arr, ...adCho];
            }
            if(detail.indexOf('불') > -1){
                arr = [...arr, ...adBool];
            }
            if(detail.indexOf('영') > -1){
                arr = [...arr, ...adYoung];
            }
            if(detail.indexOf('제') > -1){
                arr = [...arr, ...adZe];
            }
            const set = new Set(arr);
            const result = [...set];
            msg.reply(set.size > 0 ? result[getRandom(set.size)] : '결과가 없습니다. 명령어를 확인해주세요.');
        }
        else if (msg.content.indexOf('!마뎀') === 0) {
            const detail = msg.content.split(' ')[1];
            if(detail.indexOf('초') > -1){
                arr = [...arr, ...apCho];
            }
            if(detail.indexOf('불') > -1){
                arr = [...arr, ...apBool];
            }
            if(detail.indexOf('영') > -1){
                arr = [...arr, ...apYoung];
            }
            if(detail.indexOf('제') > -1){
                arr = [...arr, ...apZe];
            }
            const set = new Set(arr);
            const result = [...set];
            msg.reply(set.size > 0 ? result[getRandom(set.size)] : '결과가 없습니다. 명령어를 확인해주세요.');
        }
        else if (msg.content.indexOf('!악몽') === 0) {
            
            const cho = new Set([...adCho, ...apCho]);
            let arrCho = [...cho];
            const bool = new Set([...adBool, ...apBool]);
            let arrBool = [...bool];
            const young = new Set([...adYoung, ...apYoung]);
            let arrYoung = [...young];
            const ze = new Set([...adZe, ...apZe]);
            let arrZe = [...ze];

            for(let i = 0; i<7; i++){
                arrCho.splice(getRandom(arrCho.length),1);
                if(i <= 1){
                    arrBool.splice(getRandom(arrBool.length),1);
                    arrZe.splice(getRandom(arrZe.length),1);
                }
                if(i == 0) arrYoung.splice(getRandom(arrYoung.length),1);
            }

            msg.reply(`
악몽 룰을 적용합니다.
초월은 ${arrCho.join(', ')}
불멸은 ${arrBool.join(', ')}
영원은 ${arrYoung.join(', ')}
제한은 ${arrZe.join(', ')}
만 갈 수 있습니다.`);
        }
        else if (msg.content.indexOf('!save') === 0){
            saveCode();
        }
        else if (msg.content ==='!load') {
            loadCode();
        }
    }catch (e) {
        console.log(e);
    }

    async function saveCode(){
        const code = msg.content.replace('!save','').trim();
        const user = msg.author.username;
        const response = await notion.pages.create({
            parent: {
                database_id: process.env.NOTION_DATABASE_ID,
            },
            properties: {
                코드: {
                    title: [
                    {
                        text: {
                            content: code,
                        },
                    },
                    ],
                },
                닉네임: {
                    select: {
                        name: user,
                    },
                },
            }
        });
        msg.reply('코드가 저장되었습니다.');
    }

    async function loadCode(){
        const user = msg.author.username;
        const response = await notion.databases.query({
            database_id: process.env.NOTION_DATABASE_ID,
            filter: {
              property: "닉네임",
              select: {
                equals: user,
              },
            },
            sort: [{
                property: "상태",
                direction: "descending"
            }],
            page_size: 1
        })
        const code = response.results[0].properties["코드"].title[0].text.content;
        msg.reply(code ? code : '데이터가 없습니다. 세이브를 해주세요.');
    }
});
const getRandom = (max) => Math.floor(Math.random() * max);
// 봇과 서버를 연결해주는 부분
client.login('');

이후 oracle cloud에 ftp를 통해 프로젝트를 옮겨주고
sudo npm install forever -g
sudo forever start index.js 를 실행하여 계속해서 실행되게 해주었습니다.

sudo forever list, sudo forever stop 0 명령어를 통해 관리할 수 있습니다.




사진과 같이 잘 작동하는 것을 볼 수 있었습니다.

1개의 댓글

comment-user-thumbnail
2023년 3월 28일

오 흥미로운 포스팅이군요 잘 보고 갑니다!

답글 달기