176 lines
7.4 KiB
Python
176 lines
7.4 KiB
Python
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"])
|