loads of import fixes

This commit is contained in:
pacnpal
2024-11-17 19:47:18 +00:00
parent f71e174c0d
commit 97dd6d72f2
49 changed files with 1061 additions and 1062 deletions

View File

@@ -1,10 +1,11 @@
"""Update checker for yt-dlp"""
import logging
from importlib.metadata import version as get_package_version
from datetime import datetime, timedelta
import aiohttp
from packaging import version
import discord
import discord # type: ignore
from typing import Optional, Tuple, Dict, Any
import asyncio
import sys
@@ -15,20 +16,21 @@ import tempfile
import os
import shutil
from .exceptions import UpdateError
from .utils.exceptions import UpdateError
logger = logging.getLogger("VideoArchiver")
logger = logging.getLogger('VideoArchiver')
class UpdateChecker:
"""Handles checking for yt-dlp updates"""
GITHUB_API_URL = 'https://api.github.com/repos/yt-dlp/yt-dlp/releases/latest'
GITHUB_API_URL = "https://api.github.com/repos/yt-dlp/yt-dlp/releases/latest"
UPDATE_CHECK_INTERVAL = 21600 # 6 hours in seconds
MAX_RETRIES = 3
RETRY_DELAY = 5
REQUEST_TIMEOUT = 30
SUBPROCESS_TIMEOUT = 300 # 5 minutes
def __init__(self, bot, config_manager):
self.bot = bot
self.config_manager = config_manager
@@ -44,10 +46,10 @@ class UpdateChecker:
if self._session is None or self._session.closed:
self._session = aiohttp.ClientSession(
headers={
'Accept': 'application/vnd.github.v3+json',
'User-Agent': 'VideoArchiver-Bot'
"Accept": "application/vnd.github.v3+json",
"User-Agent": "VideoArchiver-Bot",
},
timeout=aiohttp.ClientTimeout(total=self.REQUEST_TIMEOUT)
timeout=aiohttp.ClientTimeout(total=self.REQUEST_TIMEOUT),
)
async def start(self) -> None:
@@ -67,30 +69,36 @@ class UpdateChecker:
except asyncio.CancelledError:
pass
self._check_task = None
if self._session and not self._session.closed:
await self._session.close()
self._session = None
logger.info("Update checker task stopped")
async def _check_loop(self) -> None:
"""Periodic update check loop with improved error handling"""
await self.bot.wait_until_ready()
while not self._shutdown:
try:
for guild in self.bot.guilds:
try:
settings = await self.config_manager.get_guild_settings(guild.id)
if settings.get('disable_update_check', False):
settings = await self.config_manager.get_guild_settings(
guild.id
)
if settings.get("disable_update_check", False):
continue
current_time = datetime.utcnow()
# Check if we've checked recently
last_check = self._last_version_check.get(guild.id)
if last_check and (current_time - last_check).total_seconds() < self.UPDATE_CHECK_INTERVAL:
if (
last_check
and (current_time - last_check).total_seconds()
< self.UPDATE_CHECK_INTERVAL
):
continue
# Check rate limits
@@ -105,7 +113,9 @@ class UpdateChecker:
self._last_version_check[guild.id] = current_time
except Exception as e:
logger.error(f"Error checking updates for guild {guild.id}: {str(e)}")
logger.error(
f"Error checking updates for guild {guild.id}: {str(e)}"
)
continue
except asyncio.CancelledError:
@@ -124,7 +134,7 @@ class UpdateChecker:
await self._log_error(
guild,
UpdateError("Could not determine current yt-dlp version"),
"checking current version"
"checking current version",
)
return
@@ -134,14 +144,14 @@ class UpdateChecker:
# Update last check time
await self.config_manager.update_setting(
guild.id,
"last_update_check",
datetime.utcnow().isoformat()
guild.id, "last_update_check", datetime.utcnow().isoformat()
)
# Compare versions
if version.parse(current_version) < version.parse(latest_version):
await self._notify_update(guild, current_version, latest_version, settings)
await self._notify_update(
guild, current_version, latest_version, settings
)
except Exception as e:
await self._log_error(guild, e, "checking for updates")
@@ -149,7 +159,7 @@ class UpdateChecker:
async def _get_current_version(self) -> Optional[str]:
"""Get current yt-dlp version with error handling"""
try:
return get_package_version('yt-dlp')
return get_package_version("yt-dlp")
except Exception as e:
logger.error(f"Error getting current version: {str(e)}")
return None
@@ -157,35 +167,48 @@ class UpdateChecker:
async def _get_latest_version(self) -> Optional[str]:
"""Get the latest version from GitHub with retries and rate limit handling"""
await self._init_session()
for attempt in range(self.MAX_RETRIES):
try:
async with self._session.get(self.GITHUB_API_URL) as response:
# Update rate limit info
self._remaining_requests = int(response.headers.get('X-RateLimit-Remaining', 0))
self._rate_limit_reset = int(response.headers.get('X-RateLimit-Reset', 0))
self._remaining_requests = int(
response.headers.get("X-RateLimit-Remaining", 0)
)
self._rate_limit_reset = int(
response.headers.get("X-RateLimit-Reset", 0)
)
if response.status == 200:
data = await response.json()
return data['tag_name'].lstrip('v')
elif response.status == 403 and 'X-RateLimit-Remaining' in response.headers:
return data["tag_name"].lstrip("v")
elif (
response.status == 403
and "X-RateLimit-Remaining" in response.headers
):
logger.warning("GitHub API rate limit reached")
return None
elif response.status == 404:
raise UpdateError("GitHub API endpoint not found")
else:
raise UpdateError(f"GitHub API returned status {response.status}")
raise UpdateError(
f"GitHub API returned status {response.status}"
)
except asyncio.TimeoutError:
logger.error(f"Timeout getting latest version (attempt {attempt + 1}/{self.MAX_RETRIES})")
logger.error(
f"Timeout getting latest version (attempt {attempt + 1}/{self.MAX_RETRIES})"
)
if attempt == self.MAX_RETRIES - 1:
return None
except Exception as e:
logger.error(f"Error getting latest version (attempt {attempt + 1}/{self.MAX_RETRIES}): {str(e)}")
logger.error(
f"Error getting latest version (attempt {attempt + 1}/{self.MAX_RETRIES}): {str(e)}"
)
if attempt == self.MAX_RETRIES - 1:
return None
await asyncio.sleep(self.RETRY_DELAY * (attempt + 1))
return None
@@ -195,7 +218,7 @@ class UpdateChecker:
guild: discord.Guild,
current_version: str,
latest_version: str,
settings: dict
settings: dict,
) -> None:
"""Notify about available updates with retry mechanism"""
owner = self.bot.get_user(self.bot.owner_id)
@@ -203,7 +226,7 @@ class UpdateChecker:
await self._log_error(
guild,
UpdateError("Could not find bot owner"),
"sending update notification"
"sending update notification",
)
return
@@ -223,23 +246,25 @@ class UpdateChecker:
await self._log_error(
guild,
UpdateError(f"Failed to send update notification: {str(e)}"),
"sending update notification"
"sending update notification",
)
else:
await asyncio.sleep(settings.get("discord_retry_delay", 5))
async def _log_error(self, guild: discord.Guild, error: Exception, context: str) -> None:
async def _log_error(
self, guild: discord.Guild, error: Exception, context: str
) -> None:
"""Log an error to the guild's log channel with enhanced formatting"""
timestamp = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")
error_message = f"[{timestamp}] Error {context}: {str(error)}"
log_channel = await self.config_manager.get_channel(guild, "log")
if log_channel:
try:
await log_channel.send(f"```\n{error_message}\n```")
except discord.HTTPException as e:
logger.error(f"Failed to send error to log channel: {str(e)}")
logger.error(f"Guild {guild.id} - {error_message}")
async def update_yt_dlp(self) -> Tuple[bool, str]:
@@ -247,32 +272,29 @@ class UpdateChecker:
temp_dir = None
try:
# Create temporary directory for pip output
temp_dir = tempfile.mkdtemp(prefix='ytdlp_update_')
log_file = Path(temp_dir) / 'pip_log.txt'
temp_dir = tempfile.mkdtemp(prefix="ytdlp_update_")
log_file = Path(temp_dir) / "pip_log.txt"
# Prepare pip command
cmd = [
sys.executable,
'-m',
'pip',
'install',
'--upgrade',
'yt-dlp',
'--log',
str(log_file)
"-m",
"pip",
"install",
"--upgrade",
"yt-dlp",
"--log",
str(log_file),
]
# Run pip in subprocess with timeout
process = await asyncio.create_subprocess_exec(
*cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
*cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
)
try:
stdout, stderr = await asyncio.wait_for(
process.communicate(),
timeout=self.SUBPROCESS_TIMEOUT
process.communicate(), timeout=self.SUBPROCESS_TIMEOUT
)
except asyncio.TimeoutError:
process.kill()
@@ -288,7 +310,7 @@ class UpdateChecker:
error_details = "Unknown error"
if log_file.exists():
try:
error_details = log_file.read_text(errors='ignore')
error_details = log_file.read_text(errors="ignore")
except Exception:
pass
return False, f"Failed to update: {error_details}"