지난 시간까지는 discord.js와 djs-commander를 이용해서 디스코드 봇을 작동시키는 법을 익혔고, 이제부터는 본격적으로 메이플스토리 봇을 만들어 볼 것이다.
Discord.js로 메이플스토리 디스코드 봇 만들기 (1)
이번에는 지난 포스팅에 이어 본격적으로 디스코드 봇에 여러 기능을 추가하는 방법에 대해 알아보자. 지난 포스팅 보러가기(봇 생성 및 discord.js 라이브러리 설치) Discord.js로 메이플스토리 디스
huzan2.tistory.com
먼저 지난 시간에 만들었던 명령어들을 전부 삭제해줄 것인데, 파일만 삭제하면 디스코드에서는 아래 사진처럼 여전히 명령어가 남아 있다.
때문에 명령어를 삭제하는 과정을 거쳐야 하는데, 아주 간단하게 아래와 같이 deleted 속성을 true로 설정해주고 봇을 재시작해주기만 하면 된다.
const { SlashCommandBuilder } = require("discord.js");
module.exports = {
deleted: true,
data: new SlashCommandBuilder()
.setName("ping")
.setDescription("pong이라고 응답합니다."),
run: ({ interaction }) => {
interaction.reply("Pong!");
},
};
위 코드는 ping.js의 내용을 예시로 든 것이고, add 또한 같은 방법으로 삭제해주면 된다. 봇을 재시작하면 명령어가 삭제되었다는 알림을 볼 수 있을 것이다.
캐릭터 정보 불러오기
가장 먼저 만들 명령어는 캐릭터 정보를 불러오는 명령어이다.
메이플스토리 유저 정보를 띄워주는 maple.gg를 크롤링해서 정보를 가져올 것인데, axios와 cheerio라는 라이브러리를 이용할 것이다.
먼저 각 라이브러리를 설치해 준다.
npm i axios
npm i cheerio
이제 commands 폴더 안에 charInfo.js라는 파일을 생성하고 본격적으로 명령어를 구현해 보자.
embed로 검색 결과를 출력해줄 것이기 때문에 SlashCommandBuilder와 함께 EmbedBuilder을 discord.js에서 불러와주고, 명령어의 이름과 option으로 받을 String 타입의 option을 설정해 준다.
const { SlashCommandBuilder, EmbedBuilder } = require("discord.js");
module.exports = {
data: new SlashCommandBuilder()
.setName("캐릭터")
.setDescription("캐릭터의 정보를 출력합니다.")
.addStringOption((option) =>
option
.setName("닉네임")
.setDescription("정보를 가져올 캐릭터의 닉네임")
.setRequired(true)
),
run: ({ interaction }) => {},
};
Embed
이제 embed를 출력해볼 것인데, embed가 무엇인지는 아래 사진을 보면 바로 이해할 수 있을 것이다.
각 field에 대한 설명은 공식 문서에 잘 나와있으니 생략하도록 하겠다.
discord.js Guide
Imagine a guide... that explores the many possibilities for your discord.js bot.
discordjs.guide
먼저 정상적으로 embed가 출력되는지 테스트하기 위해서 입력받은 닉네임만 출력해보도록 하자.
run 필드에 다음과 같이 함수를 입력해 준다.
run: ({ interaction }) => {
const nickName = interaction.options.get("닉네임").value;
const embed = new EmbedBuilder().setTitle(`${nickName}`);
interaction.reply({ embeds: [embed] });
}
봇을 실행하고 명령어를 입력해주면..
embed가 정상적으로 출력되는 것을 확인할 수 있다.
이때 reply() 안에 embed를 출력하는 방식을 꼭 확인해야 하는데, new EmbedBuilder()로 embed를 생성한 이후에는 꼭 객체 안에 배열의 형태로 출력해줘야 한다.
interaction.reply({ embeds: [embed] });
Axios와 cheerio
embed 테스트도 끝났으니 본격적으로 크롤링 코드를 작성해보도록 하겠다.
axios는 node.js에서 http 요청을 도와주는 라이브러리고, cheerio는 html 데이터를 파싱하는 라이브러리이다.
두 라이브러리를 charInfo.js 파일 상단에 불러와주도록 하자.
const axios = require('axios');
const cheerio = require('cheerio');
다음으로 데이터를 가져올 url 정보를 입력해야 하는데, maple.gg로 이동해서 캐릭터를 검색한 후 url을 살펴보면 다음과 같은 형식을 가지고 있는 것을 발견할 수 있다.
https://maple.gg/u/닉네임
이제 axios에서 get 방식으로 html을 가져와서 콘솔창에 찍어보도록 하자.
run: async ({ interaction }) => {
const nickName = interaction.options.get("닉네임").value;
const html = await axios.get(`https://maple.gg/u/${nickName}`);
console.log(html);
const embed = new EmbedBuilder().setTitle(`${nickName}`);
interaction.reply({ embeds: [embed] });
}
위 사진과 같이 다양한 정보가 담긴 object, 그리고 html 소스 전체를 불러온 것을 확인할 수 있다. html 소스가 시작되는 부분을 보면 data 키 밑에 소스가 담겨있는 것을 확인할 수 있는데, 이를 cheerio에 전달해주면 된다.
const $ = cheerio.load(html.data);
이제 본격적으로 캐릭터의 정보를 가져올 것인데, 먼저 가져올 정보들은 다음과 같다.
- 캐릭터의 코디 정보(이미지)
- 캐릭터가 속한 서버 정보
- 캐릭터의 레벨 및 경험치
- 캐릭터의 직업
- 캐릭터가 소속된 길드 이름
- 캐릭터의 무릉도장 최고기록 층수
- 캐릭터의 유니온 레벨
maple.gg에서 캐릭터 정보를 검색한 후 개발자 도구를 열어 html 소스를 볼 수 있는데, 가져올 정보가 어떤 className을 가진 어떤 태그 아래에 있는지 파악해야 정보를 가져올 수 있다.
예를 들어 user-summary-list라는 className을 가진 ul 태그 아래에 서버 이름/레벨/경험치/직업/인기도 정보가 나와있는 것을 확인할 수 있는데, 레벨과 직업 정보를 가져와보도록 하자.
주의: 주인장은 웹 크롤링을 처음 시도해봤고, 비효율적인 코드가 가득할 수 있습니다. 참고 부탁드립니다.
먼저 변수 하나에 해당 태그 아래에 있는 text들을 저장해주고 콘솔에 찍어보도록 하자.
const levelAndClass = $("ul.user-summary-list").text();
console.log(levelAndClass);
위 코드는 "user-summary-list"라는 className을 가진 ul태그 아래에 있는 text들을 가져오라는 뜻이다.
콘솔 출력 결과를 살펴보면 다음과 같다.
각각의 텍스트 왼쪽에 공백이 있고, 한 줄에 하나씩 출력되는 것으로 보아 개행 문자(\n)로 구분되어 있다.
여기서 필요한 정보만 추출하기 위하여 먼저 개행 문자를 기준으로 문자열을 잘라준 후 출력해보자.
const level = levelAndClass.split('\n');
console.log(level);
명령어를 실행했더니 다음과 같은 결과가 출력되었다.
[
'',
' 리부트',
' Lv.251(16.273%)',
' 섀도어',
' 인기도',
' 65',
' '
]
배열 안에 개행 문자로 구분된 각 문자열들이 저장된 모습이다. 여기서 우리는 레벨과 경험치. 직업을 가져올 것이니 좌측 공백을 잘라주기 위해 trimStart() 함수를 이용해서 변수에 담은 후 출력해보자.
const level = levelAndClass.split("\n")[2].trimStart();
console.log(level);
동일한 방식으로 직업도 가져와서 job 변수에 담아주고, embed에 해당 내용을 출력해보자.
const embed = new EmbedBuilder().setTitle(`${nickName}`).addFields(
{
name: "레벨",
value: `${level}`,
},
{
name: "직업",
value: `${job}`,
}
);
interaction.reply({ embeds: [embed] });
}
정상적으로 정보를 가져와서 출력하는 데 성공했다.
이미지 가져오기
이제 캐릭터 이미지와 서버 로고 파일을 불러올 것인데, 다시 html 소스를 보면 character-image라는 className을 가진 img 태그의 src 안에 이미지 링크가 있는 것을 확인할 수 있다.
이때는 attr("src")를 이용해서 이미지 링크를 불러올 수 있다. 아래 코드처럼 입력하니 성공적으로 이미지 링크를 불러올 수 있었다.
const charImg = $("img.character-image").attr("src");
하지만 .setImage()를 이용해 embed에 이미지를 첨부하려고 해도 이미지가 첨부되지 않았다. 이미지 크기가 너무 작아서 그런 것인가 싶어 Author의 icon으로 넣어봤더니 정상적으로 첨부되었다. 이미지 크기가 작은 것은 마음에 들지 않지만 일단 넘어가자.
.setAuthor({
name: nickName,
iconURL: charImg,
});
에러 처리
테스트를 진행하다가 오타를 내고 뒤늦게 깨달은 사실이 있는데, 바로 에러처리를 해주지 않았다는 것이다.
캐릭터 정보를 불러올 수 없는 경우에 대비하여 try catch문으로 간단하게 에러를 처리해주도록 하였다.
에러 처리 및 서버 아이콘, embed 색상 설정을 마친 charInfo.js 코드 전문을 첨부하고 이번 포스팅은 마무리하도록 하겠다.
다음 차례인 유니온과 무릉도장 층수, 길드 정보 관련해서는 애를 좀 먹었기 때문에 따로 다뤄볼 예정이다.
const { SlashCommandBuilder, EmbedBuilder } = require("discord.js");
const axios = require("axios");
const cheerio = require("cheerio");
module.exports = {
data: new SlashCommandBuilder()
.setName("캐릭터")
.setDescription("캐릭터의 정보를 출력합니다.")
.addStringOption((option) =>
option
.setName("닉네임")
.setDescription("정보를 가져올 캐릭터의 닉네임")
.setRequired(true)
),
run: async ({ interaction }) => {
const nickName = interaction.options.get("닉네임").value;
try {
const html = await axios.get(`https://maple.gg/u/${nickName}`);
const $ = cheerio.load(html.data);
const levelAndClass = $("ul.user-summary-list").text();
const level = levelAndClass.split("\n")[2].trimStart();
const job = levelAndClass.split("\n")[3].trimStart();
const charImg = $("img.character-image").attr("src");
const serverImg = $("img.align-middle").attr("src");
const embed = new EmbedBuilder()
.addFields(
{
name: "레벨",
value: `${level}`,
},
{
name: "직업",
value: `${job}`,
}
)
.setThumbnail(`https:${serverImg}`)
.setColor("White")
.setAuthor({
name: nickName,
iconURL: charImg,
});
interaction.reply({ embeds: [embed] });
} catch (error) {
interaction.reply(`에러 발생! ${error}`);
}
},
};
'Node.js > Discord.js' 카테고리의 다른 글
Discord.js로 메이플스토리 디스코드 봇 만들기 (5) (0) | 2023.08.27 |
---|---|
Discord.js로 메이플스토리 디스코드 봇 만들기 (4) (0) | 2023.08.26 |
Discord.js로 메이플스토리 디스코드 봇 만들기 (3) (0) | 2023.08.25 |
Discord.js로 메이플스토리 디스코드 봇 만들기 (1) (0) | 2023.08.24 |
Discord.js로 메이플스토리 디스코드 봇 만들기 (0) (0) | 2023.08.18 |