From 551644de5ecbec7c913213ffc5b0f6c9afb03e91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Hells=C3=A9n?= Date: Mon, 5 Dec 2022 11:15:53 +0100 Subject: [PATCH] Move update_feed and add_feed to own function, and create set_hook_by_name --- discord_rss_bot/feeds.py | 125 +++++++++++++++++++++++++++++++----- discord_rss_bot/main.py | 41 ++++++------ discord_rss_bot/webhooks.py | 24 +++++++ 3 files changed, 154 insertions(+), 36 deletions(-) create mode 100644 discord_rss_bot/webhooks.py diff --git a/discord_rss_bot/feeds.py b/discord_rss_bot/feeds.py index 6f464d6..d91557a 100644 --- a/discord_rss_bot/feeds.py +++ b/discord_rss_bot/feeds.py @@ -1,4 +1,7 @@ from discord_webhook import DiscordWebhook +from pydantic import BaseModel +from reader import FeedExistsError, FeedNotFoundError, InvalidFeedURLError, ParseError, StorageError +from requests import Response from discord_rss_bot.settings import logger, reader @@ -7,28 +10,118 @@ def check_feeds() -> None: """Check all feeds""" reader.update_feeds() entries = reader.get_entries(read=False) - _check_feed(entries) + send_to_discord(entries) def check_feed(feed_url: str) -> None: """Check a single feed""" reader.update_feeds() entry = reader.get_entries(feed=feed_url, read=False) - _check_feed(entry) + send_to_discord(entry) -def _check_feed(entries) -> None: - for entry in entries: - reader.mark_entry_as_read(entry) - logger.debug(f"New entry: {entry.title}") +class NoWebhookFoundError(Exception): + """No webhook found error.""" - webhook_url = reader.get_tag(entry.feed.url, "webhook") - if webhook_url: - logger.debug(f"Sending to webhook: {webhook_url}") - webhook = DiscordWebhook(url=str(webhook_url), content=f":robot: :mega: New entry: {entry.title}\n" - f"{entry.link}", rate_limit_retry=True) - response = webhook.execute() - if not response.ok: - # TODO: Send error to discord - logger.error(f"Error: {response.status_code} {response.reason}") - reader.mark_entry_as_unread(entry) + def __init__(self, message): + self.message = message + + def __str__(self): + return self.message + + +def send_to_discord(entry) -> Response: + """ + Send entries to Discord. + + Args: + entry: The entry to send. + + Returns: + Response: The response from the webhook. + """ + + reader.mark_entry_as_read(entry) + logger.debug(f"New entry: {entry.title}") + + webhook_url = reader.get_tag(entry.feed.url, "webhook") + if not webhook_url: + logger.error(f"No webhook found for feed: {entry.feed.url}") + raise NoWebhookFoundError(f"No webhook found for feed: {entry.feed.url}") + + logger.debug(f"Sending to webhook: {webhook_url}") + webhook = DiscordWebhook(url=str(webhook_url), content=f":robot: :mega: New entry: {entry.title}\n" + f"{entry.link}", rate_limit_retry=True) + response = webhook.execute() + if not response.ok: + # TODO: Send error to discord + logger.error(f"Error: {response.status_code} {response.reason}") + reader.mark_entry_as_unread(entry) + return response + + +class IfFeedError(BaseModel): + """Update a feed.""" + feed_url: str + webhook: str + error: bool + err_msg: str = "" + exception: str = "" + + +def update_feed(feed_url: str, webhook: str) -> IfFeedError: + """ + Update a feed. + + Args: + feed_url: The feed to update. + webhook: The webhook to use. + + Returns: + IfFeedError: Error or not. + """ + try: + reader.update_feed(feed_url) + + except FeedNotFoundError as error: + error_msg = "Feed not found" + logger.error(error_msg, exc_info=True) + return IfFeedError(error=True, err_msg=error_msg, feed_url=feed_url, webhook=webhook, exception=error.message) + + except ParseError as error: + error_msg = "An error occurred while getting/parsing feed" + logger.error(error_msg, exc_info=True) + return IfFeedError(error=True, err_msg=error_msg, feed_url=feed_url, webhook=webhook, exception=error.message) + + except StorageError as error: + error_msg = "An exception was raised by the underlying storage" + logger.error(error_msg, exc_info=True) + return IfFeedError(error=True, err_msg=error_msg, feed_url=feed_url, webhook=webhook, exception=error.message) + + return IfFeedError(error=False, feed_url=feed_url, webhook=webhook) + + +def add_feed(feed_url: str, webhook: str, exist_ok=False, allow_invalid_url=False) -> IfFeedError: + """ + Add a feed. + + Args: + feed_url: The feed to add. + webhook: The webhook to use. + exist_ok: If the feed already exists, do nothing. + allow_invalid_url: If the feed url is invalid, add it anyway. + + Returns: + IfFeedError: Error or not. + """ + try: + reader.add_feed(feed=feed_url, exist_ok=exist_ok, allow_invalid_url=allow_invalid_url) + except FeedExistsError as error: + error_msg = "Feed already exists" + logger.error(f"{error_msg}: {error}") + return IfFeedError(error=True, err_msg=error_msg, feed_url=feed_url, webhook=webhook, exception=error.message) + + except InvalidFeedURLError as error: + error_msg = "Invalid feed URL" + logger.error(f"{error_msg}: {error}") + return IfFeedError(error=True, err_msg=error_msg, feed_url=feed_url, webhook=webhook, exception=error.message) diff --git a/discord_rss_bot/main.py b/discord_rss_bot/main.py index 888e067..71b750d 100644 --- a/discord_rss_bot/main.py +++ b/discord_rss_bot/main.py @@ -4,13 +4,14 @@ from functools import cache import uvicorn from apscheduler.schedulers.background import BackgroundScheduler -from fastapi import FastAPI, Form, Request +from fastapi import FastAPI, Form, HTTPException, Request from fastapi.responses import FileResponse, HTMLResponse from fastapi.templating import Jinja2Templates -from reader import FeedExistsError +from reader import ResourceNotFoundError -from discord_rss_bot.feeds import _check_feed +from discord_rss_bot.feeds import add_feed, send_to_discord, update_feed from discord_rss_bot.settings import logger, read_settings_file, reader +from discord_rss_bot.webhooks import set_hook_by_name app = FastAPI() templates = Jinja2Templates(directory="templates") @@ -28,7 +29,7 @@ def check_feed(request: Request, feed_url: str = Form()): """Check all feeds""" reader.update_feeds() entry = reader.get_entries(feed=feed_url, read=False) - _check_feed(entry) + send_to_discord(entry) logger.info(f"Get feed: {feed_url}") feed = reader.get_feed(feed_url) @@ -149,13 +150,6 @@ def create_list_of_webhooks(): return enum.Enum("DiscordWebhooks", list_of_webhooks) -def get_hook_by_name(name): - """Get a webhook by name.""" - settings = read_settings_file() - logger.debug(f"Webhook name: {name} with URL: {settings['webhooks'][name]}") - return settings["webhooks"][name] - - @app.post("/add") async def create_feed(feed_url: str = Form(), webhook_dropdown: str = Form()): """ @@ -170,18 +164,25 @@ async def create_feed(feed_url: str = Form(), webhook_dropdown: str = Form()): """ logger.info(f"Add feed: {feed_url}") logger.info(f"Webhook: {webhook_dropdown}") - try: - reader.add_feed(feed_url) - except FeedExistsError as error: - logger.error(f"Feed already exists: {error}") - return {"error": "Feed already exists."} - reader.update_feed(feed_url) - webhook_url = get_hook_by_name(webhook_dropdown) - reader.set_tag(feed_url, "webhook", webhook_url) + + # Update a single feed. The feed will be updated even if updates are disabled for it. + updated_feed = update_feed(feed_url, webhook_dropdown) + + # Add a new feed to the database. + added_feed = add_feed(feed_url, webhook_dropdown) + + if updated_feed.error or added_feed.error: + error_dict = {"error": updated_feed.error, "feed": updated_feed.feed_url, "webhook": updated_feed.webhook, + "exception": updated_feed.exception} + return HTTPException(status_code=500, detail=error_dict) + + # Check if set_hook_by_name() was successful. + if isinstance(set_hook_by_name(name=webhook_dropdown, feed_url=feed_url), ResourceNotFoundError): + return set_hook_by_name(name=webhook_dropdown, feed_url=feed_url) new_tag = reader.get_tag(feed_url, "webhook") logger.info(f"New tag: {new_tag}") - return {"feed_url": str(feed_url), "status": "added", "webhook": webhook_url} + return {"feed_url": str(feed_url), "status": "added"} @app.get("/add", response_class=HTMLResponse) diff --git a/discord_rss_bot/webhooks.py b/discord_rss_bot/webhooks.py new file mode 100644 index 0000000..6ece80c --- /dev/null +++ b/discord_rss_bot/webhooks.py @@ -0,0 +1,24 @@ +from fastapi import HTTPException +from reader import ResourceNotFoundError + +from discord_rss_bot.settings import logger, read_settings_file, reader + + +def set_hook_by_name(name: str, feed_url: str) -> None or HTTPException: + """Set a webhook by name. + + Args: + name: The name of the webhook. + feed_url: The feed to set the webhook for. + + Returns: + HTTPException: The HTTP exception if the webhook was not found, otherwise None. + """ + settings = read_settings_file() + logger.debug(f"Webhook name: {name} with URL: {settings['webhooks'][name]}") + webhook_url = settings["webhooks"][name] + try: + reader.set_tag(feed_url, "webhook", webhook_url) + except ResourceNotFoundError as e: + logger.error(f"ResourceNotFoundError: {e}") + return HTTPException(status_code=500, detail=f"ResourceNotFoundError: Could not set webhook: {e}")