- Unity: 添加 VFXManager 实现金币雨和爱心爆炸特效 - Unity: NetworkManager 支持 GiftEffect 事件 - Unity: AgentVisual 支持自定义时长的 SpeechBubble - Backend: LLMService 支持生成个性化感谢语 - Backend: Engine 统一处理礼物逻辑 (handle_gift) - Backend: TwitchBot 接入新的礼物处理流程
114 lines
3.6 KiB
Python
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}")
|
|
|