rftdgsh
This commit is contained in:
@@ -5,36 +5,28 @@ import models
|
|||||||
import security
|
import security
|
||||||
|
|
||||||
class TwitchBot(twitchio.Client):
|
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
|
self.websocket_manager = websocket_manager
|
||||||
# Store our application's database user ID to avoid conflict with twitchio's internal 'owner_id'
|
# Store our application's database user ID to avoid conflict with twitchio's internal 'owner_id'
|
||||||
self.db_user_id = db_user_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:'.
|
# 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}"
|
formatted_token = f"oauth:{access_token}"
|
||||||
|
|
||||||
super().__init__(
|
# The sensitive __init__ call is now inside the awaitable task.
|
||||||
token=formatted_token,
|
super().__init__(token=formatted_token, client_id=client_id, client_secret=client_secret,
|
||||||
# The client_id and client_secret are required for the client to
|
refresh_token=refresh_token, initial_channels=[channel_name], ssl=True)
|
||||||
# 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.
|
|
||||||
)
|
|
||||||
self.channel_name = channel_name
|
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
|
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.connect()
|
||||||
await self.join_channels([self.channel_name])
|
await self.join_channels([self.channel_name])
|
||||||
|
|
||||||
|
|||||||
@@ -32,22 +32,20 @@ class ListenerManager:
|
|||||||
access_token = tokens['access_token']
|
access_token = tokens['access_token']
|
||||||
refresh_token = tokens['refresh_token']
|
refresh_token = tokens['refresh_token']
|
||||||
|
|
||||||
|
# Initialize the bot object without credentials first. It's just a lightweight container.
|
||||||
bot = TwitchBot(
|
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,
|
websocket_manager=websocket_manager,
|
||||||
db_user_id=user.id
|
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
|
# Store both the task and the bot instance for graceful shutdown
|
||||||
self.active_listeners[user.id] = {"task": task, "bot": bot}
|
self.active_listeners[user.id] = {"task": task, "bot": bot}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -66,7 +64,8 @@ class ListenerManager:
|
|||||||
bot = listener_info["bot"]
|
bot = listener_info["bot"]
|
||||||
|
|
||||||
# Gracefully close the bot's connection
|
# 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()
|
await bot.close()
|
||||||
|
|
||||||
# Cancel the asyncio task
|
# Cancel the asyncio task
|
||||||
|
|||||||
Reference in New Issue
Block a user