Skip to content

Commit 9d281e7

Browse files
pladisdevCopilotCopilot
authored
Development (#33)
* pop up mode and chat buble toggle * potential audio fix * linux sh and better docker support * fixed linux build? * linux fix part 2 * docker fix part 1 * Initial plan * Initial plan * Initial plan * Initial plan * Initial plan * Initial plan * Initial plan * Update frontend/src/pages/YappersPage.jsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Initial plan * Update frontend/src/pages/YappersPage.jsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Replace sys.platform with platform.system() for OS detection Co-authored-by: pladisdev <127021507+pladisdev@users.noreply.github.com> * Fix race condition in popup avatar lifecycle Co-authored-by: pladisdev <127021507+pladisdev@users.noreply.github.com> * Extract magic numbers to named constants for better readability Co-authored-by: pladisdev <127021507+pladisdev@users.noreply.github.com> * Extract magic number -2.5px into avatarActiveOffset setting Co-authored-by: pladisdev <127021507+pladisdev@users.noreply.github.com> * Fix audio cleanup in popup mode when play() fails Co-authored-by: pladisdev <127021507+pladisdev@users.noreply.github.com> * Extract hex opacity calculation to utility function Co-authored-by: pladisdev <127021507+pladisdev@users.noreply.github.com> * Fix audio error handlers to clean up tracking references Co-authored-by: pladisdev <127021507+pladisdev@users.noreply.github.com> * update flow for builds * better build and release file * workflow fix * build fix * usernames in chat bubbles * twitch authentifcation * tts limit * notifcation that user needs to click page, quick status * update readme, version * emergency twitch fix * yep * dang * better twitch logging * animations and avatar layout editor * fix spin animation * twitch fix part 2 * twitch fix, better fonts, cleanup * fixed imports * fixed things * undefined fix * twitch test fix * another mock fix * another fix for twitch creds tests * better logging --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent d248765 commit 9d281e7

2 files changed

Lines changed: 49 additions & 8 deletions

File tree

backend/app.py

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -482,8 +482,9 @@ async def restart_twitch_if_needed(settings: Dict[str, Any]):
482482
# Catch any other errors during cancellation (e.g., twitchio internal errors)
483483
logger.warning(f"Error while cancelling Twitch bot task: {e}")
484484

485-
# Give it a moment to fully clean up
486-
await asyncio.sleep(0.1)
485+
# Give TwitchIO's EventSub server more time to fully shut down
486+
# This prevents CancelledError spam from aiohttp adapter callbacks
487+
await asyncio.sleep(0.5)
487488

488489
# Start new task if enabled
489490
if run_twitch_bot and settings.get("twitch", {}).get("enabled"):
@@ -819,8 +820,8 @@ async def restart_youtube_if_needed(settings: Dict[str, Any]):
819820
except Exception as e:
820821
logger.warning(f"Error while cancelling YouTube bot task: {e}")
821822

822-
# Give it a moment to fully clean up
823-
await asyncio.sleep(0.1)
823+
# Give YouTube API more time to fully clean up
824+
await asyncio.sleep(0.5)
824825

825826
# Start new task if enabled
826827
if run_youtube_bot and settings.get("youtube", {}).get("enabled"):
@@ -1512,9 +1513,37 @@ async def handle_moderation_event(evt: Dict[str, Any]):
15121513
# ---------- Avatar Slot Management API ----------
15131514
# Avatar slot endpoints have been moved to routers/avatars.py
15141515

1516+
def custom_exception_handler(loop, context):
1517+
"""
1518+
Custom exception handler to suppress harmless TwitchIO internal errors during shutdown.
1519+
These CancelledError exceptions from aiohttp adapter callbacks are expected during bot restarts.
1520+
"""
1521+
exception = context.get("exception")
1522+
message = context.get("message", "")
1523+
handle = str(context.get("handle", ""))
1524+
1525+
# Suppress TwitchIO EventSub server cancellation errors (harmless during shutdown)
1526+
if isinstance(exception, asyncio.CancelledError):
1527+
# Check if this is from TwitchIO's aiohttp adapter
1528+
if ("AiohttpAdapter._task_callback" in message or
1529+
"AiohttpAdapter._task_callback" in handle or
1530+
"aio_adapter.py" in message or
1531+
"web_runner.py" in message):
1532+
# This is an expected error during TwitchIO bot shutdown - don't log it
1533+
return
1534+
1535+
# For all other exceptions, use the default handler
1536+
loop.default_exception_handler(context)
1537+
15151538
@app.on_event("startup")
15161539
async def startup():
15171540
logger.info("FastAPI startup event triggered")
1541+
1542+
# Install custom exception handler to suppress TwitchIO shutdown noise
1543+
loop = asyncio.get_running_loop()
1544+
loop.set_exception_handler(custom_exception_handler)
1545+
logger.info("Custom exception handler installed for cleaner TwitchIO shutdown")
1546+
15181547
try:
15191548
# Broadcast initial avatar slot assignments to any connected clients
15201549
await broadcast_avatar_slots()

backend/modules/youtube_listener.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@
99
from google.oauth2.credentials import Credentials
1010
from googleapiclient.discovery import build
1111
from googleapiclient.errors import HttpError
12+
from google.auth.exceptions import RefreshError
1213
YOUTUBE_AVAILABLE = True
1314
except ImportError as e:
1415
logger.error(f"Failed to import YouTube dependencies: {e}")
1516
logger.info("Please install: pip install google-auth google-auth-oauthlib google-api-python-client")
1617
YOUTUBE_AVAILABLE = False
1718
Credentials = None # type: ignore
1819
HttpError = Exception
20+
RefreshError = Exception # type: ignore
1921

2022

2123
class YouTubeListener:
@@ -82,15 +84,20 @@ async def find_active_stream(self) -> bool:
8284
else:
8385
logger.warning("No active live stream found for this channel")
8486
return False
87+
except RefreshError as e:
88+
logger.error("YouTube authentication token has expired or been revoked")
89+
logger.info("Please reconnect your YouTube account in settings to continue using YouTube chat integration")
90+
return False
8591
except HttpError as e:
8692
status_code = getattr(e, 'resp', {}).get('status', 0)
8793
if status_code == 401:
88-
logger.error("YouTube API authentication error while finding active stream - token may be expired")
94+
logger.error("YouTube API authentication error - token may be expired")
95+
logger.info("Please reconnect your YouTube account in settings")
8996
else:
9097
logger.error(f"YouTube API error finding active stream: {e}")
9198
return False
9299
except Exception as e:
93-
logger.error(f"Error finding active stream: {e}", exc_info=True)
100+
logger.error(f"Error finding active stream: {e}")
94101
return False
95102

96103
async def get_live_chat_id(self) -> Optional[str]:
@@ -122,15 +129,20 @@ async def get_live_chat_id(self) -> Optional[str]:
122129
else:
123130
logger.error(f"Video {self.video_id} not found")
124131
return None
132+
except RefreshError as e:
133+
logger.error("YouTube authentication token has expired or been revoked")
134+
logger.info("Please reconnect your YouTube account in settings to continue using YouTube chat integration")
135+
return None
125136
except HttpError as e:
126137
status_code = getattr(e, 'resp', {}).get('status', 0)
127138
if status_code == 401:
128-
logger.error("YouTube API authentication error while getting live chat ID - token may be expired")
139+
logger.error("YouTube API authentication error - token may be expired")
140+
logger.info("Please reconnect your YouTube account in settings")
129141
else:
130142
logger.error(f"YouTube API error getting live chat ID: {e}")
131143
return None
132144
except Exception as e:
133-
logger.error(f"Error getting live chat ID: {e}", exc_info=True)
145+
logger.error(f"Error getting live chat ID: {e}")
134146
return None
135147

136148
async def listen_to_chat(self, on_event: Callable[[Dict[str, Any]], None]):

0 commit comments

Comments
 (0)