This commit is contained in:
2025-11-18 01:57:12 +01:00
parent 7748e55a71
commit 4b640861a6
2 changed files with 24 additions and 33 deletions

View File

@@ -5,36 +5,28 @@ import models
import security
class TwitchBot(twitchio.Client):
def __init__(self, access_token: str, refresh_token: str, client_id: str, client_secret: str, channel_name: str, websocket_manager, db_user_id: int):
def __init__(self, websocket_manager, db_user_id: int):
self.websocket_manager = websocket_manager
# Store our application's database user ID to avoid conflict with twitchio's internal 'owner_id'
self.db_user_id = db_user_id
self.is_initialized = False # Health check flag
async def start(self, access_token: str, refresh_token: str, client_id: str, client_secret: str, channel_name: str):
"""
A custom start method that also handles initialization. This makes the
entire setup process an awaitable, atomic operation.
"""
print(f"DIAGNOSTIC: Initializing and connecting for user {self.db_user_id}...")
# Per twitchio documentation, the token must be prefixed with 'oauth:'.
# This is a common cause of silent authentication failures.
formatted_token = f"oauth:{access_token}"
super().__init__(
token=formatted_token,
# The client_id and client_secret are required for the client to
# identify itself with Twitch's services.
client_id=client_id,
client_secret=client_secret,
refresh_token=refresh_token,
initial_channels=[channel_name],
ssl=True # Mandate: Explicitly use SSL for the IRC connection.
)
# The sensitive __init__ call is now inside the awaitable task.
super().__init__(token=formatted_token, client_id=client_id, client_secret=client_secret,
refresh_token=refresh_token, initial_channels=[channel_name], ssl=True)
self.channel_name = channel_name
# Health Check: If __init__ completes successfully, this flag will exist.
# If super().__init__ fails silently, this line is never reached.
self.is_initialized = True
async def start(self):
"""
A custom start method to bypass the default start() which can
unpredictably start a webserver. This gives us direct control.
"""
print(f"DIAGNOSTIC: Initiating secure connection for user {self.db_user_id}...")
await self.connect()
await self.join_channels([self.channel_name])

View File

@@ -32,22 +32,20 @@ class ListenerManager:
access_token = tokens['access_token']
refresh_token = tokens['refresh_token']
# Initialize the bot object without credentials first. It's just a lightweight container.
bot = TwitchBot(
access_token=access_token,
refresh_token=refresh_token,
channel_name=user.username,
client_id=settings.TWITCH_CLIENT_ID,
client_secret=settings.TWITCH_CLIENT_SECRET,
websocket_manager=websocket_manager,
db_user_id=user.id
)
# Verify that the bot was initialized correctly before proceeding.
# This catches silent failures from the twitchio library.
if not getattr(bot, 'is_initialized', False):
raise Exception("TwitchBot failed to initialize, likely due to an invalid token.")
task = asyncio.create_task(bot.start())
# Create a task that runs our new start method with all credentials.
# If super().__init__ fails inside bot.start(), the exception will be
# caught by our try/except block here, preventing hollow objects.
task = asyncio.create_task(bot.start(
access_token=access_token, refresh_token=refresh_token,
client_id=settings.TWITCH_CLIENT_ID, client_secret=settings.TWITCH_CLIENT_SECRET,
channel_name=user.username
))
# Store both the task and the bot instance for graceful shutdown
self.active_listeners[user.id] = {"task": task, "bot": bot}
except Exception as e:
@@ -66,7 +64,8 @@ class ListenerManager:
bot = listener_info["bot"]
# Gracefully close the bot's connection
if bot and not bot.is_closed():
# The getattr check prevents the shutdown crash if the bot was never initialized.
if bot and getattr(bot, 'is_initialized', False) and not bot.is_closed():
await bot.close()
# Cancel the asyncio task