From e2c120d99fc25854b2108aef055ad3c422935580 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Hells=C3=A9n?= Date: Tue, 21 Jul 2026 06:20:19 +0200 Subject: [PATCH] Add webhook avatar and username to embed settings Allow configuring the webhook identity (name and avatar) directly from the embed settings page. The embed-level values are applied before per-feed tag overrides and extension overrides, so they act as a baseline that can still be overridden by the custom message page or extensions. --- discord_rss_bot/custom_message.py | 13 +++++- discord_rss_bot/extensions/hoyolab.py | 12 ++++-- discord_rss_bot/feeds.py | 20 ++++++++- discord_rss_bot/main.py | 12 +++++- discord_rss_bot/templates/embed.html | 60 ++++++++++++++++++++------- 5 files changed, 96 insertions(+), 21 deletions(-) diff --git a/discord_rss_bot/custom_message.py b/discord_rss_bot/custom_message.py index 2ec3a17..a9411fc 100644 --- a/discord_rss_bot/custom_message.py +++ b/discord_rss_bot/custom_message.py @@ -41,6 +41,8 @@ class CustomEmbed: footer_text: str = "" footer_icon_url: str = "" show_steam_game_icon_in_thumbnail: bool = True + avatar_url: str = "" + username: str = "" def try_to_replace(custom_message: str, template: str, replace_with: str) -> str: @@ -313,7 +315,6 @@ def replace_tags_in_embed(feed: Feed, entry: Entry, reader: Reader) -> CustomEmb embed.thumbnail_url = embed.thumbnail_url.replace("\\n", "\n") embed.footer_text = embed.footer_text.replace("\\n", "\n") embed.footer_icon_url = embed.footer_icon_url.replace("\\n", "\n") - return embed @@ -334,6 +335,8 @@ def _replace_embed_tags(embed: CustomEmbed, template: str, replace_with: str) -> embed.thumbnail_url = try_to_replace(embed.thumbnail_url, template, replace_with) embed.footer_text = try_to_replace(embed.footer_text, template, replace_with) embed.footer_icon_url = try_to_replace(embed.footer_icon_url, template, replace_with) + embed.avatar_url = try_to_replace(embed.avatar_url, template, replace_with) + embed.username = try_to_replace(embed.username, template, replace_with) def get_custom_message(reader: Reader, feed: Feed) -> str: @@ -461,6 +464,8 @@ def save_embed(reader: Reader, feed: Feed, embed: CustomEmbed) -> None: "footer_text": embed.footer_text, "footer_icon_url": embed.footer_icon_url, "show_steam_game_icon_in_thumbnail": embed.show_steam_game_icon_in_thumbnail, + "avatar_url": embed.avatar_url, + "username": embed.username, } reader.set_tag(feed, "embed", json.dumps(embed_dict)) # pyright: ignore[reportArgumentType] @@ -495,6 +500,8 @@ def get_embed(reader: Reader, feed: Feed) -> CustomEmbed: footer_text="", footer_icon_url="", show_steam_game_icon_in_thumbnail=True, + avatar_url="", + username="", ) @@ -521,6 +528,8 @@ def get_embed_data(embed_data: dict[str, str | int | bool]) -> CustomEmbed: "show_steam_game_icon_in_thumbnail", True, ) + avatar_url: str = str(embed_data.get("avatar_url", "")) + username: str = str(embed_data.get("username", "")) return CustomEmbed( title=title, @@ -534,4 +543,6 @@ def get_embed_data(embed_data: dict[str, str | int | bool]) -> CustomEmbed: footer_text=footer_text, footer_icon_url=footer_icon_url, show_steam_game_icon_in_thumbnail=show_steam_game_icon_in_thumbnail, + avatar_url=avatar_url, + username=username, ) diff --git a/discord_rss_bot/extensions/hoyolab.py b/discord_rss_bot/extensions/hoyolab.py index 9cbc832..4078cc0 100644 --- a/discord_rss_bot/extensions/hoyolab.py +++ b/discord_rss_bot/extensions/hoyolab.py @@ -454,13 +454,19 @@ class HoyolabExtension(FeedExtension): webhook.add_file(file=video_response.content, filename=f"{entry.id}.mp4") def _apply_author_from_post(self, webhook: DiscordWebhook, post_data: JsonObject) -> None: - """Set webhook author (username and avatar) from post user data.""" + """Set webhook author (username and avatar) from post user data. + + Only sets values that haven't been explicitly configured via + the feed's embed or message template settings. + """ user: JsonObject = _as_json_object(post_data.get("user")) author_name: str = str(user.get("nickname", "")) avatar_url: str = str(user.get("avatar_url", "")) if author_name: - webhook.avatar_url = avatar_url - webhook.username = author_name + if not webhook.username: + webhook.username = author_name + if not webhook.avatar_url: + webhook.avatar_url = avatar_url def _apply_structured_content(self, webhook: DiscordWebhook, post_data: JsonObject) -> None: """Parse structured content for YouTube embeds and add them to the webhook.""" diff --git a/discord_rss_bot/feeds.py b/discord_rss_bot/feeds.py index 6de7b0a..5d6da2f 100644 --- a/discord_rss_bot/feeds.py +++ b/discord_rss_bot/feeds.py @@ -52,6 +52,7 @@ from discord_rss_bot.custom_message import get_custom_message from discord_rss_bot.custom_message import get_image_urls from discord_rss_bot.custom_message import get_validated_message_avatar_url from discord_rss_bot.custom_message import get_validated_message_username +from discord_rss_bot.custom_message import normalize_message_username from discord_rss_bot.custom_message import replace_tags_in_embed from discord_rss_bot.custom_message import replace_tags_in_text_message from discord_rss_bot.extensions import auto_enable_extensions_for_feed @@ -1754,12 +1755,14 @@ def create_components_v2_webhook( }, create_media_gallery_component(media_items), ] - return DiscordWebhook( + webhook: DiscordWebhook = DiscordWebhook( url=webhook_url, flags=1 << 15, components=components, rate_limit_retry=True, ) + _apply_embed_identity(webhook, custom_embed) + return webhook def create_embed_webhook( # ruff:ignore[complex-structure, too-many-branches] @@ -1851,10 +1854,25 @@ def create_embed_webhook( # ruff:ignore[complex-structure, too-many-branches] if custom_embed.footer_icon_url and not custom_embed.footer_text: discord_embed.set_footer(text="-", icon_url=custom_embed.footer_icon_url) + _apply_embed_identity(webhook, custom_embed) webhook.add_embed(discord_embed) return webhook +def _apply_embed_identity(webhook: DiscordWebhook, custom_embed: CustomEmbed) -> None: + """Apply the embed's avatar_url and username to the webhook. + + Sets the webhook-level avatar and display name from the custom embed + configuration, if valid. These can be overridden later by per-feed + ``message_avatar_url`` / ``message_username`` tags. + """ + if custom_embed.avatar_url and is_url_valid(custom_embed.avatar_url): + webhook.avatar_url = custom_embed.avatar_url + embed_username: str = normalize_message_username(custom_embed.username) + if embed_username: + webhook.username = embed_username + + def get_webhook_url(reader: Reader, entry: Entry) -> str: """Get the webhook URL for the entry. diff --git a/discord_rss_bot/main.py b/discord_rss_bot/main.py index 4197c1c..c053f76 100644 --- a/discord_rss_bot/main.py +++ b/discord_rss_bot/main.py @@ -1506,6 +1506,8 @@ async def get_embed_page( "thumbnail_url": embed.thumbnail_url, "footer_text": embed.footer_text, "footer_icon_url": embed.footer_icon_url, + "avatar_url": embed.avatar_url, + "username": embed.username, "show_steam_game_icon_in_thumbnail": embed.show_steam_game_icon_in_thumbnail, "is_steam_feed": is_steam_feed_url(feed.url or ""), "extension_variables": extension_variables, @@ -1520,7 +1522,7 @@ async def get_embed_page( @app.post("/embed", response_class=HTMLResponse) -async def post_embed( # ruff:ignore[complex-structure] +async def post_embed( # ruff:ignore[complex-structure, too-many-branches] feed_url: Annotated[str, Form()], reader: Annotated[Reader, Depends(get_reader_dependency)], title: Annotated[str, Form()] = "", @@ -1534,6 +1536,8 @@ async def post_embed( # ruff:ignore[complex-structure] footer_text: Annotated[str, Form()] = "", footer_icon_url: Annotated[str, Form()] = "", show_steam_game_icon_in_thumbnail: Annotated[str, Form()] = "", + avatar_url: Annotated[str, Form()] = "", + username: Annotated[str, Form()] = "", ) -> RedirectResponse: """Set the embed settings. @@ -1550,6 +1554,8 @@ async def post_embed( # ruff:ignore[complex-structure] footer_text: The footer text of the embed. footer_icon_url: The footer icon url of the embed. show_steam_game_icon_in_thumbnail: Whether to use Steam game icon as thumbnail. + avatar_url: Optional webhook avatar URL override for embed messages. + username: Optional webhook display name override for embed messages. reader: The Reader instance. Returns: @@ -1580,6 +1586,10 @@ async def post_embed( # ruff:ignore[complex-structure] custom_embed.footer_text = footer_text if footer_icon_url != custom_embed.footer_icon_url: custom_embed.footer_icon_url = footer_icon_url + if avatar_url != custom_embed.avatar_url: + custom_embed.avatar_url = avatar_url + if username != custom_embed.username: + custom_embed.username = username # Handle the steam thumbnail toggle (checkbox: "true" when checked, "" when unchecked). new_steam_icon_toggle: bool = show_steam_game_icon_in_thumbnail.strip().lower() in {"true", "on", "1"} diff --git a/discord_rss_bot/templates/embed.html b/discord_rss_bot/templates/embed.html index e5ce5da..ff2726d 100644 --- a/discord_rss_bot/templates/embed.html +++ b/discord_rss_bot/templates/embed.html @@ -238,6 +238,8 @@
  • You can remove the embed from links by adding < and > around the link. (For example <{% raw %}{{entry_link}}{% endraw %}>)
  • +
    +
    Embed content
    @@ -247,15 +249,6 @@ - - - - - - @@ -265,7 +258,31 @@ + +
    +
    Author
    + + + + + + + +
    +
    Footer
    + + + + + {% if is_steam_feed %} +
    +
    Options
    {% endif %} - - - - + +
    +
    Webhook identity
    +
    + +
    + + + +