Skip to content

Commit

Permalink
Merge pull request #15 from kenkenpa198/feature/refactoring
Browse files Browse the repository at this point in the history
Feature/refactoring
  • Loading branch information
kenkenpa198 authored May 25, 2024
2 parents e47d548 + faacf3a commit e0178bf
Show file tree
Hide file tree
Showing 7 changed files with 211 additions and 130 deletions.
126 changes: 88 additions & 38 deletions discordbot-mdn/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,59 +12,109 @@

from cogs.utils import send as sd

print('====================================')
print(' discordbot-mdn ')
print('====================================')
def setup_logging():
"""ログ設定を行う関数"""
logging.basicConfig(level=logging.INFO, format='%(asctime)s [ %(levelname)s ] %(message)s')

def print_startup_info():
"""起動情報を表示する関数"""
print('====================================')
print(' discordbot-mdn ')
print('====================================')
print(str(datetime.now()))
print(f'python {platform.python_version()}')
print(f'discord.py {discord.__version__}')

def get_bot_token():
"""
環境変数から Bot のトークンを取得する関数
Returns:
str: Bot のトークン
Raises:
ValueError: BOT_TOKEN 環境変数が設定されていない場合
"""
BOT_TOKEN = os.environ.get('BOT_TOKEN')
if not BOT_TOKEN:
logging.error("BOT_TOKEN 環境変数が設定されていません")
raise ValueError("BOT_TOKEN 環境変数が設定されていません")
return BOT_TOKEN

print(str(datetime.now()))
print(f'python {platform.python_version()}')
print(f'discord.py {discord.__version__}')
def create_bot():
"""
Bot インスタンスを作成する関数
# logging の設定
logging.basicConfig(level=logging.INFO, format='%(asctime)s [ %(levelname)s ] %(message)s')
Returns:
discord.ext.commands.Bot: Bot のインスタンス
"""
intents = discord.Intents.default()
intents.message_content = True
# Bot インスタンスの作成
bot = commands.Bot(command_prefix='!mdn ', intents=intents)
# デフォルトの help を削除
bot.remove_command('help')

# intents の許可設定
intents = discord.Intents.default()
intents.message_content = True
return bot

# Bot インスタンスの作成
bot = commands.Bot(command_prefix='!mdn ', intents=intents)
async def load_cogs(bot):
"""
cogs をロードする関数
# デフォルトの help を削除
bot.remove_command('help')
Args:
bot (discord.ext.commands.Bot): コグをロードする Bot のインスタンス
"""
cog_names = [
'hello',
'help',
'init',
'janken',
'reload',
'small',
'talk',
'uranai'
]
for cog in cog_names:
await bot.load_extension(f'cogs.{cog}')
logging.info(f'cogs.{cog} をロードしました')

async def main():
"""
discordbot-mdn を実行
Bot のメインエントリポイント
"""
async with bot:
logging.info('Bot を起動')
# ログ設定を実行
setup_logging()

# 起動情報を表示
print_startup_info()

logging.info('Cog を読み込み')
await bot.load_extension('cogs.hello')
await bot.load_extension('cogs.help')
await bot.load_extension('cogs.init')
await bot.load_extension('cogs.janken')
await bot.load_extension('cogs.reload')
await bot.load_extension('cogs.small')
await bot.load_extension('cogs.talk')
await bot.load_extension('cogs.uranai')
# Bot インスタンスを作製
bot = create_bot()

# 環境変数に格納したトークンを取得
BOT_TOKEN = os.environ.get('BOT_TOKEN')
# Bot のトークンを取得
BOT_TOKEN = get_bot_token()

# Bot を起動する
async with bot:
logging.info('Bot を起動')

logging.info('cogs をロード')
await load_cogs(bot)
logging.info('Bot のログインと接続を実行')
await bot.start(BOT_TOKEN)

@bot.event
async def on_command_error(ctx, error):
"""
コマンドのエラー時に実行する処理
"""
logging.error('on command error: %s', error)
logging.error(traceback.format_exc())
@bot.event
async def on_command_error(ctx, error):
"""
コマンド実行中に発生したエラーを処理する関数
Args:
ctx (discord.ext.commands.Context): エラーが発生したコンテキスト
error (Exception): 発生したエラー
"""
logging.error('on command error: %s', error)
logging.error(traceback.format_exc())

await sd.send_on_command_error(ctx)
await sd.send_on_command_error(ctx)

##### main() を呼び出し #####
# main() を呼び出し
asyncio.run(main())
6 changes: 3 additions & 3 deletions discordbot-mdn/cogs/init.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ async def on_ready(self):
# 読み上げ機能: 自動再接続処理
logging.info('読み上げ機能: 自動再接続処理を開始')
logging.info('読み上げ対象チャンネルの情報を talk_channels テーブルから取得')
guild_id_list = psql.do_query_fetch_list('./sql/talk/select_guild_ids.sql')
vc_id_list = psql.do_query_fetch_list('./sql/talk/select_vc_ids.sql')
channel_id_list = psql.do_query_fetch_list('./sql/talk/select_channel_ids.sql')
guild_id_list = psql.execute_query_fetch_list('./sql/talk/select_guild_ids.sql')
vc_id_list = psql.execute_query_fetch_list('./sql/talk/select_vc_ids.sql')
channel_id_list = psql.execute_query_fetch_list('./sql/talk/select_channel_ids.sql')

if guild_id_list:
num = 0
Expand Down
128 changes: 61 additions & 67 deletions discordbot-mdn/cogs/talk.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ def is_talkable(self, message):
読み上げ不可能な場合は偽を返す
"""
# メッセージが投稿されたサーバーに Bot のボイス接続が存在しなかったら無視
# 別のサーバーに投稿されたメッセージに対して反応を行わなくさせるための条件
#
# Discord ボットは複数のサーバーに参加できるが、ボイス接続はサーバーごとに独立している
# ボイス接続がないサーバーのメッセージに反応することを防ぐためにこのチェックを通す
if not message.guild.voice_client:
return False

Expand All @@ -53,13 +55,12 @@ def is_talkable(self, message):
return False

# talk_channels テーブルにテキストチャンネルのIDが入っていなかったら無視
talk_channel_list = psql.do_query_fetch_list('./sql/talk/select_channel_ids.sql')
talk_channel_list = psql.execute_query_fetch_list('./sql/talk/select_channel_ids.sql')
if str(message.channel.id) not in talk_channel_list:
return False

return True

def jtalk(self, text, guild_id):
async def jtalk(self, text, guild_id):
"""
音声ファイルを作成する
Expand All @@ -85,23 +86,29 @@ def jtalk(self, text, guild_id):
speed = ['-r', '0.7'] # スピーチ速度係数
halftone = ['-fm', '-3.5'] # 追加ハーフトーン(高低)
volume = ['-g', '-5.0'] # 声の大きさ

# ファイルパスの指定
voice_path = 'voice_' + str(guild_id) + '.wav'
outwav = ['-ow', voice_path]

voice_path = f'voice_{guild_id}.wav'
outwav = ['-ow', voice_path]
# コマンドを作成して標準入力から作成
cmd = open_jtalk + mech + htsvoice + speed + halftone + volume + outwav
c = subprocess.Popen(cmd, stdin=subprocess.PIPE)
c.stdin.write(text.encode())
c.stdin.close()
c.wait()
cmd = open_jtalk + mech + htsvoice + speed + halftone + volume + outwav
try:
c = subprocess.Popen(cmd, stdin=subprocess.PIPE)
c.stdin.write(text.encode())
c.stdin.close()
c.wait()
except Exception as e:
logging.error(f"Error generating voice file: {e}")
return None

# 音声ファイルをモノラルからステレオへ変換
voice_fmt_src = openjtalk.mono_to_stereo(voice_path)
os.remove(voice_path)
with open(voice_path, 'wb') as f:
f.write(voice_fmt_src)
try:
voice_fmt_src = openjtalk.mono_to_stereo(voice_path)
os.remove(voice_path)
with open(voice_path, 'wb') as f:
f.write(voice_fmt_src)
except Exception as e:
logging.error(f"Error processing voice file: {e}")
return None

return voice_path

Expand All @@ -123,11 +130,18 @@ def play_voice(self, voice_path, message):
talk_src = discord.PCMAudio(stream)
message.guild.voice_client.play(talk_src, after=lambda e: stream.close())
"""
with wave.open(voice_path, 'rb') as wi:
voice_src = wi.readframes(-1)
stream = io.BytesIO(voice_src) # バイナリファイルとして読み込み
talk_src = discord.PCMAudio(stream) # 音声ファイルを音声ソースとして変数に格納
message.guild.voice_client.play(talk_src) # ボイスチャンネルで再生
try:
with wave.open(voice_path, 'rb') as wi:
voice_src = wi.readframes(-1)

# バイナリファイルとして読み込み
stream = io.BytesIO(voice_src)
# 音声ファイルを音声ソースとする
talk_src = discord.PCMAudio(stream)
# ボイスチャンネルで再生
message.guild.voice_client.play(talk_src)
except Exception as e:
logging.error(f"Error playing voice file: {e}")

def talk_deinit(self, member):
"""
Expand All @@ -144,22 +158,22 @@ def talk_deinit(self, member):
削除前にボイスチャンネルを抜けた場合、削除処理が行われないため後処理でも削除処理を行う。
"""
# 音声ファイルを削除
voice_path = 'voice_' + str(member.guild.id) + '.wav'
voice_path = f'voice_{member.guild.id}.wav'
if os.path.isfile(voice_path):
logging.info('残っていた音声ファイルを削除')
os.remove(voice_path)

# talk_channels テーブルから読み上げ対象のレコードを削除
logging.info('talk_channels テーブルから退出した ID のレコードを削除')
guild_id = member.guild.id
psql.do_query('./sql/talk/delete_target_id.sql', {'guild_id': guild_id})
psql.execute_query('./sql/talk/delete_target_id.sql', {'guild_id': guild_id})

@commands.hybrid_command(
name='talk-begin',
description='🎤 読み上げを開始するよ',
aliases=['b', 'begin', 's', 'start']
)
async def talk_begin(self, ctx, text_channel: discord.TextChannel=None):
async def talk_begin(self, ctx, text_channel: discord.TextChannel = None):
"""
読み上げ開始コマンド
"""
Expand All @@ -169,22 +183,16 @@ async def talk_begin(self, ctx, text_channel: discord.TextChannel=None):
if ctx.guild.voice_client:
# 読み上げ対象のサーバー/ ボイスチャンネル / テキストチャンネルを変数に格納
logging.info('読み上げ対象チャンネルを設定')
talk_guild = ctx.guild # サーバー
talk_vc = ctx.author.voice.channel # ボイスチャンネル
if text_channel:
# !mdn s に引数がある場合は指定のテキストチャンネルを格納
talk_channel = discord.utils.get(ctx.guild.text_channels, name=text_channel.name)
else:
# 引数がない場合はコマンドを実行したテキストチャンネルを格納
talk_channel = ctx.channel
talk_guild = ctx.guild # サーバー
talk_vc = ctx.author.voice.channel # ボイスチャンネル
talk_channel = text_channel or ctx.channel # テキストチャンネル

# 読み上げるサーバー / テキストチャンネル / ボイスチャンネルの ID を talk_channels テーブルへ格納
logging.info('読み上げ対象チャンネルの情報を talk_channels テーブルへ格納')
guild_id = talk_guild.id
vc_id = talk_vc.id
guild_id = talk_guild.id
vc_id = talk_vc.id
channel_id = talk_channel.id

psql.do_query(
psql.execute_query(
'./sql/talk/upsert_target_id.sql',
{'guild_id': guild_id, 'vc_id': vc_id, 'channel_id': channel_id}
)
Expand Down Expand Up @@ -215,24 +223,17 @@ def check(member, before, after):

# 読み上げ対象のサーバー/ ボイスチャンネル / テキストチャンネルを変数に格納
logging.info('読み上げ対象チャンネルを設定')
talk_guild = ctx.guild # サーバー
talk_vc = ctx.author.voice.channel # ボイスチャンネル
if text_channel:
# !mdn s に引数がある場合は指定のテキストチャンネルを格納
talk_channel = discord.utils.get(ctx.guild.text_channels, name=text_channel.name)
send_hello = False
else:
# 引数がない場合はコマンドを実行したテキストチャンネルを格納
talk_channel = ctx.channel
send_hello = True

# 読み上げるサーバー / ボイスチャンネル / テキストチャンネルの ID を talk_channels テーブルへ格納
talk_guild = ctx.guild # サーバー
talk_vc = ctx.author.voice.channel # ボイスチャンネル
talk_channel = text_channel or ctx.channel # テキストチャンネル
send_hello = not text_channel

logging.info('読み上げ対象チャンネルの情報を talk_channels テーブルへ格納')
guild_id = talk_guild.id
vc_id = talk_vc.id
guild_id = talk_guild.id
vc_id = talk_vc.id
channel_id = talk_channel.id

psql.do_query(
psql.execute_query(
'./sql/talk/upsert_target_id.sql',
{'guild_id': guild_id, 'vc_id': vc_id, 'channel_id': channel_id}
)
Expand Down Expand Up @@ -295,7 +296,10 @@ async def on_message(self, message):
talk_msg = rp.make_talk_src(message.clean_content)

logging.info('音声ファイルを生成')
voice_path = self.jtalk(talk_msg, message.guild.id)
voice_path = await self.jtalk(talk_msg, message.guild.id)

if not voice_path:
return

logging.info('音声ファイルを再生')
self.play_voice(voice_path, message)
Expand All @@ -305,10 +309,7 @@ async def on_message(self, message):
os.remove(voice_path)

@commands.Cog.listener()
async def on_voice_state_update(self,
member: discord.Member,
before: discord.VoiceState,
after: discord.VoiceState):
async def on_voice_state_update(self, member: discord.Member, before: discord.VoiceState, after: discord.VoiceState):
"""
ボイスチャンネルへユーザーが入退室した時の処理
"""
Expand All @@ -328,10 +329,7 @@ async def on_voice_state_update(self,
vc_b = before.channel

# Bot が最後の一人になったら自動退出する
if (
len(vc_b.members) == 1
and vc_b.members[0] == self.bot.user
):
if len(vc_b.members) == 1 and vc_b.members[0] == self.bot.user:
vc = discord.utils.get(self.bot.voice_clients, channel=vc_b)
if vc and vc.is_connected():
logging.info('読み上げを終了: 自動退出')
Expand All @@ -340,16 +338,12 @@ async def on_voice_state_update(self,

# 自動退出したメッセージを送信
guild_id = member.guild.id
talk_id = int(psql.do_query_fetch_one('./sql/talk/select_channel_id.sql', {'guild_id': guild_id}))
talk_id = int(psql.execute_query_fetch_one('./sql/talk/select_channel_id.sql', {'guild_id': guild_id}))
talk_channel = member.guild.get_channel(talk_id)
await sd.send_talk_end_auto(talk_channel)

# Bot が VC から退出した時の処理
if (
before.channel
and not after.channel
and member == self.bot.user
):
if before.channel and not after.channel and member == self.bot.user:
logging.info('読み上げ終了時の処理を実行')
self.talk_deinit(member)

Expand Down
Loading

0 comments on commit e0178bf

Please sign in to comment.