fix(commands): /albums honors per-chat scope, disable link previews

- /albums ignored CommandTrackerListener.allowed_album_ids and listed
  every album tracked by the provider — scoped chats saw neighbours'
  albums.  Thread the listener through _cmd_albums and apply the same
  intersect filter the media commands already use in _cmd_immich.
- Command text replies are listings (albums, events, people, ...) that
  embed multiple links; Telegram's default behavior of rendering a
  preview for the first URL is never useful here and ignored the
  "Disable link previews" toggle operators set on their target.  Always
  pass disable_web_page_preview=True from send_reply.
This commit is contained in:
2026-04-22 03:03:09 +03:00
parent 83215473c7
commit 4ff3876e49
3 changed files with 27 additions and 5 deletions
@@ -348,12 +348,23 @@ async def send_reply(
bot_token: str, chat_id: str, text: str, reply_to_message_id: int | None = None,
session: aiohttp.ClientSession | None = None,
) -> None:
"""Send a text reply via TelegramClient."""
"""Send a text reply via TelegramClient.
Command responses are listings (albums, people, events, ...) that embed
multiple links; Telegram's default behavior of rendering a preview of
the first URL is almost never what the user wants and clashes with the
"Disable link previews" toggle operators set on their Telegram target.
We always pass ``disable_web_page_preview=True`` here.
"""
if session is None:
from ..services.http_session import get_http_session
session = await get_http_session()
client = TelegramClient(session, bot_token)
result = await client.send_message(chat_id, text, reply_to_message_id=reply_to_message_id)
result = await client.send_message(
chat_id, text,
reply_to_message_id=reply_to_message_id,
disable_web_page_preview=True,
)
if not result.get("success"):
_LOGGER.warning("Telegram reply failed: %s", result.get("error"))
@@ -6,7 +6,7 @@ import asyncio
import logging
from typing import Any
from ...database.models import ServiceProvider
from ...database.models import CommandTrackerListener, ServiceProvider
from ...services import make_immich_provider
from ...services.http_session import get_http_session
from ..command_utils import get_trackers_for_provider
@@ -17,7 +17,10 @@ _LOGGER = logging.getLogger(__name__)
async def _cmd_albums(
provider: ServiceProvider, locale: str,
provider: ServiceProvider,
locale: str,
*,
listener: CommandTrackerListener | None = None,
) -> dict[str, Any]:
trackers = await get_trackers_for_provider(provider.id)
if not trackers:
@@ -31,6 +34,14 @@ async def _cmd_albums(
if aid not in seen:
seen.add(aid)
album_ids.append(aid)
# Per-chat album scope — match what _cmd_immich does for media commands.
# Without this, /albums leaks the full list of tracked albums into chats
# that were explicitly scoped to a subset.
if listener is not None and listener.allowed_album_ids is not None:
allowed = set(listener.allowed_album_ids)
album_ids = [aid for aid in album_ids if aid in allowed]
if not album_ids:
return {"albums": []}
@@ -86,7 +86,7 @@ class ImmichCommandHandler(ProviderCommandHandler):
ctx = await _cmd_status(provider, locale)
return CommandResponse(text=_render_cmd_template(cmd_templates, "status", locale, ctx))
if cmd == "albums":
ctx = await _cmd_albums(provider, locale)
ctx = await _cmd_albums(provider, locale, listener=listener)
return CommandResponse(text=_render_cmd_template(cmd_templates, "albums", locale, ctx))
if cmd == "events":
ctx = await _cmd_events(provider, count, locale)