diff --git a/.forgejo/workflows/build.yml b/.forgejo/workflows/build.yml index ef20d03..f7865c3 100644 --- a/.forgejo/workflows/build.yml +++ b/.forgejo/workflows/build.yml @@ -49,10 +49,10 @@ jobs: docker buildx inspect --bootstrap - name: Lint Python code - run: ruff check --exit-non-zero-on-fix --verbose + run: ruff check --exit-non-zero-on-fix - name: Check Python formatting - run: ruff format --check --verbose + run: ruff format --check - name: Lint Dockerfile run: docker build --check . diff --git a/discord_rss_bot/custom_message.py b/discord_rss_bot/custom_message.py index 46224cf..e5f1b14 100644 --- a/discord_rss_bot/custom_message.py +++ b/discord_rss_bot/custom_message.py @@ -155,7 +155,7 @@ def replace_tags_in_text_message(entry: Entry, reader: Reader) -> str: entry_updated: str = entry.updated.strftime("%Y-%m-%d %H:%M:%S") if entry.updated else "Never" list_of_replacements: list[dict[str, str]] = [ - {"{{feed_author}}": feed.author or ""}, + {"{{feed_author}}": feed.authors_str or ""}, {"{{feed_added}}": feed_added}, {"{{feed_last_exception}}": feed_last_exception}, {"{{feed_last_updated}}": feed_last_updated}, @@ -168,7 +168,7 @@ def replace_tags_in_text_message(entry: Entry, reader: Reader) -> str: {"{{feed_user_title}}": feed.user_title or ""}, {"{{feed_version}}": feed.version or ""}, {"{{entry_added}}": entry_added}, - {"{{entry_author}}": entry.author or ""}, + {"{{entry_author}}": entry.authors_str or ""}, {"{{entry_content}}": content}, {"{{entry_content_raw}}": entry.content[0].value if entry.content else ""}, {"{{entry_id}}": entry.id or ""}, @@ -318,7 +318,7 @@ def replace_tags_in_embed(feed: Feed, entry: Entry, reader: Reader) -> CustomEmb embed.title = "" list_of_replacements: list[dict[str, str]] = [ - {"{{feed_author}}": feed.author or ""}, + {"{{feed_author}}": feed.authors_str or ""}, {"{{feed_added}}": feed_added or ""}, {"{{feed_last_exception}}": feed_last_exception}, {"{{feed_last_updated}}": feed_last_updated or ""}, @@ -331,7 +331,7 @@ def replace_tags_in_embed(feed: Feed, entry: Entry, reader: Reader) -> CustomEmb {"{{feed_user_title}}": feed.user_title or ""}, {"{{feed_version}}": feed.version or ""}, {"{{entry_added}}": entry_added or ""}, - {"{{entry_author}}": entry.author or ""}, + {"{{entry_author}}": entry.authors_str or ""}, {"{{entry_content}}": content or ""}, {"{{entry_content_raw}}": entry.content[0].value if entry.content else ""}, {"{{entry_id}}": entry.id}, diff --git a/discord_rss_bot/feeds.py b/discord_rss_bot/feeds.py index 87cd162..833bf33 100644 --- a/discord_rss_bot/feeds.py +++ b/discord_rss_bot/feeds.py @@ -22,9 +22,11 @@ from urllib.parse import parse_qs from urllib.parse import urljoin from urllib.parse import urlparse -import httpx +import httpx2 import tldextract from fastapi import HTTPException +from httpx2 import HTTPError +from httpx2 import Response from markdownify import markdownify from playwright.sync_api import Browser from playwright.sync_api import Page @@ -702,7 +704,7 @@ def get_webhook_files(webhook: DiscordWebhook) -> list[WebhookFile]: # noqa: C9 return files -def get_retry_after_seconds(response: httpx.Response) -> float | None: +def get_retry_after_seconds(response: Response) -> float | None: """Return Discord's retry delay for a rate-limited response when available.""" response_json: JsonObject = get_response_json(response) retry_after: JsonValue = response_json.get("retry_after") @@ -727,7 +729,7 @@ def request_discord_webhook( files: list[WebhookFile] | None, timeout: float, rate_limit_retry: bool, -) -> httpx.Response: +) -> Response: """Send a Discord webhook request with optional multipart files. Returns: @@ -742,7 +744,7 @@ def request_discord_webhook( else: request_kwargs["json"] = payload - response: httpx.Response = httpx.request(method, url, **request_kwargs) + response: Response = httpx2.request(method, url, **request_kwargs) if not rate_limit_retry or response.status_code != 429: # noqa: PLR2004 return response @@ -751,11 +753,11 @@ def request_discord_webhook( return response time.sleep(max(0.0, retry_after)) - return httpx.request(method, url, **request_kwargs) + return httpx2.request(method, url, **request_kwargs) -def send_webhook_message(webhook: DiscordWebhook, payload: JsonObject) -> httpx.Response: - """Execute a Discord webhook message create request using httpx. +def send_webhook_message(webhook: DiscordWebhook, payload: JsonObject) -> Response: + """Execute a Discord webhook message create request using httpx2. Returns: Discord API response. @@ -777,11 +779,11 @@ def edit_sent_webhook_message( message_id: str, webhook: DiscordWebhook, payload: JsonObject, -) -> httpx.Response: +) -> Response: """Edit an already-sent Discord webhook message. Returns: - httpx.Response: Discord API response. + Response: Discord API response. """ clean_webhook_url, params = get_webhook_query_params(webhook_url, payload, webhook=webhook, wait=True) return request_discord_webhook( @@ -938,8 +940,13 @@ def update_sent_webhook_record_for_entry( now: str = datetime.datetime.now(tz=datetime.UTC).isoformat() try: - response = edit_sent_webhook_message(webhook_url_value, message_id_value, webhook, edit_payload) - except (AssertionError, RequestException, httpx.HTTPError, OSError, ValueError) as e: + response: Response = edit_sent_webhook_message( + webhook_url=webhook_url_value, + message_id=message_id_value, + webhook=webhook, + payload=edit_payload, + ) + except (AssertionError, RequestException, HTTPError, OSError, ValueError) as e: logger.exception("Failed to edit Discord webhook message %s for entry %s", message_id_value, entry.id) return ( { @@ -1484,13 +1491,13 @@ def fetch_ttvdrops_campaign_media_items(entry: Entry) -> list[JsonObject]: return [] try: - response: httpx.Response = httpx.get(api_url, follow_redirects=True, timeout=10.0) + response: Response = httpx2.get(api_url, follow_redirects=True, timeout=10.0) if response.status_code != 200: # noqa: PLR2004 logger.warning("Failed to fetch ttvdrops campaign data from %s: %s", api_url, response.text[:500]) return [] response_json = cast("JsonValue", response.json()) - except (httpx.HTTPError, ValueError, TypeError): + except (HTTPError, ValueError, TypeError): logger.exception("Failed to fetch ttvdrops campaign data from %s", api_url) return [] @@ -1759,7 +1766,7 @@ def send_to_discord(reader: Reader | None = None, feed: Feed | None = None, *, d ) try: update_sent_webhooks_for_modified_entries(effective_reader, modified_entries) - except (AssertionError, ReaderError, RequestException, httpx.HTTPError, OSError, ValueError): + except (AssertionError, ReaderError, RequestException, HTTPError, OSError, ValueError): logger.exception("Failed to update saved Discord webhooks for modified feed entries.") # Loop through the unread entries. @@ -1826,7 +1833,7 @@ def execute_webhook( request_payload: JsonObject = get_webhook_request_payload(webhook) payload: JsonObject = get_webhook_message_payload(webhook) - response: httpx.Response = send_webhook_message(webhook, request_payload) + response: Response = send_webhook_message(webhook, request_payload) logger.debug("Discord webhook response for entry %s: status=%s", entry.id, response.status_code) if response.status_code not in {200, 204}: msg: str = f"Error sending entry to Discord: {response.text}\n{pprint.pformat(request_payload)}" diff --git a/discord_rss_bot/filter/evaluator.py b/discord_rss_bot/filter/evaluator.py index 30ca4ca..01851db 100644 --- a/discord_rss_bot/filter/evaluator.py +++ b/discord_rss_bot/filter/evaluator.py @@ -245,7 +245,7 @@ def get_entry_fields(entry: Entry) -> dict[str, str]: "title": entry.title or "", "summary": entry.summary or "", "content": content_value, - "author": entry.author or "", + "author": entry.authors_str or "", } diff --git a/discord_rss_bot/main.py b/discord_rss_bot/main.py index d3ee932..a27199b 100644 --- a/discord_rss_bot/main.py +++ b/discord_rss_bot/main.py @@ -18,7 +18,7 @@ from typing import Annotated from typing import TypedDict from typing import cast -import httpx +import httpx2 import sentry_sdk import uvicorn from apscheduler.schedulers.asyncio import AsyncIOScheduler @@ -30,7 +30,8 @@ from fastapi import Request from fastapi.responses import HTMLResponse from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates -from httpx import Response +from httpx2 import HTTPError +from httpx2 import Response from markdownify import markdownify from reader import Entry from reader import EntryNotFoundError @@ -82,6 +83,7 @@ from discord_rss_bot.settings import get_reader if TYPE_CHECKING: from collections.abc import AsyncGenerator from collections.abc import Iterable + from pathlib import Path from reader.types import JSONType @@ -1881,7 +1883,7 @@ def create_html_for_feed( # noqa: C901, PLR0914 html += f"""
- {% if row.entry.author %}By {{ row.entry.author }} |{% endif %} + {% if row.entry.authors_str %}By {{ row.entry.authors_str }} |{% endif %} {{ row.published_label }}
@@ -114,7 +114,7 @@
{% raw %}
{{entry_author}}
{% endraw %}
- {{ entry.author }}
+ {{ entry.authors_str }}
@@ -107,7 +107,7 @@
{% raw %}
{{entry_author}}
{% endraw %}
- {{entry.author}}
+ {{entry.authors_str}}
Massive MMO news update
", author="Blizzard", + authors_str="Blizzard", link="https://example.com/wow-1", published=datetime(2024, 1, 1, tzinfo=UTC), content=[DummyContent("The expansion launches soon.
")], @@ -1461,6 +1465,7 @@ def test_create_html_marks_entries_from_another_feed(monkeypatch: pytest.MonkeyP link: str = "https://example.com/post" title: str = "Example title" author: str = "Author" + authors_str: str = "Author" summary: str = "Summary" content: list[DummyContent] = field(default_factory=lambda: [DummyContent("Content")]) published: None = None diff --git a/tests/test_update_interval.py b/tests/test_update_interval.py index 26c5421..0d9f948 100644 --- a/tests/test_update_interval.py +++ b/tests/test_update_interval.py @@ -8,7 +8,7 @@ from fastapi.testclient import TestClient from discord_rss_bot.main import app if TYPE_CHECKING: - from httpx import Response + from httpx2 import Response client: TestClient = TestClient(app) webhook_name: str = "Test Webhook for Update Interval"