discordの読み上げbot作成 -part2 (自動退室)

## **🔹 関連記事**
本記事は[discordの読み上げbot作成 (Mac Intel上で作成)](https://qiita.com/Mimmy/items/b35a318ea2ee749f4d31)の続きです。

## **🔹 実装方法**
1. **ボットがVCに参加したら、定期的にVCの状態をチェック**
2. **VCに他のメンバーがいなくなったら自動で退出**

---

### **✅ 実装**
`discord.ext.tasks` を使って、定期的にVCのメンバー数を確認し、誰もいなければ切断します。

**コード全体**
```python
import discord
import requests
from discord import app_commands
from discord.ext import commands, tasks
from pydub import AudioSegment

TOKEN = "YOUR_BOT_TOKEN"  # 🔴 自分のBotトークンを入れてください
VOICEVOX_URL = "http://localhost:50021"

intents = discord.Intents.default()
intents.message_content = True
intents.voice_states = True  # VCの状態を取得するために必要
bot = commands.Bot(command_prefix="/", intents=intents)

# ボットが接続しているVCのテキストチャンネルを記録
vc_text_channels = {}

@bot.event
async def on_ready():
    print(f"✅ {bot.user} が起動しました！")
    try:
        synced = await bot.tree.sync()
        print(f"✅ スラッシュコマンド {len(synced)} 個を同期しました！")
    except Exception as e:
        print(f"❌ スラッシュコマンドの同期に失敗: {e}")

@bot.tree.command(name="join", description="ボイスチャットに参加します")
async def join(interaction: discord.Interaction):
    """ボットをVCに参加させる"""
    if interaction.user.voice:
        channel = interaction.user.voice.channel
        vc = await channel.connect()
        vc_text_channels[interaction.guild.id] = interaction.channel.id  # ボットが接続したVCのテキストチャンネルを記録
        await interaction.response.send_message("✅ ボイスチャンネルに参加しました！")

        # 🔴 チェックループが動作していない場合は開始
        if not check_empty_vc.is_running():
            check_empty_vc.start(interaction.guild)
    else:
        await interaction.response.send_message("❌ 先にボイスチャンネルに参加してください！", ephemeral=True)

@bot.tree.command(name="disconnect", description="ボイスチャットから退出")
async def disconnect(interaction: discord.Interaction):
    """ボットをVCから切断"""
    if interaction.guild.voice_client:
        await interaction.guild.voice_client.disconnect()
        vc_text_channels.pop(interaction.guild.id, None)  # 記録していたテキストチャンネルIDを削除
        check_empty_vc.cancel()  # 自動切断のチェックを停止
        await interaction.response.send_message("✅ ボイスチャンネルから切断しました！")
    else:
        await interaction.response.send_message("❌ ボットはVCにいません！", ephemeral=True)

@tasks.loop(seconds=2)
async def check_empty_vc(guild: discord.Guild):
    """VCのメンバーが0人になったら自動で切断"""
    if guild.voice_client:
        vc = guild.voice_client.channel
        members = [member for member in vc.members if not member.bot]  # 人間のみをカウント
        if len(members) == 0:
            print(f"⚠️ {vc.name} に誰もいないため、自動切断します")
            await guild.voice_client.disconnect()
            check_empty_vc.cancel()  # ループを停止
@bot.event
async def on_message(message):
    """ボットが接続しているVCのテキストチャンネルのメッセージのみを読み上げ"""

    if message.author == bot.user or message.author.bot:
        return  # ボット自身や他のBotのメッセージは無視

    # ボットがVCに接続していなければ無視
    if not message.guild or not message.guild.voice_client:
        return

    # 読み上げるメッセージ内容
    text = message.content

    # ボットが接続しているVCのテキストチャンネルか確認
    if message.guild.id not in vc_text_channels or message.channel.id != vc_text_channels[message.guild.id]:
        print(f"対象外のメッセージを無視: {text}")
        return  # VCのテキストチャンネル以外は無視


    # VOICEVOXで音声合成
    query_payload = {"text": text, "speaker": 1}
    query_response = requests.post(f"{VOICEVOX_URL}/audio_query", params=query_payload)

    if query_response.status_code != 200:
        return  # エラーなら無視

    query_data = query_response.json()
    synthesis_response = requests.post(f"{VOICEVOX_URL}/synthesis", json=query_data, params={"speaker": 1})

    if synthesis_response.status_code != 200:
        return  # エラーなら無視

    # 音声を保存（WAV形式のまま）
    wav_path = "voice.wav"
    with open(wav_path, "wb") as f:
        f.write(synthesis_response.content)

    # VCで再生（WAVをそのまま）
    source = discord.FFmpegPCMAudio(wav_path)
    message.guild.voice_client.play(source)

    await bot.process_commands(message)  # 他のコマンドも処理

bot.run(TOKEN)



```

---

## **🔹 実装のポイント**
- **tasksモジュールをインポート**
  ``from discord.ext import commands, tasks``の箇所
- **VCに接続 (`/join`) すると、自動切断の監視を開始**
  ``check_empty_vc.start(interaction.guild)``の箇所
- **`@tasks.loop(seconds=2)`** を使って2秒ごとにVCの人数をチェック
- **ボット以外の人間がVCにいない場合、自動で切断**
- **VCから退出 (`/disconnect`) すると、監視を停止**
  ``check_empty_vc.cancel()``の箇所

---

## **✨ 使い方**
1. **`/join` でボットをVCに招待**
2. **VCに誰もいなくなると、2秒後にボットが自動で退出**
3. **手動で退出したい場合は `/disconnect`**