import discord import signal import asyncio import logging import os from utils.file_utils import read_text from config import Config from classes.VoiceSynthesizer import VoiceSynthesizer from classes.WordDictionary import WordDictionary from classes.CommandHandler import CommandHandler from classes.VoiceChannelManager import VoiceChannelManager from classes.MessageReader import MessageReader from classes.IndependenceChat import IndependenceChat from threading import Thread from flask import Flask, request, jsonify logging.basicConfig(level=logging.INFO) class MyDiscordBot(discord.Client): def __init__(self, config, intents, system_setting_file_path="config/system_setting.txt"): super().__init__(intents=intents) self.config = config self.system_setting_file_path = system_setting_file_path self.voice_synthesizer = VoiceSynthesizer( config["voicepeak_executable_path"], config["voicevox_url"], config["default_navigator"] ) self.word_dictionary = WordDictionary() self.prefix = config["prefix"] self.target_channel_id = None self.volume = 1.0 self.emotion_happy = 50 self.emotion_sad = 50 self.emotion_angry = 0 self.emotion_fun = 0 self.is_name_reading = False self.is_independence_chat = False self.independence_chat = IndependenceChat(self) self.command_handler = CommandHandler(self) self.voice_channel_manager = VoiceChannelManager() self.message_reader = MessageReader(self.voice_synthesizer, self) self.independence_channel_ids = config["independence_channel_ids"] self.independence_timers = [] # デバッグモードのチェック self.debug_mode_file = "debug_mode.txt" self.debug_mode_enabled = os.path.exists(self.debug_mode_file) # シグナルハンドラを設定 signal.signal(signal.SIGINT, self.shutdown) signal.signal(signal.SIGTERM, self.shutdown) # Flaskサーバーを別スレッドで起動 self.start_flask_server() def start_flask_server(self): app = Flask(__name__) @app.route('/debug', methods=['POST']) def toggle_debug(): data = request.json if 'enable' not in data: return jsonify({'error': 'Invalid request'}), 400 if data['enable']: open(self.debug_mode_file, 'w').close() self.debug_mode_enabled = True logging.info("デバッグモードが有効になりました。") asyncio.run_coroutine_threadsafe(self.independence_chat.start_debug_mode_check(), self.loop) else: if os.path.exists(self.debug_mode_file): os.remove(self.debug_mode_file) self.debug_mode_enabled = False logging.info("デバッグモードが無効になりました。") return jsonify({'success': True}) def run_flask(): app.run(host='0.0.0.0', port=5000) flask_thread = Thread(target=run_flask) flask_thread.daemon = True flask_thread.start() def reset_emotion(self): """感情の設定をリセットし、音声合成器に反映させる""" self.emotion_happy = 50 self.emotion_sad = 50 self.emotion_angry = 0 self.emotion_fun = 0 self.voice_synthesizer.set_emotion(self.emotion_happy, self.emotion_sad, self.emotion_angry, self.emotion_fun) self.voice_synthesizer.set_pitch(50) async def on_ready(self): """ボットが準備完了時に実行されるイベントハンドラ""" self.voice_synthesizer.set_emotion(self.emotion_happy, self.emotion_sad, self.emotion_angry, self.emotion_fun) self.voice_synthesizer.set_pitch(50) await self.set_bot_status(f"タイキチュウ… | {self.prefix}help") logging.info(f'{self.user} がログインしました') # 独立チャットモードの自動参加を開始 await self.independence_chat.start_independence_mode_check() async def on_message(self, message): """メッセージ受信時に実行されるイベントハンドラ""" await self.command_handler.handle_message(message) async def on_voice_state_update(self, member, before, after): """ボイスチャンネルの状態が更新された時に実行されるイベントハンドラ""" try: if before.channel is None and after.channel is not None: # ユーザーがVCに参加した場合 if self.is_independence_chat and after.channel == self.voice_channel_manager.get_connected_channel(member.guild): greeting = await self.independence_chat.generate_greeting(member.display_name) await self.message_reader.read_message(greeting, self.word_dictionary, self.config["ffmpeg_executable_path"], self.volume) elif before.channel and not after.channel and before.channel == self.voice_channel_manager.get_connected_channel(member.guild): # ユーザーがVCから退出した場合 members = [m for m in before.channel.members if m != self.user] if not members: self.target_channel_id = None await self.voice_channel_manager.leave_voice_channel(before.channel.guild, self) except Exception as e: logging.error(f"Failed to update voice state: {e}") raise def is_valid_channel(self, message): """メッセージが有効なチャンネルであるかどうかを確認する""" if self.target_channel_id and message.channel.id != self.target_channel_id: return False if not message.guild.voice_client or not message.guild.voice_client.is_connected(): return False return True def enable_independence_chat(self, voice_client): """独立チャット機能を有効にする""" system_setting = read_text(self.system_setting_file_path) self.is_independence_chat = True self.independence_chat.enable(system_setting, voice_client, self.config["independence_character_name"]) def disable_independence_chat(self): """独立チャット機能を無効にする""" self.is_independence_chat = False self.independence_chat.disable() def shutdown(self, signum, frame): """シグナルを受け取ってシャットダウンする""" logging.info("シャットダウン中...") asyncio.create_task(self.cleanup()) async def cleanup(self): """クリーンアップ処理""" for guild in self.guilds: if guild.voice_client and guild.voice_client.is_connected(): await self.voice_channel_manager.leave_voice_channel(guild, self) await self.close() async def set_bot_status(self, status): """ステータスを変更する""" activity = None if status: activity = discord.Activity(type=discord.ActivityType.listening, name=status) await self.change_presence(activity=activity) if __name__ == "__main__": config = Config.load_config() intents = discord.Intents.default() intents.guilds = True intents.messages = True intents.message_content = True intents.voice_states = True bot = MyDiscordBot(config, intents) bot.run(config["discord_token"])