From d5d9194bef5c32ad0c9004ca9ed160e6c40373f1 Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 22 Jul 2026 22:15:52 +0500 Subject: [PATCH] Store MAX publication URLs --- scripts/backfill_max_publication_urls.py | 66 ++++++++++++++++++++++++ src/vk_parser_app/workers/max_poster.py | 17 +++++- 2 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 scripts/backfill_max_publication_urls.py diff --git a/scripts/backfill_max_publication_urls.py b/scripts/backfill_max_publication_urls.py new file mode 100644 index 0000000..73fb469 --- /dev/null +++ b/scripts/backfill_max_publication_urls.py @@ -0,0 +1,66 @@ +from __future__ import annotations + +import asyncio + +from loguru import logger + +from vk_parser_app.constants import PUBLICATION_STATUS_PUBLISHED +from vk_parser_app.db import close_pool, fetch_int_setting, fetch_setting, get_pool +from vk_parser_app.workers.max_poster import MAXAPIClient, MAXPoster + + +async def main() -> None: + pool = await get_pool() + token = str(await fetch_setting("max_poster_bot_token", "") or "").strip() + base_url = str(await fetch_setting("max_poster_api_base_url", "https://platform-api2.max.ru") or "").strip() + if not token: + raise RuntimeError("max_poster_bot_token is empty") + + rows = await pool.fetch( + """ + SELECT id, raw_post_id, external_id + FROM post_publications + WHERE platform='max' + AND status=$1 + AND COALESCE(url, '')='' + AND COALESCE(external_id, '')<>'' + ORDER BY published_at DESC NULLS LAST, id DESC + """, + PUBLICATION_STATUS_PUBLISHED, + ) + + poster = MAXPoster() + updated = 0 + missing = 0 + async with MAXAPIClient( + token, + base_url, + timeout_sec=max(30, await fetch_int_setting("max_poster_timeout_sec", 120)), + max_attempts=max(1, await fetch_int_setting("max_poster_max_attempts", 3)), + retry_backoff_max_sec=max(1, await fetch_int_setting("max_poster_retry_backoff_max_sec", 30)), + ) as client: + for row in rows: + data = await client.get_message(str(row["external_id"])) + url = poster.message_url_from_response(data) + if not url: + missing += 1 + continue + await pool.execute( + """ + UPDATE post_publications + SET url=$2, + updated_at=NOW() + WHERE id=$1 + """, + int(row["id"]), + url, + ) + updated += 1 + logger.info("MAX URL backfilled raw_post={} url={}", row["raw_post_id"], url) + + logger.info("MAX URL backfill done: scanned={} updated={} missing={}", len(rows), updated, missing) + await close_pool() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/src/vk_parser_app/workers/max_poster.py b/src/vk_parser_app/workers/max_poster.py index 1b4cace..8882944 100644 --- a/src/vk_parser_app/workers/max_poster.py +++ b/src/vk_parser_app/workers/max_poster.py @@ -161,6 +161,9 @@ class MAXAPIClient: payload["attachments"] = attachments return await self.request("POST", f"/messages?chat_id={chat_id}", json=payload) + async def get_message(self, message_id: str) -> dict[str, Any]: + return await self.request("GET", f"/messages/{quote(message_id, safe='')}") + async def get_video_info(self, token: str) -> dict[str, Any]: return await self.request("GET", f"/videos/{quote(token, safe='')}") @@ -486,6 +489,13 @@ class MAXPoster: candidates.extend([body.get("id"), body.get("mid"), body.get("message_id")]) return next((str(value) for value in candidates if value), "") + def message_url_from_response(self, data: dict[str, Any]) -> str | None: + candidates = [data.get("url")] + message = data.get("message") if isinstance(data.get("message"), dict) else {} + body = message.get("body") if isinstance(message.get("body"), dict) else {} + candidates.extend([message.get("url"), body.get("url")]) + return next((str(value).strip() for value in candidates if str(value or "").strip()), None) + async def send_message_waiting_for_media( self, client: MAXAPIClient, @@ -579,10 +589,15 @@ class MAXPoster: first_text = chunks[0] if chunks else "" first = await self.send_message_waiting_for_media(client, first_text, attachments) message = first.get("message") if isinstance(first.get("message"), dict) else first - first_url = message.get("url") if isinstance(message, dict) else None + first_url = self.message_url_from_response(message) if isinstance(message, dict) else None first_message_id = self.message_id_from_response(first) if first_message_id: message_ids.append(first_message_id) + if not first_url: + try: + first_url = self.message_url_from_response(await client.get_message(first_message_id)) + except Exception as exc: + logger.warning("MAX message URL lookup failed message={}: {}", first_message_id, exc) if self.auto_reaction_enabled and self.reaction_path_template: try: await client.react(self.reaction_path_template, first_message_id, self.auto_reaction)