Skip to content

Commit b9636bc

Browse files
committed
feat: make Jellyfin API requests asynchronous using aiohttp to prevent Discord heartbeat blocking
1 parent ab3db08 commit b9636bc

File tree

1 file changed

+118
-117
lines changed

1 file changed

+118
-117
lines changed

cogs/jellyfin_core.py

Lines changed: 118 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from discord import app_commands
1212
from main import is_authorized
1313
import asyncio
14+
import aiohttp
1415

1516
# Library name to emoji mapping with priority order
1617
LIBRARY_EMOJIS = {
@@ -357,32 +358,32 @@ async def get_server_info(self) -> Dict[str, Any]:
357358
"X-Emby-Authorization": "MediaBrowser Client=\"JellyWatch\", Device=\"JellyWatch\", DeviceId=\"jellywatch-bot\", Version=\"1.0.0\""
358359
}
359360

360-
# Get system info
361-
response = requests.get(f"{self.JELLYFIN_URL}/System/Info", headers=headers)
362-
if response.status_code != 200:
363-
return {}
364-
365-
system_info = response.json()
366-
367-
# Get sessions
368-
sessions = self.get_sessions()
369-
current_streams = len([s for s in sessions if s.get("NowPlayingItem")]) if sessions else 0
370-
371-
# Get library stats
372-
library_stats = self.get_library_stats()
373-
total_items = sum(int(stats.get("count", 0)) for stats in library_stats.values())
374-
total_episodes = sum(int(episodes) for stats in library_stats.values()
375-
if (episodes := stats.get("episodes")) is not None)
376-
377-
return {
378-
"server_name": system_info.get("ServerName", "Unknown Server"),
379-
"version": system_info.get("Version", "Unknown Version"),
380-
"operating_system": system_info.get("OperatingSystem", "Unknown OS"),
381-
"current_streams": current_streams,
382-
"total_items": total_items,
383-
"total_episodes": total_episodes,
384-
"library_stats": library_stats
385-
}
361+
async with aiohttp.ClientSession() as session:
362+
# Get system info
363+
async with session.get(f"{self.JELLYFIN_URL}/System/Info", headers=headers) as response:
364+
if response.status != 200:
365+
return {}
366+
system_info = await response.json()
367+
368+
# Get sessions
369+
sessions = await self.get_sessions()
370+
current_streams = len([s for s in sessions if s.get("NowPlayingItem")]) if sessions else 0
371+
372+
# Get library stats
373+
library_stats = await self.get_library_stats()
374+
total_items = sum(int(stats.get("count", 0)) for stats in library_stats.values())
375+
total_episodes = sum(int(episodes) for stats in library_stats.values()
376+
if (episodes := stats.get("episodes")) is not None)
377+
378+
return {
379+
"server_name": system_info.get("ServerName", "Unknown Server"),
380+
"version": system_info.get("Version", "Unknown Version"),
381+
"operating_system": system_info.get("OperatingSystem", "Unknown OS"),
382+
"current_streams": current_streams,
383+
"total_items": total_items,
384+
"total_episodes": total_episodes,
385+
"library_stats": library_stats
386+
}
386387
except Exception as e:
387388
self.logger.error(f"Error getting server info: {e}")
388389
return {}
@@ -396,7 +397,7 @@ def calculate_uptime(self) -> str:
396397
minutes = total_minutes % 60
397398
return "99+ Hours" if hours > 99 else f"{hours:02d}:{minutes:02d}"
398399

399-
def get_library_stats(self) -> Dict[str, Dict[str, Any]]:
400+
async def get_library_stats(self) -> Dict[str, Dict[str, Any]]:
400401
"""Fetch and cache Jellyfin library statistics."""
401402
current_time = datetime.now()
402403
if (
@@ -419,99 +420,99 @@ def get_library_stats(self) -> Dict[str, Dict[str, Any]]:
419420
"X-Emby-Authorization": "MediaBrowser Client=\"JellyWatch\", Device=\"JellyWatch\", DeviceId=\"jellywatch-bot\", Version=\"1.0.0\""
420421
}
421422

422-
# Get all libraries
423-
response = requests.get(f"{self.JELLYFIN_URL}/Library/VirtualFolders", headers=headers)
424-
if response.status_code != 200:
425-
self.logger.error(f"Failed to get library folders: HTTP {response.status_code}")
426-
return self.library_cache
427-
428-
libraries = response.json()
429-
stats: Dict[str, Dict[str, Any]] = {}
430-
jellyfin_config = self.config["jellyfin_sections"]
431-
configured_sections = jellyfin_config["sections"]
432-
433-
for library in libraries:
434-
library_id = library.get("ItemId")
435-
library_name = library.get("Name", "").lower()
436-
437-
if not jellyfin_config["show_all"] and library_id not in configured_sections:
438-
continue
439-
440-
# Get library configuration
441-
config = configured_sections.get(library_id, {
442-
"display_name": library.get("Name", "Unknown Library"),
443-
"emoji": LIBRARY_EMOJIS["default"],
444-
"show_episodes": False
445-
})
446-
447-
# Find matching emoji based on library name with priority
448-
emoji = LIBRARY_EMOJIS["default"]
449-
best_match_length = 0
450-
best_match_key = None
451-
452-
# First pass: find all matches
453-
matches = []
454-
library_name_lower = library_name.lower()
455-
for key, value in LIBRARY_EMOJIS.items():
456-
if key == "default":
423+
async with aiohttp.ClientSession() as session:
424+
# Get all libraries
425+
async with session.get(f"{self.JELLYFIN_URL}/Library/VirtualFolders", headers=headers) as response:
426+
if response.status != 200:
427+
self.logger.error(f"Failed to get library folders: HTTP {response.status}")
428+
return self.library_cache
429+
libraries = await response.json()
430+
431+
stats: Dict[str, Dict[str, Any]] = {}
432+
jellyfin_config = self.config["jellyfin_sections"]
433+
configured_sections = jellyfin_config["sections"]
434+
435+
for library in libraries:
436+
library_id = library.get("ItemId")
437+
library_name = library.get("Name", "").lower()
438+
439+
if not jellyfin_config["show_all"] and library_id not in configured_sections:
457440
continue
458-
if key in library_name_lower:
459-
matches.append((key, value, len(key), key in GENERIC_TERMS))
460-
461-
# Second pass: find the best non-generic match
462-
for key, value, length, is_generic in matches:
463-
if not is_generic and length > best_match_length:
464-
best_match_length = length
465-
best_match_key = key
466-
emoji = value
467-
468-
# If no non-generic match was found, use the best match overall
469-
if best_match_key is None and matches:
470-
best_match_length = max(length for _, _, length, _ in matches)
471-
for key, value, length, _ in matches:
472-
if length == best_match_length:
473-
emoji = value
474-
break
475-
476-
# If we found a match in the config, use that instead
477-
if config.get("emoji"):
478-
emoji = config["emoji"]
479-
480-
# Log the emoji selection for debugging
481-
self.logger.debug(f"Library '{library_name}' matched with emoji '{emoji}' (best match: '{best_match_key}')")
482441

483-
# Get item counts
484-
params = {
485-
"ParentId": library_id,
486-
"Recursive": True,
487-
"IncludeItemTypes": "Movie,Series,Episode"
488-
}
489-
items_response = requests.get(
490-
f"{self.JELLYFIN_URL}/Items",
491-
headers=headers,
492-
params=params
493-
)
494-
495-
if items_response.status_code == 200:
496-
items = items_response.json()
497-
movie_count = sum(1 for item in items["Items"] if item["Type"] == "Movie")
498-
series_count = sum(1 for item in items["Items"] if item["Type"] == "Series")
499-
episode_count = sum(1 for item in items["Items"] if item["Type"] == "Episode")
500-
501-
# Create base stats dictionary
502-
library_stats = {
503-
"count": movie_count + series_count,
504-
"display_name": config.get("display_name", library.get("Name", "Unknown Library")),
505-
"emoji": emoji
442+
# Get library configuration
443+
config = configured_sections.get(library_id, {
444+
"display_name": library.get("Name", "Unknown Library"),
445+
"emoji": LIBRARY_EMOJIS["default"],
446+
"show_episodes": False
447+
})
448+
449+
# Find matching emoji based on library name with priority
450+
emoji = LIBRARY_EMOJIS["default"]
451+
best_match_length = 0
452+
best_match_key = None
453+
454+
# First pass: find all matches
455+
matches = []
456+
library_name_lower = library_name.lower()
457+
for key, value in LIBRARY_EMOJIS.items():
458+
if key == "default":
459+
continue
460+
if key in library_name_lower:
461+
matches.append((key, value, len(key), key in GENERIC_TERMS))
462+
463+
# Second pass: find the best non-generic match
464+
for key, value, length, is_generic in matches:
465+
if not is_generic and length > best_match_length:
466+
best_match_length = length
467+
best_match_key = key
468+
emoji = value
469+
470+
# If no non-generic match was found, use the best match overall
471+
if best_match_key is None and matches:
472+
best_match_length = max(length for _, _, length, _ in matches)
473+
for key, value, length, _ in matches:
474+
if length == best_match_length:
475+
emoji = value
476+
break
477+
478+
# If we found a match in the config, use that instead
479+
if config.get("emoji"):
480+
emoji = config["emoji"]
481+
482+
# Log the emoji selection for debugging
483+
self.logger.debug(f"Library '{library_name}' matched with emoji '{emoji}' (best match: '{best_match_key}')")
484+
485+
# Get item counts
486+
params = {
487+
"ParentId": library_id,
488+
"Recursive": True,
489+
"IncludeItemTypes": "Movie,Series,Episode"
506490
}
507-
508-
# Only add episodes if show_episodes is True
509-
if config.get("show_episodes", False):
510-
library_stats["episodes"] = episode_count
511-
512-
stats[library_id] = library_stats
513-
else:
514-
self.logger.error(f"Failed to get items for library {library_name}: HTTP {items_response.status_code}")
491+
async with session.get(
492+
f"{self.JELLYFIN_URL}/Items",
493+
headers=headers,
494+
params=params
495+
) as items_response:
496+
if items_response.status == 200:
497+
items = await items_response.json()
498+
movie_count = sum(1 for item in items["Items"] if item["Type"] == "Movie")
499+
series_count = sum(1 for item in items["Items"] if item["Type"] == "Series")
500+
episode_count = sum(1 for item in items["Items"] if item["Type"] == "Episode")
501+
502+
# Create base stats dictionary
503+
library_stats = {
504+
"count": movie_count + series_count,
505+
"display_name": config.get("display_name", library.get("Name", "Unknown Library")),
506+
"emoji": emoji
507+
}
508+
509+
# Only add episodes if show_episodes is True
510+
if config.get("show_episodes", False):
511+
library_stats["episodes"] = episode_count
512+
513+
stats[library_id] = library_stats
514+
else:
515+
self.logger.error(f"Failed to get items for library {library_name}: HTTP {items_response.status}")
515516

516517
self.library_cache = stats
517518
self.last_library_update = current_time

0 commit comments

Comments
 (0)