본문 바로가기
Discord/Discord Bot Python

Discord Bot 만들기 - 음성 채널 입장하기

by 깐테 2023. 5. 16.

디스코드 봇을 사용해본 경험이 있다면, 유튜브 혹은 사운드 클라우드 등의 앱에서 음악을 재생해주는 봇을 사용해 봤을 수 있다.

 

여기서는 그러한 음악 재생 봇들의 기본이 될 수 있는 방법인 "봇을 음성 채널에 입장시키는 방법"을 설명해보려 한다.

 

** 음성 채널 입장의 기본 코드는 discord.py 깃허브를 참조했다.

https://github.com/Rapptz/discord.py/tree/master/examples

 

GitHub - Rapptz/discord.py: An API wrapper for Discord written in Python.

An API wrapper for Discord written in Python. Contribute to Rapptz/discord.py development by creating an account on GitHub.

github.com

 

Basic - 음성 채널

  • 음성 채널에 입장하는 봇을 사용하기 전, PyNaCl 설치 필요
  • discord voice 확장 모듈도 설치 pip install discord.py[voice]
pip install PyNaCl

pip install discord.py[voice]

 

💡 만약 PyNaCl 오류가 발생하거나 설치가 되지 않는다면?

  • CMD 관리자 권한으로 실행 → py -3 -m pip install -U discord.py[voice]

 

음성 채널 확인

print(ctx.author.voice)

  • 해당 명령어는 명령어를 사용한 유저가 위치한 채널의 정보를 가져온다.
  • 해당 채널의 이름, 아이디, 위치, 비트레이트, 사용자 수 제한 등의 정보를 가져 오고, 없다면 None을 반환한다.

 

print(ctx.author.voice.channel)

  • 명령어를 사용한 유저가 위치한 채널의 이름을 가져온다.
  • 해당 채널의 이름이 존재하지 않거나 없다면 아무것도 반환하지 않는다.

 

음성 채널 입장

@bot.command()
async def join(ctx):
	if ctx.author.voice and ctx.author.voice.channel:
		channel = ctx.author.voice.channel
		await channel.connect()
	else:
		await ctx.send("음성 채널이 존재하지 않습니다.")

 

음성 채널 퇴장

@bot.command()
async def out(ctx):
	await bot.voice_clients[0].disconnect()

 

소스 코드

import discord, asyncio
from discord.ext import commands
from dico_token import Token

# 수정. 2024.02.22
intents = discord.Intents.default()
intents.message_content = True

bot = commands.Bot(
    command_prefix=commands.when_mentioned_or("!"),
    description='디스코드 입장을 위한 테스트 코드',
    intents=intents,
)

@bot.event
async def on_ready():
    print('{0.user} 봇을 실행합니다.'.format(bot))

# 2024.08.24 수정.
@bot.command(aliases=['입장'])
async def join(ctx):
    channel = ctx.author.voice.channel
    
    if ctx.author.voice is None:
        await ctx.send("사용자가 존재하는 음성 채널을 찾지 못했습니다. 음성 채널에 입장해주세요.")
        raise commands.CommandInvokeError("사용자가 존재하는 음성 채널을 찾지 못했습니다.")
    
    if ctx.voice_client is not None:
        await ctx.send("봇이 사용자가 있는 채널에 입장합니다.")
        print("음성 채널 정보: {0.author.voice}".format(ctx))
        print("음성 채널 이름: {0.author.voice.channel}".format(ctx))
        return await ctx.voice_client.move_to(channel)
    
    await channel.connect()
        
    # if ctx.author.voice and ctx.author.voice.channel:
    #     channel = ctx.author.voice.channel
    #     await ctx.send("봇이 {0.author.voice.channel} 채널에 입장합니다.".format(ctx))
    #     await channel.connect()
    #     print("음성 채널 정보: {0.author.voice}".format(ctx))
    #     print("음성 채널 이름: {0.author.voice.channel}".format(ctx))
    # else:
    #     await ctx.send("음성 채널에 유저가 존재하지 않습니다. 1명 이상 입장해 주세요.")

@bot.command(aliases=['나가기'])
async def out(ctx):
    # await bot.voice_clients[0].disconnect()
    try:
        await ctx.voice_client.disconnect()
        await ctx.send("봇을 {0.author.voice.channel} 에서 내보냈습니다.".format(ctx))
    except IndexError as error_message:
        print(f"에러 발생: {error_message}")
        await ctx.send("{0.author.voice.channel}에 유저가 존재하지 않거나 봇이 존재하지 않습니다.\\n다시 입장후 퇴장시켜주세요.".format(ctx))
    except AttributeError as not_found_channel:
        print(f"에러 발생: {not_found_channel}")
        await ctx.send("봇이 존재하는 채널을 찾는 데 실패했습니다.")

bot.run(Token)
  • 입장 명령어: !입장, !join
    퇴장 명령어: !나가기, !out
  • 음성 채널에 유저가 존재하는 경우에만 봇을 불러올 수 있음.
  • 코드 실행 종료 후에도 봇이 채널에 남아있는 경우 코드를 다시 실행할 때 봇을 찾지 못하는 에러가 발생.
  • 해당 경우에는 봇을 재 입장 후 퇴장시키면 해결.

결과

 

 

Embed를 이용한 디스코드 봇 음성채널 구성

Embed

Embed 

 

API Reference

Loads the libopus shared library for use with voice. If this function is not called then the library uses the function ctypes.util.find_library() and then loads that one if available. Not loading a library and attempting to use PCM based AudioSources will

discordpy.readthedocs.io

  • discord python에서 제공하는 API
  • 디스코드 메시지나 공지가 박스 형태로 표시되거나 이모지를 이용해 반응을 표시하는 등의 봇들을 본 경험이 있을 것.
  • 다양한 색상, 제목, 내용 꾸미기, 인라인 기능 등을 제공한다.

 

소스 코드

import discord, asyncio
from discord.ext import commands
from dico_token import Token

# 수정. 2024.02.22
intents = discord.Intents.default()
intents.message_content = True

bot = commands.Bot(
    command_prefix=commands.when_mentioned_or("!"),
    description='디스코드 입장을 위한 테스트 코드',
    intents=intents,
)

@bot.event
async def on_ready():
    print('{0.user} 봇을 실행합니다.'.format(bot))

# 2024 08 24 수정
@bot.command(aliases=['입장'])
async def join(ctx):
    embed = discord.Embed(title = "디스코드 봇 도우미(개발용)", description = "음성 채널 개발용 디스코드 봇",color=0x00ff56)
    channel = ctx.author.voice.channel
    
    if ctx.author.voice is None:
        embed.add_field(name=':exclamation:', value="음성 채널에 유저가 존재하지 않습니다. 1명 이상 입장해주세요.")
        await ctx.send(embed=embed)
        raise commands.CommandInvokeError("사용자가 존재하는 음성 채널을 찾지 못했습니다.")
    
    if ctx.voice_client is not None:
        embed.add_field(name=":robot:",value="사용자가 있는 채널로 이동합니다.", inline=False)
        await ctx.send(embed=embed)
        print("음성 채널 정보: {0.author.voice}".format(ctx))
        print("음성 채널 이름: {0.author.voice.channel}".format(ctx))
        return await ctx.voice_client.move_to(channel)
        
    await channel.connect()
    
@bot.command(aliases=['나가기'])
async def out(ctx):
    try:
        embed = discord.Embed(color=0x00ff56)
        embed.add_field(name=":regional_indicator_b::regional_indicator_y::regional_indicator_e:",value=" {0.author.voice.channel}에서 내보냈습니다.".format(ctx),inline=False)
        await bot.voice_clients[0].disconnect()
        await ctx.send(embed=embed)
    except IndexError as error_message:
        print(f"에러 발생: {error_message}")
        embed = discord.Embed(color=0xf66c24)
        embed.add_field(name=":grey_question:",value="{0.author.voice.channel}에 유저가 존재하지 않거나 봇이 존재하지 않습니다.\\n다시 입장후 퇴장시켜주세요.".format(ctx),inline=False)
        await ctx.send(embed=embed)
    except AttributeError as not_found_channel:
        print(f"에러 발생: {not_found_channel}")
        embed = discord.Embed(color=0xf66c24)
        embed.add_field(name=":x:",value="봇이 존재하는 채널을 찾는 데 실패했습니다.")
        await ctx.send(embed=embed)

bot.run(Token)
  • 위의 메시지로 구성한 소스 코드를 Embed로 재 작성한 코드
  • EmbedTitle, description을 설정해주지 않아도 Embed로 구성 가능

하지만 add_filedName, value빈 값을 가질 수 없도록 구성되어 있다.

 

결과

반응형