본문으로 바로가기
728x90
반응형

이전글보기

[정보/Discord Bot] - [discord.py 2.0] 05. 봇 기본설정하기

[정보/Discord Bot] - [discord.py 2.0] 06. 봇 명령어 추가하기

[정보/Discord Bot] - [discord.py 2.0] 07. 대화에 Embed 추가하기

[정보/Discord Bot] - [discord.py 2.0] 08. Embed 꾸미기

[정보/Discord Bot] - [discord.py 2.0] 09. Cogs를 생성하여 소스코드 분할하기


이번 포스팅에서는 저번 포스팅에서 작성했던 Cogs 들을 Bot의 재시작 없이 수정사항을 적용할 수 있도록 할겁니다.

 

참고할 메소드는 아래 두 메소드입니다.

load_extension 호출시 발생가능한 exception
unload_extension 호출시 발생 가능한 exception

extension 을 load하고 unload 하는 메소드죠 ! 

위 메소드를 호출할 때, 이미 load 되어있는 cogs를 한번더 load 한다던지, load 되어있지 않은 cogs를 unload 한는 경우들은 모두 exception을 발생시키고 있으므로 확인해주시면 좋습니다.

 

명령어를 추가하는 위치는 main.py 에다가 추가합니다.

왜나하면.. Cogs에다가 추가할 경우, unload 될 경우 Bot의 재시작 말고는 답이 없잖아요 ...?

 

먼저 Unload 동작의 코드부터 보도록 하겠습니다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@app.command(name="unload")
async def unload_extension(ctx, extension=None):
    if extension is not None:
        await unload_function(extension)
        await ctx.send(f":white_check_mark: {extension}기능을 종료했습니다!")
    else:
        await unload_function(None)
        await ctx.send(":white_check_mark: 모든 확장기능을 종료했습니다!")
 
 
async def unload_function(extension=None):
    if extension is not None:
        try:
            await app.unload_extension(f"Cogs.{extension}")
        except (commands.ExtensionNotLoaded, commands.ExtensionNotFound):
            pass
    else:
        for filename in os.listdir("Cogs"):
            if filename.endswith(".py"):
                try:
                    await app.unload_extension(f"Cogs.{filename[:-3]}")
                except (commands.ExtensionNotLoaded, commands.ExtensionNotFound):
                    pass
cs

unload 의 경우, reload 할때도 사용되기 때문에, unload 하는 동작만 따로 메소드(unload_function)로 만들었습니다 

@app.command(name="unload")

1 : 봇의 재실행 없이 명령어로 unload하는것이기 때문에 그 명령어를 등록합니다

 

async def unload_extension(ctx, extension=None):

2 : unload 명령어에 대한 메소드를 생성합니다. 이때 추가 매개변수로 extension을 받습니다.
이 extension은 이후에 사용자가 특정 Cogs를 비활성화 할 때, 문자열을 받습니다.

 

if extension is not None:

3,12 : extension 의 조건이 있을 경우 이므로, 사용자가 추가 명령어를 포함하여 unload 만 입력했을때의 조건문 입니다.

 

await unload_function(extension)

4,7 : 생성한 unload_function 메소드를 추가로 호출하며 사용자로부터 입력받은 추가 명령어를 인자로 전달합니다.

 

await ctx.send(f":white_check_mark: {extension}기능을 종료했습니다!")
await ctx.send(":white_check_mark: 모든 확장기능을 종료했습니다!")

5,8 : unload를 완료 후, 서버에 메세지를 전달합니다.

 

async def unload_function(extension=None):

11 : 반복되는 동작을 재사용하기 위해 추가로 메소드를 정의합니다

 

try:
    await app.unload_extension(f"Cogs.{extension}")
except (commands.ExtensionNotLoaded, commands.ExtensionNotFound):
    pass

14 : 사용자로부터 입력받은 cogs 이름으로 unload_extension을 시도합니다

15,16 : unload_extension을 시도하는 도중 예외가 발생할 경우 무시(pass) 합니다. 어차피 비활성화 동작인데, throw 되는 예외는 모두 활성화가 되지 않은 것이나 다름없으므로 무시해도 되는것으로 판단합니다.

 

for filename in os.listdir("Cogs"):
    if filename.endswith(".py"):

18 : Bot이 실행되고 있는 폴더 내에 존재하는 Cogs 안에 포함된 파일을 확인합니다.

19 : 18 line 에서 확인된 파일 중, 파일의 확장자가 .py로 끝나는 파일을 찾아내는 조건식입니다.

 

여기까지가 unload 동작을 위한 소스코드 입니다.

다음으로 Reload 동작의 코드를 보도록 하겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@app.command(name="reload")
async def reload_extension(ctx, extension=None):
    if extension is not None:
        await unload_function(extension)
        try:
            await app.load_extension(f"Cogs.{extension}")
        except commands.ExtensionNotFound:
            await ctx.send(f":x: '{extension}'을(를) 파일을 찾을 수 없습니다!")
        except (commands.NoEntryPointError, commands.ExtensionFailed):
            await ctx.send(f":x: '{extension}'을(를) 불러오는 도중 에러가 발생했습니다!")
        else:
            await ctx.send(f":white_check_mark: '{extension}'을(를) 다시 불러왔습니다!")
    else:
        for filename in os.listdir("Cogs"):
            if filename.endswith(".py"):
                await unload_function(filename[:-3])
                try:
                    await app.load_extension(f"Cogs.{filename[:-3]}")
                except commands.ExtensionNotFound:
                    await ctx.send(f":x: '{filename[:-3]}'을(를) 파일을 찾을 수 없습니다!")
                except (commands.NoEntryPointError, commands.ExtensionFailed):
                    await ctx.send(f":x: '{filename[:-3]}'을(를) 불러오는 도중 에러가 발생했습니다!")
        await ctx.send(":white_check_mark: reload 작업을 완료하였습니다!")
cs
reload 동작에는 위에서 만들어뒀던 unload_function을 재사용하였습니다.
 
@app.command(name="reload")

1 : 봇의 재실행 없이 명령어로 다시 reload하는것이기 때문에 그 명령어를 등록합니다

 

async def reload_extension(ctx, extension=None):

2 : reload 명령어에 대한 메소드를 생성합니다. 이때 추가 매개변수로 extension을 받습니다.
이 extension은 이후에 사용자가 특정 Cogs를 리로드 할 때, 문자열을 받습니다.

 

if extension is not None:

3 : extension 의 조건이 None이 아닐때 이므로, 즉 사용자가 특정 Cog 모듈을 리로드 하기위 해 추가 명령어를 입력했을때 입니다.

 

await unload_function(extension)

4,16 : load하기 전에 먼저 실행되어있는 모듈을 unload 합니다. 

 

        try:
            await app.load_extension(f"Cogs.{extension}")
        except commands.ExtensionNotFound:
            await ctx.send(f":x: '{extension}'을(를) 파일을 찾을 수 없습니다!")
        except (commands.NoEntryPointError, commands.ExtensionFailed):
            await ctx.send(f":x: '{extension}'을(를) 불러오는 도중 에러가 발생했습니다!")
        else:
            await ctx.send(f":white_check_mark: '{extension}'을(를) 다시 불러왔습니다!")

5~12, 17~22 : load_extension 메소드를 호출하는 동안 발생하는 오류를 예외처리 합니다.

 

완성된 main.py 소스코드입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import asyncio
import os
import discord
from discord.ext import commands
 
intents = discord.Intents.all()
app = commands.Bot(command_prefix='!', intents=intents)
 
 
async def load_extensions():
    for filename in os.listdir("Cogs"):
        if filename.endswith(".py"):
            await app.load_extension(f"Cogs.{filename[:-3]}")
    # cog 하나씩 불러오기
    # activate_list = ["ping"]
    # for name in activate_list:
    #     await app.load_extension(f"Cogs.{name}")
 
 
@app.command(name="reload")
async def reload_extension(ctx, extension=None):
    if extension is not None:
        await unload_function(extension)
        try:
            await app.load_extension(f"Cogs.{extension}")
        except commands.ExtensionNotFound:
            await ctx.send(f":x: '{extension}'을(를) 파일을 찾을 수 없습니다!")
        except (commands.NoEntryPointError, commands.ExtensionFailed):
            await ctx.send(f":x: '{extension}'을(를) 불러오는 도중 에러가 발생했습니다!")
        else:
            await ctx.send(f":white_check_mark: '{extension}'을(를) 다시 불러왔습니다!")
    else:
        for filename in os.listdir("Cogs"):
            if filename.endswith(".py"):
                await unload_function(filename[:-3])
                try:
                    await app.load_extension(f"Cogs.{filename[:-3]}")
                except commands.ExtensionNotFound:
                    await ctx.send(f":x: '{filename[:-3]}'을(를) 파일을 찾을 수 없습니다!")
                except (commands.NoEntryPointError, commands.ExtensionFailed):
                    await ctx.send(f":x: '{filename[:-3]}'을(를) 불러오는 도중 에러가 발생했습니다!")
        await ctx.send(":white_check_mark: reload 작업을 완료하였습니다!")
 
 
@app.command(name="unload")
async def unload_extension(ctx, extension=None):
    if extension is not None:
        await unload_function(extension)
        await ctx.send(f":white_check_mark: {extension}기능을 종료했습니다!")
    else:
        await unload_function(None)
        await ctx.send(":white_check_mark: 모든 확장기능을 종료했습니다!")
 
 
async def unload_function(extension=None):
    if extension is not None:
        try:
            await app.unload_extension(f"Cogs.{extension}")
        except (commands.ExtensionNotLoaded, commands.ExtensionNotFound):
            pass
    else:
        for filename in os.listdir("Cogs"):
            if filename.endswith(".py"):
                try:
                    await app.unload_extension(f"Cogs.{filename[:-3]}")
                except (commands.ExtensionNotLoaded, commands.ExtensionNotFound):
                    pass
 
 
async def main():
    async with app:
        await load_extensions()
        file = open("discord_token.txt")
        bot_token = file.readline()
        file.close()
        await app.start(bot_token)
 
 
asyncio.run(main())
cs

 

이렇게 작성하시게 되면, 이제 Cogs 폴더 내의 각 기능별 소스코드를 수정하였을 때, 봇을 재시작 해 줄 필요 없이 reload 명령어만으로 해당 소스코드를 다시 불러 읽어올수 있으며, 필요에 따라 기능을 unload 도 할 수 있게 됩니다.


포스팅에 사용된 모든 소스코드는 아래 Github에서 확인하실 수 있습니다.

https://github.com/aochfl/ChoRi_TestBot

 

GitHub - aochfl/ChoRi_TestBot

Contribute to aochfl/ChoRi_TestBot development by creating an account on GitHub.

github.com


참고자료

https://discord.com/developers/docs/reference - ( Discord Developer API )

https://discordpy.readthedocs.io/en/latest/index.html  -  ( discord.py library 문서 )

 

728x90
반응형