main
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3import contextlib
4import re
5from datetime import UTC, datetime
6from zoneinfo import ZoneInfo
7
8from glom import glom
9from loguru import logger
10from pyrogram.client import Client
11from pyrogram.types import Message
12
13from config import DB, PROXY, TZ
14from database.database import get_db
15from messages.database import copy_messages_from_db, save_messages
16from messages.progress import modify_progress
17from messages.sender import send2tg
18from messages.utils import blockquote, summay_media
19from networking import download_file, download_media, hx_req
20from preview.utils import has_markdown_img
21from utils import nowstr
22
23
24async def preview_reddit(client: Client, message: Message, url: str = "", db_key: str = "", **kwargs):
25 """Preview reddit link in the message.
26
27 Args:
28 client (Client): The Pyrogram client.
29 message (Message): The trigger message object.
30 url (str, optional): Reddit link
31 db_key (str, optional): The cache key.
32 """
33 if kwargs.get("show_progress") and "progress" not in kwargs:
34 res = await send2tg(client, message, texts=f"🔗正在解析Reddit链接\n{url}", **kwargs)
35 kwargs["progress"] = res[0]
36 if kv := await get_db(db_key):
37 logger.debug(f"Reddit preview {DB.ENGINE} cache hit for key={db_key}")
38 if await copy_messages_from_db(client, message, key=db_key, kv=kv, **kwargs):
39 return
40 await modify_progress(text=f"❌从{DB.ENGINE}缓存中转发失败, 尝试重新解析...", **kwargs)
41 logger.info(f"Reddit link preview for {url}")
42
43 post_info = await get_reddit_info(url)
44 if error := post_info.get("error"):
45 await modify_progress(text=f"❌Reddit链接解析失败{url}\n{error}", force_update=True, **kwargs)
46 return
47 sent_messages = await send2tg(client, message, **post_info, **kwargs)
48 await modify_progress(del_status=True, **kwargs)
49 await save_messages(messages=sent_messages, key=db_key)
50
51
52async def get_reddit_info(url: str, **kwargs) -> dict:
53 """Get Reddit post info."""
54 api_url = url + ".json"
55 resp = await hx_req(api_url, proxy=PROXY.REDDIT, check_kv={"0.data.dist": 1, "1.data.children.0.kind": "t1"}, check_keys=["0.data.children.0.data.selftext"], **kwargs)
56 if isinstance(resp, dict) and resp.get("hx_error"):
57 return {"error": resp["hx_error"]}
58 try:
59 data = glom(resp, "0.data.children.0.data")
60 title = data.get("title", "Title")
61 author = data.get("author", "author")
62 author_url = f"https://www.reddit.com/user/{author}"
63 dt = nowstr()
64 with contextlib.suppress(Exception):
65 dt = datetime.fromtimestamp(data["created_utc"], tz=UTC).astimezone(ZoneInfo(TZ))
66 dt = dt.strftime("%Y-%m-%d %H:%M:%S")
67 desc = remove_preview_links(data.get("selftext", "")).strip()
68 texts = f"🎈[{author}]({author_url})\n🕒{dt}\n**📝[{title}]({url})**\n{desc}"
69 media = []
70 if gallery := glom(data, "media_metadata.*", default=[]): # multiple images
71 for img in gallery:
72 ext = img.get("m", "").split("/")[-1] # image/png -> ping
73 img_url = f"https://i.redd.it/{img['id']}.{ext}"
74 media.append({"photo": download_file(img_url, proxy=PROXY.REDDIT, **kwargs)})
75 elif data.get("url", "").startswith("https://i.redd.it/"): # single image
76 media.append({"photo": download_file(data["url"], proxy=PROXY.REDDIT, **kwargs)})
77 if video_url := glom(data, "secure_media.reddit_video.fallback_url", default=""):
78 media.append({"video": download_file(video_url, proxy=PROXY.REDDIT, **kwargs)})
79 comments = ""
80 for reply in glom(resp, "1.data.children.*.data"):
81 author = reply.get("author", "author")
82 author_url = f"https://www.reddit.com/user/{author}"
83 comment = reply.get("body", "")
84 if author == "[deleted]":
85 continue
86 if comment == "[removed]" or has_markdown_img(comment):
87 continue
88 cmt = f"💬**[{author}]({author_url})**: {comment}"
89 comments += f"\n{blockquote(cmt)}"
90 if comments:
91 comments = f"\n{blockquote('💬**点此展开评论区**:')}{comments}"
92 await modify_progress(text=f"⏬正在下载:\n{summay_media(media)}", force_update=True, **kwargs)
93 media = await download_media(media, **kwargs)
94 except Exception as e:
95 logger.error(e)
96 return {"error": str(e)}
97 return {"texts": texts + comments, "media": media}
98
99
100def remove_preview_links(text: str) -> str:
101 """Remove the preview.redd.it links in the post contents."""
102 pattern = r"https?://preview\.redd\.it/\S+\s"
103 return re.sub(pattern, "", text)