Files
the-island/backend/app/twitch_service.py
empty 3e89a17b69 fix: 修复 Twitch 集成 - 降级到 twitchio 2.x
- 从 twitchio 3.x 降级到 2.10.0 (IRC-based)
  - 3.x 使用 EventSub API 需要更复杂的配置
  - 2.x 使用 IRC 方式更简单可靠
- 简化 Twitch 配置,只需要 Token 和频道名
- 移除 client_id, client_secret, bot_id 要求
- 更新 .env.example 配置说明
2026-01-01 18:59:14 +08:00

113 lines
3.8 KiB
Python

"""
Twitch service for connecting to Twitch chat and handling events.
Integrates with the game engine to process chat commands and bits.
Compatible with twitchio 2.x (IRC-based)
"""
import os
import logging
from typing import TYPE_CHECKING
from twitchio.ext import commands
if TYPE_CHECKING:
from .engine import GameEngine
logger = logging.getLogger(__name__)
class TwitchBot(commands.Bot):
"""
Twitch bot that listens to chat messages and bits events.
Forwards them to the game engine for processing.
Compatible with twitchio 2.x API (IRC-based).
"""
def __init__(self, game_engine: "GameEngine"):
# Initialize bot with environment variables
self._token = os.getenv("TWITCH_TOKEN")
self._channel = os.getenv("TWITCH_CHANNEL_NAME")
prefix = os.getenv("TWITCH_COMMAND_PREFIX", "!")
if not self._token:
raise ValueError("TWITCH_TOKEN environment variable is required")
if not self._channel:
raise ValueError("TWITCH_CHANNEL_NAME environment variable is required")
# Store game engine reference
self._game_engine = game_engine
# Initialize the bot (twitchio 2.x API - IRC based)
super().__init__(
token=self._token,
prefix=prefix,
initial_channels=[self._channel]
)
logger.info(f"TwitchBot initialized for channel: {self._channel}")
async def event_ready(self):
"""Called when the bot successfully connects to Twitch."""
logger.info(f"Twitch Bot logged in as: {self.nick}")
logger.info(f"Connected to channels: {[c.name for c in self.connected_channels]}")
async def event_message(self, message):
"""Called when a message is received in chat."""
# Ignore messages from the bot itself
if message.echo:
return
# Handle commands first
await self.handle_commands(message)
# Extract user and message content
author = message.author
if author is None:
return
username = author.name
content = message.content.strip()
# Log the message for debugging
logger.info(f"Twitch chat [{username}]: {content}")
# Forward to game engine for command processing
try:
await self._game_engine.process_command(username, content)
except Exception as e:
logger.error(f"Error processing command: {e}")
# Check for bits in the message tags
if hasattr(message, 'tags') and message.tags:
bits = message.tags.get('bits')
if bits:
try:
bits_amount = int(bits)
logger.info(f"Received {bits_amount} bits from {username}")
await self._game_engine.process_bits(username, bits_amount)
# Send special gift effect to Unity
await self._game_engine._broadcast_event("gift_effect", {
"type": "gift_effect",
"user": username,
"value": bits_amount,
"message": f"{username} cheered {bits_amount} bits!"
})
except (ValueError, TypeError) as e:
logger.error(f"Error parsing bits amount: {e}")
async def event_command_error(self, context, error):
"""Called when a command error occurs."""
# Ignore command not found errors (most chat messages aren't commands)
if isinstance(error, commands.CommandNotFound):
return
logger.error(f"Command error: {error}")
async def event_error(self, error: Exception, data: str = None):
"""Called when an error occurs."""
logger.error(f"Twitch bot error: {error}")
if data:
logger.debug(f"Error data: {data}")