|
|
@@ -5,15 +5,18 @@ X (Twitter) 平台实现
|
|
|
"""
|
|
|
|
|
|
import json
|
|
|
+import logging
|
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
|
|
import httpx
|
|
|
|
|
|
from agent.tools.models import ToolResult
|
|
|
-from agent.tools.utils.image import build_image_grid, encode_base64, load_images
|
|
|
+from agent.tools.builtin.content.collage import render_image_collage
|
|
|
from agent.tools.builtin.content.registry import PlatformDef, register_platform
|
|
|
from agent.tools.builtin.content.quality import get_post_evaluator
|
|
|
|
|
|
+logger = logging.getLogger(__name__)
|
|
|
+
|
|
|
CRAWLER_URL = "http://crawler.aiddit.com/crawler/x/keyword"
|
|
|
COMMENT_URL = "http://crawler.aiddit.com/crawler/x/comment"
|
|
|
DEFAULT_TIMEOUT = 60.0
|
|
|
@@ -64,8 +67,8 @@ async def search(
|
|
|
"quality_grade": eval_res["grade"]
|
|
|
}
|
|
|
tweet["_quality_score"] = eval_res["total_score"]
|
|
|
- except Exception:
|
|
|
- pass
|
|
|
+ except Exception as exc:
|
|
|
+ logger.debug("X post quality evaluation failed: %s", exc)
|
|
|
|
|
|
summary_item = {
|
|
|
"index": idx,
|
|
|
@@ -101,34 +104,13 @@ KEEP_INDIVIDUAL = 8
|
|
|
|
|
|
|
|
|
async def _build_images_collage(urls: List[str]) -> Optional[Dict[str, Any]]:
|
|
|
- """将一组图片 URL 拼成单张网格图"""
|
|
|
- if not urls:
|
|
|
- return None
|
|
|
-
|
|
|
- loaded = await load_images(urls)
|
|
|
- valid_images = [img for (_, img) in loaded if img is not None]
|
|
|
- if not valid_images:
|
|
|
- return None
|
|
|
+ """Render detail overflow images as one collage."""
|
|
|
|
|
|
- grid = build_image_grid(images=valid_images, labels=None)
|
|
|
- import io
|
|
|
- buf = io.BytesIO()
|
|
|
- grid.save(buf, format="PNG")
|
|
|
- img_bytes = buf.getvalue()
|
|
|
-
|
|
|
- try:
|
|
|
- from agent.tools.builtin.file.image_cdn import _upload_bytes_to_oss
|
|
|
- import hashlib
|
|
|
-
|
|
|
- md5_hash = hashlib.md5(img_bytes).hexdigest()[:12]
|
|
|
- filename = f"x_detail_collage_{md5_hash}.png"
|
|
|
- cdn_url = await _upload_bytes_to_oss(img_bytes, filename)
|
|
|
- return {"type": "url", "url": cdn_url}
|
|
|
- except Exception as e:
|
|
|
- import logging
|
|
|
- logging.getLogger(__name__).warning("Failed to upload x detail collage to CDN: %s", e)
|
|
|
- b64, _ = encode_base64(grid, format="PNG")
|
|
|
- return {"type": "base64", "media_type": "image/png", "data": b64}
|
|
|
+ return await render_image_collage(
|
|
|
+ urls,
|
|
|
+ filename_prefix="x_detail_collage",
|
|
|
+ logger=logger,
|
|
|
+ )
|
|
|
|
|
|
|
|
|
async def _fetch_author_comments(content_id: str, author_id: str) -> List[Dict[str, Any]]:
|
|
|
@@ -247,56 +229,26 @@ async def detail(post: Dict[str, Any], extras: Optional[Dict[str, Any]] = None)
|
|
|
async def _build_tweet_collage(tweets: List[Dict[str, Any]]) -> Optional[str]:
|
|
|
urls, titles = [], []
|
|
|
for tweet in tweets:
|
|
|
- thumb = None
|
|
|
- for img_item in tweet.get("image_url_list", []):
|
|
|
- url = img_item.get("image_url") if isinstance(img_item, dict) else img_item
|
|
|
- if url:
|
|
|
- thumb = url
|
|
|
- break
|
|
|
- if not thumb:
|
|
|
- thumb = tweet.get("cover_url")
|
|
|
- if thumb:
|
|
|
- urls.append(thumb)
|
|
|
- base_title = f"@{tweet.get('channel_account_name', '')}"
|
|
|
+ thumbnail = next(
|
|
|
+ (
|
|
|
+ item.get("image_url") if isinstance(item, dict) else item
|
|
|
+ for item in tweet.get("image_url_list", [])
|
|
|
+ if (item.get("image_url") if isinstance(item, dict) else item)
|
|
|
+ ),
|
|
|
+ None,
|
|
|
+ ) or tweet.get("cover_url")
|
|
|
+ if thumbnail:
|
|
|
+ urls.append(thumbnail)
|
|
|
+ title = f"@{tweet.get('channel_account_name', '')}"
|
|
|
score = tweet.get("_quality_score")
|
|
|
- if score is not None:
|
|
|
- title_with_score = f"[{score}分] {base_title}"
|
|
|
- else:
|
|
|
- title_with_score = base_title
|
|
|
- titles.append(title_with_score)
|
|
|
-
|
|
|
- if not urls:
|
|
|
- return None
|
|
|
-
|
|
|
- loaded = await load_images(urls)
|
|
|
- valid_images, valid_labels = [], []
|
|
|
- for (_, img), title in zip(loaded, titles):
|
|
|
- if img is not None:
|
|
|
- valid_images.append(img)
|
|
|
- valid_labels.append(title)
|
|
|
-
|
|
|
- if not valid_images:
|
|
|
- return None
|
|
|
-
|
|
|
- grid = build_image_grid(images=valid_images, labels=valid_labels)
|
|
|
- import io
|
|
|
- buf = io.BytesIO()
|
|
|
- grid.save(buf, format="PNG")
|
|
|
- img_bytes = buf.getvalue()
|
|
|
-
|
|
|
- try:
|
|
|
- from agent.tools.builtin.file.image_cdn import _upload_bytes_to_oss
|
|
|
- import hashlib
|
|
|
-
|
|
|
- md5_hash = hashlib.md5(img_bytes).hexdigest()[:12]
|
|
|
- filename = f"x_collage_{md5_hash}.png"
|
|
|
- cdn_url = await _upload_bytes_to_oss(img_bytes, filename)
|
|
|
- return {"type": "url", "url": cdn_url}
|
|
|
- except Exception as e:
|
|
|
- import logging
|
|
|
- logging.getLogger(__name__).warning("Failed to upload x collage to CDN: %s", e)
|
|
|
- b64, _ = encode_base64(grid, format="PNG")
|
|
|
- return {"type": "base64", "media_type": "image/png", "data": b64}
|
|
|
+ titles.append(f"[{score}分] {title}" if score is not None else title)
|
|
|
+
|
|
|
+ return await render_image_collage(
|
|
|
+ urls,
|
|
|
+ labels=titles,
|
|
|
+ filename_prefix="x_collage",
|
|
|
+ logger=logger,
|
|
|
+ )
|
|
|
|
|
|
|
|
|
# ── 注册 ──
|