미사용/(舊)디스코드봇 강좌

디스코드봇(파이썬) 06 | discord.py의 이벤트함수를 알아보고 서로 상호작용을 해보자.

건유1019 2020. 4. 18. 17:03

이번에는 discord.py의 이벤트 함수와, 이것을 기반으로 서로 상호작용을 해보겠습니다.


이벤트 함수?

이벤트 함수란, 특정 행동이 발생할 때 작동하는 것으로 예를 들어 지난 2편에서 진행한, on_message, on_ready 이런 것들이 이벤트 함수라고 합니다. 이제 이 이벤트 함수들에 대해 알아봅시다.

 

1. on_ready

@client.event
async def on_ready():
	#내용
	return

on_ready는 만약에 정상적으로 봇이 작동되었을 때, 작동하게 되는 함수입니다. 대부분 아래의 코드처럼, 이 함수를 통해서 정상적으로 부팅되었다는 것을 알려줍니다.

@client.event
async def on_ready(): 
	print("디스코드 봇 로그인이 완료되었습니다.")
	print("디스코드봇 이름:" + client.user.name)
	print("디스코드봇 ID:" + str(client.user.id))
	print("디스코드봇 버전:" + str(discord.__version__))
	print('------')
	await client.change_presence(status=discord.Status.online, activity=discord.Game("서술"))

위와 같이 정상적으로 부팅될 경우, 상태메세지와 봇이 정상적으로 로그인되었다고 알려줍니다.

 

2. on_message(message)

@client.event
async def on_message(message):
	#내용
	return

on_message는 만약에 해당봇이 들어가 있는 방에서 누군가가 메시지를 보낼 경우 작동하게 됩니다. 메시지가 오면, message라는 변수 안에 들어가게 되며, 이 message 안의 값은 지난 2편에서 설명했으므로 요약하겠습니다.

참고자료: [디스코드봇(파이썬) 강좌] - 디스코드봇(파이썬) 02 | 기본적인 내용을 주고 받자.

 

카카오톡 봇 기준에선 이것이 자바스크립트의 function response()와 똑같습니다. 단지 비동기로 들어온다는 것과, 변수가 한 번에 들어온다는 차이점이 존재합니다.

 

3. on_error(event, *args, **kwargs)

@client.event
async def on_error(event, *args, **kwargs):
	#내용
	return

에러가 발생하면, 작동되는 함수입니다. event에서는 오류가 발생한 함수명을 알려줍니다. 만약에 on_message에서 오류가 발생하면, event에는 on_message가 반환됩니다. 그리고 *args에서는 그 message를 돌려줍니다. **kwargs 에는 "예외를 발생시킨 이벤트에 대한 키워드 인수."라고 하지만, 사실상 사용해본 적은 없습니다. 에러 값은 sys.exc_info를 통해서 읽을 수 있다고 합니다. 만약에 on_error를 활용해서 on_message의 에러가 발생한 것을 잡아낸다고 가정해봅시다.

 

 

@client.event
async def on_error(event, *args, **kwargs):
	if event == "on_message": #on_message에서 발생했을때 작동합니다.
    	message = *args[0] #args값에는 여러개가 들어올수도 있으니, 첫번째껏만 잡아줍니다.
    	exc = sys.exc_info() #sys를 활용해서 에러를 확인합니다.
        message.channel.send(str(exc[0].__name__) + "" + str(exc[1])) #그 에러를 출력합니다.
	return​

위와 같이 에러가 발생하면 이렇게 잡아줄 수 있습니다.

 

4. on_typing(channel, user, when)

@client.event
async def on_typing(channel, user, when):
	#내용
	return

만약에 유저가 메시지를 작성 중이면, 이 함수가 작동하게 됩니다. 아까 on_message에 비해 이것은 사용자가 작성 중이기만 하면 바로 작동합니다. channel 에는 채널 값, user에는 유저 값, when에는 datetime 형으로 시간이 반환됩니다.

@client.event
async def on_typing(channel, user, when):
	await channel.send(str(user) + "이 작성중!")
	return

이렇게 만약에 누가 작성 중이라면 'ㅇㅇ가 작성중!'라고 잡아낼 수 있습니다.

 

5. on_message_delete(message)

@client.event
async def on_message_delete(message):
	#내용
	return

만약에 메시지가 삭제되면 작동되는 함수입니다. message값 안에 삭제된 메시지의 내용이 반환됩니다. 메시지 값은 전편 2편에서 알려주던 내용과 같습니다. on_message처럼 message.content 하면 삭제된 메시지의 값을 알 수 있습니다. 이를 응용해서 아래의 코드처럼 작성하실 수 있습니다.

@client.event
async def on_message_delete(message):
	await message.channel.send("메세지 삭제 감지(" + str(message.author) + "): " + message.content)
	return

이렇게 해서 만약에 특정인이 메시지를 삭제했으면 이 봇이 그 삭제한 메시지를 복구시켜줍니다. 

 

6. on_bulk_message_delete(messages)

@client.event
async def on_bulk_message_delete(messages):
	#내용
	return

이건 5번처럼 하나하나 삭제된 것이 아닌, 한 번에 메시지들이 단체로 삭제되었을 경우 작동되게 합니다. 그 메시지들은 messages에 리스트로 반환됩니다.

 

7. on_message_edit(before, after)

@client.event
async def on_message_edit(before, after):
	#내용
	return

만약에 메시지가 수정되었으면 함수가 작동합니다. before과 after에는 message값이 들어가 있으나, before은 변경 전 메시지, after에는 변경 후 메시지 값이 들어가게 됩니다.

@client.event
async def on_message_edit(before, after):
	await before.channel.send(before.content + "->" + after.content)
	return

이렇게 만약에 "안녕하세요"라는 내용을 "반갑습니다"라고 수정했으면, 이 봇은 "안녕하세요->반갑습니다"라고 보낼 것입니다.

메세지 수정시 작동.

위 사진처럼 메시지가 수정되면 작동되게 됩니다.

8. on_reaction_add(reaction, user), on_reaction_remove(reaction, user)

8번부터는 중첩되는 부분에서는 같이 작성하였습니다.

@client.event
async def on_reaction_add(reaction, user):
	#내용
	return
@client.event
async def on_reaction_remove(reaction, user):
	#내용
	return

on_reaction_add는 반응이 추가되면 작동되게 됩니다. reaction에는 반응이, user에는 그 반응을 추가한 유저를 출력합니다. 반대로 on_reaction_remove는 그 반응이 삭제될 경우에 작동되게 되며, reaction에는 반응 값, user에는 반응을 했었던 유저를 출력해줍니다.

 

9. on_reaction_clear(message, reactions)

@client.event
async def on_reaction_clear(message, reactions):
	#내용
	return

아까 9번에서 on_reaction_remove이건 반응이 한 개씩 제거됐을 경우 작동합니다. 물론 이건 한 번에 삭제될 경우 작동되게 됩니다. message에는 그 내용 값을 반환하며, reactions에는 리스트형으로 삭제된 반응들을 전부 반환합니다. 

 

10. on_member_join(member), on_member_remove(member)

@client.event
async def on_member_join(member):
	#내용
	return
@client.event
async def on_member_remove(member):
	#내용
	return

서버에 새로운 유저가 들어왔을 때 작동하게 됩니다. on_member_join을 이용해서 환영인사를 작성하실 수 있습니다. member값에는 신규 유저, 혹은 나간 유저의 값이 돌아오게 됩니다.

@client.event
async def on_member_join(member):
	await member.guild.get_channel(채널 ID).send(member.mention + "님이 새롭게 접속했습니다. 환영해주세요!")
	return

이렇게 해주시면, 만약에 특정 유저가 서버에 들어오게 된다면, 그 "(유저)가 새롭게 접속했습니다. 환영해주세요!" 라고 말하게 됩니다. 여기서 get_channel의 채널 ID를 수정해주셔야 하는데요.

해당 채널 -> 우클릭 -> ID 복사하기

이렇게 복사한 값을 채널 ID에 넣어주시면 됩니다. 절때, "1234567890" 이런 식으로 말고 그냥 1234567890 식으로 넣어주셔야 합니다.

 

11. on_user_update(before, after)

@client.event
async def on_user_update(before, after):
	#내용
	return

만약 해당 봇이 특정 서버에 있을 경우에서 유저가 유저 정보(아바타, 설명, 닉네임)를 변경했을 때, 이 함수가 작동되게 되며 before과 after에는 유저 정보가 들어가 있습니다. 그러나 이거의 경우 악용 우려가 있어 예제 문을 작성하지 않겠습니다.

 

12. on_guild_join(guild), on_guild_remove(guild)

@client.event
async def on_guild_join(guild):
	#내용
	return
@client.event
async def on_guild_remove(guild):
	#내용
	return

봇이 특정 서버에 접속했을 때 혹은 제거 됐을 때 작동되게 됩니다. guild 에는 새로 접속한 혹은 나간 서버를 불러옵니다. 이것을 응용해서 특정 서버에 접속했다고 메시지를 주실 수 있습니다.

@client.event
async def on_guild_join(guild):
	print("새로운 서버에 접속!" + str(guild))
	return

 이렇게 하시면 새로운 서버에 접속 시 콘솔 창에 "새로운 서버에 접속! (서버명)"라고 뜹니다.

 

13. on_guild_update(before, after)

 

@client.event
async def on_guild_update(before, after):
	#내용
	return

서버 정보가 바뀌었을 때 작동합니다. 예를 들어 서버명, AFK채널, 국가설정, 서버 아이콘 등등의 서버 정보가 바뀌면 작동되는 코드입니다.

 

14. on_guild_role_create(role), on_guild_role_delete(role)

@client.event
async def on_guild_role_create(role):
	#내용
	return
@client.event
async def on_guild_role_delete(role):
	#내용
	return

역할이 추가되거나 제거되면 작동합니다. role값에는 새로 추가된 역할 혹은 제거된 역할 값이 들어옵니다.

 

15. on_guild_role_update(before, after)

@client.event
async def on_guild_role_update(before, after):
	#내용
	return

역할이 업데이트되면 들어옵니다. before에는 업데이트 전 역할, after에는 업데이트 후의 역할이 들어오게 됩니다.

 

16. on_guild_emojis_update(guild, before, after)

@client.event
async def on_guild_emojis_update(guild, before, after):
	#내용
	return

길드의 이모지 정보가 변경(추가, 제거 포함)되면 들어옵니다. guild값에는 서버 정보, before에는 변경 전 이모 지목 록, after에는 변경 후 이모 지목 록이 들어오게 됩니다.

 

17. on_voice_state_update(member, before, after)

@client.event
async def on_voice_state_update(member, before, after):
	#내용
	return

음성 채팅방의 정보가 업데이트되면 작동합니다. 여기서 음성 채팅방의 정보가 업데이트된다는 소리가 무엇이냐면, 헤드셋 음소거, 마이크 음소거부터, 유저가 접속 유/무에 따라 값이 작동하게 됩니다. 예를 들자면 유저가 그 서버에서 음성방에 접속하게 되면 해당 함수가 작동합니다. member에는 유저 값, before과 after에는 음성방 전/후 값을 알려줍니다.

 

18. on_member_ban(guild, user), on_member_unban(guild, user)

@client.event
async def on_member_ban(guild, user):
	#내용
	return
@client.event
async def on_member_unban(guild, user):
	#내용
	return

유저가 밴 혹은 언밴되었을때 작동하게 되며, guild에는 그 서버와 user에는 그 유저를 알려줍니다.

 

19. on_invite_create(invite), on_invite_delete(invite)

@client.event
async def on_invite_create(invite):
	#내용
	return
@client.event
async def on_invite_delete(invite):
	#내용
	return

초대 코드가 생성되거나 제거 됐을 때 작동하게 됩니다. invite값에는 초댓값이 들어가 있으며, invite.url으로 초대 링크를 불러올 수 있습니다.


드디어, 이벤트 목록을 대부분 작성했습니다. 그러나, 위에 있는 것들 말곤 대부분 쓸 곳이 없으며, 사실상 더 많은 이벤트 함수가 존재하지만, 사실상 그것들까지는 쓸 때가 없기 때문에 작성하질 않겠습니다. 정 궁금하시다면. 이곳(링크)을 참조하시면 되겠습니다. 이제 이걸 기반으로 서로 상호작용을 해봅시다.


서로 상호작용을 해보자!

유저-> 디스코드 봇-> 유저-> 디스코드 봇... 등 서로 상호작용을 해봅시다. 상호작용은 아래의 코드를 통해 사용됩니다.

msg =  await client.wait_for('message', timeout=60.0, check=check)

그냥 이것을 넣으시면 바로 사용할 수 있는 것이 아닙니다;; 이제 이 사용법에 대해 알아봅시다.

 

wait_for을 사용하는 것 맞으나, () 안에 들어가는 값들에 대해 알아봅시다.

L 첫 번째로 들어가는 인자는 이벤트 함수입니다. 위에 있는 이벤트 함수명을 "on_"을 빼주신 채로 넣어주시면 작동하게 됩니다.

L 두 번째로 들어가는 인자는 timeout입니다. 타임아웃은 초단위로 설정해주실 수 있으며, 만약 시간 초과가 될 경우

except asyncio.TimeoutError: 를 통해 잡아주실 수 있습니다. 물론 try를 사용해주신 상태이여 합니다.

try:
	msg = await client.wait_for('message', timeout=60.0, check=check)
except asyncio.TimeoutError:
	#시간초과
else:
	#정상작동

L 세 번째로 들어가는 인자는 check입니다. 만약 들어오는 값이 일치하면 반환하게 됩니다. check 경우는 아래의 코드처럼 함수를 미리 만들어줘야 합니다.

def check(m):
	return m.channel.id == message.channel.id and m.author == message.author

이렇게 on_message 문안에서 wait_for로 작성한 유저가 다른 메시지를 작성하기 위해 대기하기 위한 check 문입니다.

채널명이랑 작성 유저가 같을 경우 True값이 반환되므로, 이럴 경우 msg값에 메시지가 반환됩니다. check 함수에는 message라는 이벤트이므로, on_message와 동일하게 작동됩니다.

 

그런데 만약에 reaction_add 등의 인자가 2개일 경우에는 어떻게 할까요?

reaction, user = await client.wait_for('reaction_add', timeout=60.0, check=check)

위와 같이 사용해주시면 됩니다!


응용: 가위바위보를 만들자!

이제 client.wait_for를 활용하여, 가위바위보를 작성해봅시다.

@client.event
async def on_message(message):
	if message.content.startswith('=가위바위보'):
		rsp = ["가위","바위","보"]
		embed = discord.Embed(title="가위바위보",description="가위바위보를 합니다 3초내로 (가위/바위/보)를 써주세요!", color=0x00aaaa)
		channel = message.channel
		msg1 = await message.channel.send(embed=embed)
		def check(m):
			return m.author == message.author and m.channel == channel
		try:
			msg2 = await client.wait_for('message', timeout=3.0, check=check)
		except asyncio.TimeoutError:
			await msg1.delete()
			embed = discord.Embed(title="가위바위보",description="앗 3초가 지났네요...!", color=0x00aaaa)
			await message.channel.send(embed=embed)
			return
		else:
			await msg1.delete()
			bot_rsp = str(random.choice(rsp))
			user_rsp  = str(msg2.content)
			answer = ""
			if bot_rsp == user_rsp:
				answer = "저는 " + bot_rsp + "을 냈고, 당신은 " + user_rsp + "을 내셨내요.\n" + "아쉽지만 비겼습니다."
			elif (bot_rsp == "가위" and user_rsp == "바위") or (bot_rsp == "보" and user_rsp == "가위") or (bot_rsp == "바위" and user_rsp == "보"):
				answer = "저는 " + bot_rsp + "을 냈고, 당신은 " + user_rsp + "을 내셨내요.\n" + "아쉽지만 제가 졌습니다."
			elif (bot_rsp == "바위" and user_rsp == "가위") or (bot_rsp == "가위" and user_rsp == "보") or (bot_rsp == "보" and user_rsp == "바위"):
				answer = "저는 " + bot_rsp + "을 냈고, 당신은 " + user_rsp + "을 내셨내요.\n" + "제가 이겼습니다!"
			else:
				embed = discord.Embed(title="가위바위보",description="앗, 가위, 바위, 보 중에서만 내셔야죠...", color=0x00aaaa)
				await message.channel.send(embed=embed)
				return
			embed = discord.Embed(title="가위바위보",description=answer, color=0x00aaaa)
			await message.channel.send(embed=embed)
			return

일단 효율적인 강의를 진행하기 위해 완성본을 먼저 적고 설명하겠습니다.

rsp = ["가위","바위","보"]
embed = discord.Embed(title="가위바위보",description="가위바위보를 합니다 3초내로 (가위/바위/보)를 써주세요!", color=0x00aaaa)
channel = message.channel
msg1 = await message.channel.send(embed=embed)

rsp 함숫값에 "가위", "바위", "보"의 3가지 값을 리스트로 넣어주고, 가위바위보를 작성해달라고 합니다.

def check(m):
	return m.author == message.author and m.channel == channel
try:
	msg2 = await client.wait_for('message', timeout=3.0, check=check)
except asyncio.TimeoutError:
	await msg1.delete()
	embed = discord.Embed(title="가위바위보",description="앗 3초가 지났네요...!", color=0x00aaaa)
	await message.channel.send(embed=embed)
	return

check라는 변수를 제작해주시고, 채널 값이 동일한지 확인해줍니다. 그리고 client.wait_for를 사용하여, "가위, 바위, 보" 3가지 중 한 가지를 출력하기까지 기다립니다. 또한 3초 후 타임아웃을 하라고 설정했으며, 3초 후 3초가 지났다고 말하면서 동작을 멈춥니다.

else:
	await msg1.delete()
	bot_rsp = str(random.choice(rsp))
	user_rsp  = str(msg2.content)
	answer = ""
	if bot_rsp == user_rsp:
		answer = "저는 " + bot_rsp + "을 냈고, 당신은 " + user_rsp + "을 내셨내요.\n" + "아쉽지만 비겼습니다."
	elif (bot_rsp == "가위" and user_rsp == "바위") or (bot_rsp == "보" and user_rsp == "가위") or (bot_rsp == "바위" and user_rsp == "보"):
		answer = "저는 " + bot_rsp + "을 냈고, 당신은 " + user_rsp + "을 내셨내요.\n" + "아쉽지만 제가 졌습니다."
	elif (bot_rsp == "바위" and user_rsp == "가위") or (bot_rsp == "가위" and user_rsp == "보") or (bot_rsp == "보" and user_rsp == "바위"):
		answer = "저는 " + bot_rsp + "을 냈고, 당신은 " + user_rsp + "을 내셨내요.\n" + "제가 이겼습니다!"
	else:
		embed = discord.Embed(title="가위바위보",description="앗, 가위, 바위, 보 중에서만 내셔야죠...", color=0x00aaaa)
		await message.channel.send(embed=embed)
		return
	embed = discord.Embed(title="가위바위보",description=answer, color=0x00aaaa)
	await message.channel.send(embed=embed)
	return

만약 가위, 바위, 보를 작성했다면 아까 "가위바위보를 합니다 3초 내로 (가위/바위/보)를 써주세요!"라는 내용을 지우고, 봇도 3가지(가위, 바위, 보)를 택한 후 if문과 elif문을 활용해서 이겼는지, 비겼는지, 졌는지를 판단합니다. 그렇다고 만약에 다른 내용을 작성했을 경우 "앗, 가위, 바위, 보 중에서만 내셔야죠..."라고 말합니다.

 

만약 이겼는지, 비겼는지, 졌는지 판단이 끝나면, 값을 보내주고 함수가 끝나게 됩니다. 이 기능은 YBOT에 실제로 탑재한 기능 중 하나이며, 실제로 YBOT을 통하여 이용해 보실수 있습니다.

실제로 시현해보는 모습.


이상으로, 강의를 마치며 다음에는 저번에 예고했던 author 등의 값에 대해 파악해봅시다. 이번에는 강의가 예정보다 좀 많이 지연되서 나왔습니다. 죄송합니다. 다음에는 맞추도록 노력하겠습니다.