Files
the-island/backend/app/twitch_service.py
empty d1b02b4dfd feat: Phase 8 - VFX 和 AI 打赏反应系统
- Unity: 添加 VFXManager 实现金币雨和爱心爆炸特效
- Unity: NetworkManager 支持 GiftEffect 事件
- Unity: AgentVisual 支持自定义时长的 SpeechBubble
- Backend: LLMService 支持生成个性化感谢语
- Backend: Engine 统一处理礼物逻辑 (handle_gift)
- Backend: TwitchBot 接入新的礼物处理流程
2026-01-01 21:38:49 +08:00

114 lines
3.6 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
import random
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:
await self._handle_bits(username, int(bits))
async def _handle_bits(self, username: str, bits_amount: int):
"""
Handle bits donation.
Delegates to game engine's unified gift handling.
"""
try:
logger.info(f"Received {bits_amount} bits from {username}")
await self._game_engine.handle_gift(username, bits_amount, "bits")
except Exception as e:
logger.error(f"Error handling bits: {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}")