Upgraded modules, fixed error handling because Ruff complained
This commit is contained in:
1 parent
afb8c6059a
commit
2e2c82ad22
6 files changed
+112
-89
No files matched your search
+16
-13
@@ -1,12 +1,15 @@
|
||||
import requests
|
||||
import yt_dlp
|
||||
import os
|
||||
import time
|
||||
|
||||
import requests
|
||||
import yt_dlp
|
||||
from dotenv import load_dotenv
|
||||
from ssh_helper import upload_via_sftp, send_notification_via_ssh
|
||||
from youtube_handler import return_download_options, check_for_sponsorblock_segments
|
||||
from simple_logger_handler import setup_logger
|
||||
from rss_feed_handler import grab_latest_chapter_information, EpisodeData
|
||||
|
||||
from rss_feed_handler import EpisodeData, FeedError, grab_latest_chapter_information
|
||||
from ssh_helper import send_notification_via_ssh, upload_via_sftp
|
||||
from youtube_handler import check_for_sponsorblock_segments, return_download_options
|
||||
|
||||
logger = setup_logger(__name__)
|
||||
|
||||
load_dotenv()
|
||||
@@ -84,8 +87,8 @@ def check_until_new_episode_gets_released() -> tuple[EpisodeData | None, str | N
|
||||
|
||||
try:
|
||||
episode_data = grab_latest_chapter_information("UCC3ehuUksTyQ7bbjGntmx3Q")
|
||||
except Exception as e:
|
||||
logger.warning(f"[EpisodeCheck] Failed to fetch latest video data: {e}, retrying in 1 hour.")
|
||||
except FeedError as e:
|
||||
logger.warning(f"[EpisodeCheck] Failed to fetch latest video data: {e!r}, retrying in 1 hour.")
|
||||
time.sleep(CHECK_INTERVAL_HOURS * 3600)
|
||||
continue
|
||||
|
||||
@@ -144,8 +147,8 @@ def download_episode() -> None:
|
||||
return
|
||||
|
||||
logger.info("[App] New episode found")
|
||||
except Exception as e:
|
||||
logger.error(f"[App] Failed to fetch new episode info: {e}", exc_info=True)
|
||||
except Exception:
|
||||
logger.exception("[App] Failed to fetch new episode info")
|
||||
return
|
||||
|
||||
try:
|
||||
@@ -165,8 +168,8 @@ def download_episode() -> None:
|
||||
|
||||
try:
|
||||
options = return_download_options(episode_data)
|
||||
except Exception as e:
|
||||
logger.error(f"[App] Failed to generate download options: {e}", exc_info=True)
|
||||
except Exception:
|
||||
logger.exception("[App] Failed to generate download options")
|
||||
return
|
||||
|
||||
logger.info("[App] Downloading new episode")
|
||||
@@ -174,8 +177,8 @@ def download_episode() -> None:
|
||||
with yt_dlp.YoutubeDL(options) as episode:
|
||||
episode.download(episode_data.episode_link)
|
||||
logger.debug("[App] Download completed successfully")
|
||||
except Exception as e:
|
||||
logger.error(f"[App] Failed to download episode: {e}", exc_info=True)
|
||||
except Exception:
|
||||
logger.exception("[App] Failed to download episode")
|
||||
return
|
||||
|
||||
logger.info("[App] Uploading episode via SFTP")
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import re
|
||||
|
||||
|
||||
def return_string_as_html(input_text):
|
||||
string_without_ads=""
|
||||
for line in input_text.splitlines():
|
||||
|
||||
+21
-19
@@ -1,23 +1,25 @@
|
||||
--extra-index-url https://git.gansejunge.com/api/packages/notifier/pypi/simple/
|
||||
bcrypt==5.0.0
|
||||
Brotli==1.1.0
|
||||
brotli==1.2.0
|
||||
certifi==2026.10.19
|
||||
cffi==2.0.0
|
||||
charset-normalizer==3.4.3
|
||||
cryptography==46.0.2
|
||||
cffi==2.1.1
|
||||
charset-normalizer==3.5.2
|
||||
cryptography==50.0.2
|
||||
deno==2.9.7
|
||||
dotenv==0.9.9
|
||||
feedparser==6.0.12
|
||||
idna==3.10
|
||||
invoke==2.2.0
|
||||
mutagen==1.47.0
|
||||
paramiko==4.0.0
|
||||
pycparser==2.23
|
||||
pycryptodomex==3.23.0
|
||||
PyNaCl==1.6.0
|
||||
python-dotenv==1.1.1
|
||||
requests==2.32.5
|
||||
sgmllib3k==1.0.0
|
||||
simple-logger-handler==0.1.0
|
||||
sponsorblock.py==0.2.3
|
||||
urllib3==2.5.0
|
||||
websockets==15.0.1
|
||||
feedparser==6.0.14
|
||||
feedparser-sgmllib==2.1.0
|
||||
idna==3.20
|
||||
invoke==3.0.3
|
||||
mutagen==1.48.1
|
||||
paramiko==5.1.0
|
||||
pycparser==3.11
|
||||
pycryptodomex==3.24.1
|
||||
PyNaCl==1.6.2
|
||||
python-dotenv==1.2.4
|
||||
python-json-logger==4.2.0
|
||||
requests==2.34.2
|
||||
simple-logger-handler==0.2.1
|
||||
sponsorblock.py==1.0.0
|
||||
urllib3==2.8.0
|
||||
websockets==17.3
|
||||
@@ -1,11 +1,22 @@
|
||||
import feedparser
|
||||
from simple_logger_handler import setup_logger
|
||||
import time
|
||||
from urllib.error import URLError
|
||||
from typing import Optional
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
|
||||
import feedparser
|
||||
from simple_logger_handler import setup_logger
|
||||
|
||||
|
||||
class FeedError(Exception):
|
||||
"""Base class for all errors raised while fetching or reading the feed."""
|
||||
|
||||
|
||||
class FeedUnavailableError(FeedError):
|
||||
"""The feed could not be fetched (network failure or HTTP error status)."""
|
||||
|
||||
|
||||
class FeedParseError(FeedError):
|
||||
"""The feed was fetched but its content is empty, malformed or unexpected."""
|
||||
|
||||
@dataclass
|
||||
class EpisodeData:
|
||||
episode_date: str
|
||||
@@ -17,12 +28,24 @@ class EpisodeData:
|
||||
|
||||
logger = setup_logger(__name__)
|
||||
|
||||
def grab_latest_chapter_information(id: str, max_retries: int = 3) -> Optional[EpisodeData]:
|
||||
def fetch_feed(url: str) -> feedparser.FeedParserDict:
|
||||
feed = feedparser.parse(url)
|
||||
status = feed.get("status")
|
||||
|
||||
if status is None:
|
||||
raise FeedUnavailableError(f"No HTTP response: {feed.get('bozo_exception')}")
|
||||
if status >= 400:
|
||||
raise FeedUnavailableError(f"HTTP {status}")
|
||||
if not feed.entries:
|
||||
raise FeedParseError(f"No entries in feed (bozo={feed.get('bozo_exception')})")
|
||||
return feed
|
||||
|
||||
def grab_latest_chapter_information(channel_id: str, max_retries: int = 3) -> EpisodeData:
|
||||
"""
|
||||
Fetches the latest episodes information from a Youtube RSS feed, with retries on network-related errors.
|
||||
|
||||
Parameters:
|
||||
id: Youtube channel ID as a string.
|
||||
channel_id: Youtube channel ID as a string.
|
||||
max_retries: Number of retry attempts if fetching the feed fails due to network issues.
|
||||
|
||||
Returns:
|
||||
@@ -36,50 +59,45 @@ def grab_latest_chapter_information(id: str, max_retries: int = 3) -> Optional[E
|
||||
Returns None if the feed has no entries or all retries are exhausted.
|
||||
|
||||
Raises:
|
||||
ValueError: If the feed has no entries.
|
||||
Other network-related exceptions: If fetching fails after retries.
|
||||
FeedUnavailableError: Feed could not be fetched after all retries.
|
||||
FeedParseError: Feed was fetched but is empty or has unexpected content.
|
||||
"""
|
||||
|
||||
rss_feed_url = f"https://www.youtube.com/feeds/videos.xml?channel_id={id}"
|
||||
rss_feed_url = f"https://www.youtube.com/feeds/videos.xml?channel_id={channel_id}"
|
||||
attempt = 1
|
||||
|
||||
while attempt <= max_retries:
|
||||
logger.debug(f"[Feed] Parsing feed URL: {rss_feed_url} (attempt {attempt}/{max_retries})")
|
||||
try:
|
||||
feed = feedparser.parse(rss_feed_url)
|
||||
|
||||
if not feed.entries:
|
||||
logger.warning(f"[Feed] No entries found for feed {id}")
|
||||
return None
|
||||
|
||||
latest_chapter_data = feed["entries"][0]
|
||||
episode_link = latest_chapter_data["link"]
|
||||
episode_title = latest_chapter_data["title"]
|
||||
episode_description = latest_chapter_data["summary"]
|
||||
episode_date = latest_chapter_data["published"]
|
||||
episode_date = datetime.fromisoformat(episode_date).date().isoformat()
|
||||
episode_ytid = latest_chapter_data["yt_videoid"]
|
||||
|
||||
logger.info(f"[Feed] Latest episode '{episode_title}': {episode_link}")
|
||||
logger.debug(f"[Feed] Latest episode '{episode_title}' (YouTubeId {episode_ytid}): {episode_link} -> {episode_description}")
|
||||
return EpisodeData(
|
||||
episode_date=episode_date,
|
||||
episode_description=episode_description,
|
||||
episode_link=episode_link,
|
||||
episode_number="",
|
||||
episode_title=episode_title,
|
||||
episode_ytid=episode_ytid
|
||||
)
|
||||
|
||||
except (URLError, OSError) as e:
|
||||
logger.warning(f"[Feed] Network error on attempt {attempt} for feed {id}: {e}")
|
||||
feed = fetch_feed(rss_feed_url)
|
||||
break
|
||||
except FeedUnavailableError as e:
|
||||
logger.warning(f"[Feed] Attempt {attempt}/{max_retries} failed for {channel_id}: {e}")
|
||||
if attempt == max_retries:
|
||||
logger.error(f"[Feed] All {max_retries} attempts failed for feed {id}")
|
||||
return None
|
||||
logger.error(f"[Feed] All {max_retries} attempts failed for feed {channel_id}")
|
||||
raise
|
||||
backoff = 2 ** (attempt - 1)
|
||||
logger.debug(f"[Feed] Retrying in {backoff} seconds...")
|
||||
time.sleep(backoff)
|
||||
attempt += 1
|
||||
|
||||
latest = feed.entries[0]
|
||||
try:
|
||||
episode_date = datetime.fromisoformat(latest["published"]).date().isoformat()
|
||||
episode = EpisodeData(
|
||||
episode_date=episode_date,
|
||||
episode_description=latest["summary"],
|
||||
episode_link=latest["link"],
|
||||
episode_number="",
|
||||
episode_title=latest["title"],
|
||||
episode_ytid=latest["yt_videoid"],
|
||||
)
|
||||
except (KeyError, ValueError) as e:
|
||||
raise FeedParseError(f"Unexpected entry format for {channel_id}: {e!r}") from e
|
||||
|
||||
logger.info(f"[Feed] Latest episode '{episode.episode_title}': {episode.episode_link}")
|
||||
logger.debug(f"[Feed] Latest episode '{episode.episode_title}' (YouTubeId {episode.episode_ytid}): {episode.episode_link} -> {episode.episode_description}")
|
||||
return episode
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(grab_latest_chapter_information("UCC3ehuUksTyQ7bbjGntmx3Q"))
|
||||
+12
-11
@@ -1,10 +1,11 @@
|
||||
import paramiko
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
from json import dumps
|
||||
from simple_logger_handler import setup_logger
|
||||
import time
|
||||
import shlex
|
||||
import time
|
||||
from json import dumps
|
||||
|
||||
import paramiko
|
||||
from dotenv import load_dotenv
|
||||
from simple_logger_handler import setup_logger
|
||||
|
||||
logger = setup_logger(__name__)
|
||||
load_dotenv()
|
||||
@@ -78,8 +79,8 @@ def create_ssh_client(hostname: str, port: int, username: str, keyfile: str)-> p
|
||||
ssh.connect(hostname=hostname, username=username, port=port, pkey=pkey)
|
||||
logger.debug("[SSH] SSH connection established successfully")
|
||||
return ssh
|
||||
except Exception as e:
|
||||
logger.error(f"[SSH] SSH connection failed: {e}", exc_info=True)
|
||||
except Exception:
|
||||
logger.exception("[SSH] SSH connection failed")
|
||||
raise
|
||||
|
||||
|
||||
@@ -110,8 +111,8 @@ def upload_via_sftp(filename) -> None:
|
||||
sftp.close()
|
||||
transport.close()
|
||||
logger.info(f"[SFTP] File '{filename}' uploaded successfully")
|
||||
except Exception as e:
|
||||
logger.error(f"[SFTP] SFTP upload failed for '{filename}': {e}", exc_info=True)
|
||||
except Exception:
|
||||
logger.exception(f"[SFTP] SFTP upload failed for '{filename}'")
|
||||
raise
|
||||
|
||||
|
||||
@@ -171,8 +172,8 @@ def send_notification_via_ssh(notification_title, notification_info) -> None:
|
||||
logger.warning(f"[Notification] Notification command exited with {exit_status}")
|
||||
logger.warning(f"[Notification] Error: {error_output}")
|
||||
logger.warning(f"[Notification] Response: {response_output}")
|
||||
except Exception as e:
|
||||
logger.error(f"[Notification] Failed to send SSH notification: {e}", exc_info=True)
|
||||
except Exception:
|
||||
logger.exception("[Notification] Failed to send SSH notification")
|
||||
raise
|
||||
finally:
|
||||
if ssh:
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import yt_dlp
|
||||
import datetime
|
||||
import contextlib
|
||||
from dotenv import load_dotenv
|
||||
import os
|
||||
from helper import return_string_as_html
|
||||
from simple_logger_handler import setup_logger
|
||||
import json
|
||||
import sponsorblock as sb
|
||||
import os
|
||||
|
||||
import sponsorblock as sb
|
||||
from dotenv import load_dotenv
|
||||
from simple_logger_handler import setup_logger
|
||||
|
||||
from helper import return_string_as_html
|
||||
|
||||
logger = setup_logger(__name__)
|
||||
load_dotenv()
|
||||
|
||||
Reference in new issue
Block a user