Profile picture

[Python] 디스코드 봇 구현하기 (discord.py)

JaehyoJJAng2024년 01월 18일

◾️ 개요

image
파이썬의 discord 모듈을 사용하여 알림을 보내는 디스코드 봇을 구현해보자!

사용자의 입력에 따라 행동하는 커맨드와 정해진 시간에 정기적으로 작동하는 기능을 구현해볼 것이다.


◾️ 앱(봇) 생성

우선 디스코드 개발자 포털에 들어가서 새로운 애플리케이션(Bot)을 생성해주도록 하자.
(디스코드 가입이 되어있지 않다면 가입 먼저)
image


▪️ 토큰 발급

애플리케이션을 생성하였다면 해당 애플리케이션의 토큰을 발급 받아야한다.
아래의 사진과 같이 해당 앱의 설정 - 봇 메뉴에서 'Reset Token'을 실행하여 토큰을 얻도록 하자.
image

💥 해당 토큰은 절대로 외부에 공유하지 않도록 하고, 노출되었다면 'Reset Token'으로 재갱신 해주도록 하자.


▪️ 권한 부여

Bot이 메시지 콘텐츠를 수신하기 위해 권한 설정이 필요하다. Privileged Gateway Intents 영역에 MESSAGE CONTENT INTENT를 활성화하고 [Save Changes]를 클릭하자.
image
Discord Privileged Gateway Intents는 Bot과 Application이 특정 민감한 데이터 및 기능에 액세스 할 수 있도록 허용하는 권한 시스템이다.

Gateway Intents 시스템은 2018년 Discord에서 도입했으며 Bot과 Application에서 명시적으로 요청하지 않는 한 특정 데이터 및 이벤트에 대한 액세스를 제한하여 서버 성능을 개선하고 사용자 개인 정보를 보호하도록 설계되었다.

Gateway Intent에는 "privileged"과 "unprivileged" 두 가지 유형이 존재한다.

privileged에는 서버 구성원 존재 및 구성원 데이터가 포함되며 unprivileged에는 비구성원 데이터 및 이벤트가 포함된다.

기본적으로 Discord Bot은 privileged에 액세스 할 수 없으며 Discord 개발자 포털을 통해 명시적으로 액세스를 요청해야 가능하다.

discord.errors.PrivilegedIntentsRequired: Shard ID None is requesting privileged intents that have not been explicitly enabled in the developer portal. It is recommended to go to https://discord.com/developers/applications/ and explicitly enable the privileged intents within your application's page. If this is not possible, then consider disabling the privileged intents instead.

만약 위와 같은 오류 메시지가 나왔다면 권한 설정이 필요하므로, 위 과정을 실시해주도록 하자.


▪️ 봇 초대하기

방금 생성한 봇을 내 채널에 초대해보자.

초대할 앱의 설정 - OAuth2 - URL Generator 메뉴에서 'bot'을 선택하자.

개발할 bot에 필요한 기능들을 체크하고, 화면 하단에 보이는 GENERATED URL을 복사하여 브라우저에서 실행해주자. 그러면 봇을 초대할 디스코드 서버를 선택할 수 있다.
image
image
image


연결이 완료되었다.
image


◾️ 봇 개발하기

▪️ 패키지 설치

pip를 이용하여 discord 패키지를 설치

pip install discord

▪️ 코드

Discord 특정 채널에 'Hello World' 메시지를 전송하는 간단한 코드이다.

Token은 토큰 발급에서 복사한 TOKEN을 입력하면 되고,
채널 ID의 경우 채널 ID를 확인하는 방법을 보고 channel_id에 원하는 채널의 ID를 입력해주면 된다.

from discord.ext import commands
import discord

class MyClient(discord.Client):
    async def on_ready(self):
        # 채널 객체 받아오기
        channel = self.get_channel(self._channel_id)
        await channel.send('Hello World')


def main() -> None:
    discord_token : str = token()
    channel_id : int = 1234565788

    intents = discord.Intents.default()
    intents.message_content = True

    client = MyClient(intents=intents)
    client._channel_id = channel_id
    client.run(discord_token)

if __name__ == '__main__':
    main()

• 특정 시간마다 실행

다음 코드는 매 1분마다 특정 채널에 'Hello World' 메시지를 전송하는 코드이다.

from discord.ext import commands,tasks
from config.config import token
import discord

class MyClient(discord.Client):
    async def on_ready(self):
        self.send_message.start()

    @tasks.loop(minutes=1)
    async def send_message(self):
        channel = self.get_channel(self._channel_id)
        await channel.send('Hello World')

def main() -> None:
    discord_token : str = token()
    channel_id : int = 1234565788

    intents = discord.Intents.default()
    intents.message_content = True

    client = MyClient(intents=intents)
    client._channel_id = channel_id
    client.run(discord_token)

if __name__ == '__main__':
    main()
  • tasks.loop() 데코레이터를 사용하여 send_message 함수를 1분 마다 반복적으로 실행.

• embed 적용

  • 위 예제(특정 시간마다 실행)코드를 참고하여 1분 마다 예쁜 메시지를 보내볼 수 있도록 embed를 적용해보자.

from discord.ext import commands,tasks
from config.config import token
import discord

class MyClient(discord.Client):
    async def on_ready(self):
        self.send_message.start()

    @tasks.loop(minutes=1)
    async def send_message(self):        
        embed = discord.Embed(
            title="인삿말",
            description="함께 인사해요",
            color=discord.Color.blue()
        )
        embed.set_author(name="작성자",icon_url="작성자 아이콘 URL")
        embed.set_thumbnail(url="섬네일 이미지 URL")
        embed.add_field(name="필드 이름", value="필드 값", inline=False)
        embed.add_field(name="또 다른 필드", value="또 다른 필드 값", inline=True)
        embed.set_footer(text="푸터 텍스트")

        channel = self.get_channel(self._channel_id)
        await channel.send(embed=embed)

def main() -> None:
    discord_token : str = token()
    channel_id : int = 1234565788

    intents = discord.Intents.default()
    intents.message_content = True

    client = MyClient(intents=intents)
    client._channel_id = channel_id
    client.run(discord_token)

if __name__ == '__main__':
    main()

image


• 상호작용

  • 봇과 인사하고 요일, 현재 시간에 대해 응답이 가능하도록 코드를 작성해보자.

from discord.ext import commands
from config.config import token
from datetime import datetime
import discord

class MyClient(discord.Client):
    async def on_ready(self):
        self.send_message.start()

    async def on_message(self,message):
        if message.content == 'ping':
            await message.channel.send(
                'pong{0.author.mention}'.format(message))
        else:
            answer = self.get_answer(text=message.content)
            await message.channel.send(answer)

    def get_now_time(self) -> str:
        return datetime.today().strftime('%H시 %M분')
    
    def get_day_of_week(self) -> str:
        weekday_list : list[str] = ['월요일', '화요일', '수요일', '목요일', '금요일', '토요일', '일요일']

        weekday = weekday_list[datetime.today().weekday()]
        date = datetime.today().strftime('%Y년 %m월 %d일')
        return f'{date}({weekday})'

    def get_answer(self,text: str) -> str:
        trim_text : str = text.replace(" ","")
        
        answer_dict : dict[str,str] = {
            '안녕': '안녕하세요, Bot 입니다.',
            '요일': f':calendar: 오늘은 {self.get_day_of_week()}입니다',
            '시간': f':clock: 현재 시간은 {self.get_now_time()}입니다.'}
        
        if trim_text == '':
            return '질문이 정상적이지 않습니다!'
        elif trim_text in answer_dict.keys():
            return answer_dict[trim_text]
        else:
            for key in answer_dict.keys():
                if key.find(trim_text) != -1:
                    return f'연관 단어 [ {key} ]에 대한 답변입니다.\n{answer_dict[key]}]'
            
            for key in answer_dict.keys():
                if answer_dict[key].find(text[1:]) != -1:
                    return f'질문과 가장 유사한 질문 [ {key} ]에 대한 답변입니다.\n{answer_dict[key]}'
        
        return f'{text} 은(는) 없는 질문입니다.'

def main() -> None:
    discord_token : str = token()
    channel_id : int = 1234565788

    intents = discord.Intents.default()
    intents.message_content = True

    client = MyClient(intents=intents)
    client._channel_id = channel_id
    client.run(discord_token)

if __name__ == '__main__':
    main()

image


◾️ 채널 ID 확인

Discord 채널 ID는 디스코드 서버 각 채널에 할당되는 고유 식별값이다. Discord를 이용하는데에는 채널 ID를 몰라도 문제될 게 없지만

다양한 API 사용이나 명령, Bot 활용 등을 위해서는 필요할 수 있다.

해당 챕터에서는 디스코드에서 채널 ID를 찾는 방법에 대해서 간단하게 설명해보겠다.


1. 개발자 모드 활성화

먼저 개발자 모드가 활성화되어 있는지 확인이 필요하다. 사용자 설정을 클릭해보자.
image


비활성화 상태라면 고급 -> 개발자 모드를 클릭하여 활성화해주자.
image


2. 채널 ID 복사

다시 서버로 돌아와서 원하는 채널을 마우스 우클릭 후 메뉴에서 "ID 복사하기"를 클릭하자. 이제 필요한 곳에 붙여넣기를 하면된다.
image


Loading script...