From e729d6e93d28f4ed46d3ed366acce6d3219ba2cd Mon Sep 17 00:00:00 2001 From: xtaodada Date: Tue, 22 Oct 2024 21:04:59 +0800 Subject: [PATCH] feat: bsky hidden nsfw content --- defs/bsky.py | 13 ++++++++++--- models/models/bsky.py | 28 +++++++++++++++++++++++++++- modules/bsky_api.py | 18 ++++++++++++------ 3 files changed, 49 insertions(+), 10 deletions(-) diff --git a/defs/bsky.py b/defs/bsky.py index 70720a5..c9dae7d 100644 --- a/defs/bsky.py +++ b/defs/bsky.py @@ -59,7 +59,7 @@ class Timeline: ) @staticmethod - def get_media_group(text: str, post: HumanPost) -> list[InputMediaPhoto]: + def get_media_group(text: str, post: HumanPost, has_spoiler: bool) -> list[InputMediaPhoto]: data = [] images = post.images for idx, image in enumerate(images): @@ -68,6 +68,7 @@ class Timeline: image, caption=text if idx == 0 else None, parse_mode=ParseMode.HTML, + has_spoiler=has_spoiler, ) ) return data @@ -89,8 +90,11 @@ class Timeline: @staticmethod @flood_wait() - async def send_to_user(reply: "Reply", post: HumanPost): + async def send_to_user(reply: "Reply", post: HumanPost, override_hidden: bool): text = Timeline.get_post_text(post) + need_spoiler = post.need_spoiler + if need_spoiler and override_hidden: + need_spoiler = False if post.gif: return await bot.send_animation( reply.cid, @@ -98,6 +102,7 @@ class Timeline: caption=text, reply_to_message_id=reply.mid, parse_mode=ParseMode.HTML, + has_spoiler=need_spoiler, reply_markup=Timeline.get_button(post), ) elif post.video: @@ -108,6 +113,7 @@ class Timeline: thumb=post.video_thumbnail, reply_to_message_id=reply.mid, parse_mode=ParseMode.HTML, + has_spoiler=need_spoiler, reply_markup=Timeline.get_button(post), ) elif not post.images: @@ -126,12 +132,13 @@ class Timeline: caption=text, reply_to_message_id=reply.mid, parse_mode=ParseMode.HTML, + has_spoiler=need_spoiler, reply_markup=Timeline.get_button(post), ) else: await bot.send_media_group( reply.cid, - Timeline.get_media_group(text, post), + Timeline.get_media_group(text, post, need_spoiler), reply_to_message_id=reply.mid, ) diff --git a/models/models/bsky.py b/models/models/bsky.py index 64b55fa..82ef48e 100644 --- a/models/models/bsky.py +++ b/models/models/bsky.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Optional, Union +from typing import TYPE_CHECKING, Optional, Union, List from datetime import datetime @@ -28,6 +28,7 @@ if TYPE_CHECKING: TZ = pytz.timezone("Asia/Shanghai") XRPC_DOMAIN = "bsky.social" +LABELERS = ["did:plc:ar7c4by46qjdydhdevvrndac"] class HumanAuthor(BaseModel): @@ -113,6 +114,8 @@ class HumanPost(BaseModel, frozen=False): author: HumanAuthor + labels: List[str] + is_quote: bool = False is_reply: bool = False is_repost: bool = False @@ -137,11 +140,33 @@ class HumanPost(BaseModel, frozen=False): return "回复" return "发表" + @property + def need_spoiler(self) -> bool: + return any( + label in ["porn", "sexual", "graphic-media", "nudity"] + for label in self.labels + ) + + @staticmethod + def parse_labels( + post: Union["PostView", "BskyViewRecordRecord"], author: HumanAuthor + ) -> List[str]: + labels = [] + if not post.labels: + return labels + labelers = LABELERS.copy() + labelers.append(author.did) + for label in post.labels: + if label.src in labelers: + labels.append(label.val) + return labels + @staticmethod def parse_view(post: Union["PostView", "BskyViewRecordRecord"]) -> "HumanPost": record = post.value if isinstance(post, BskyViewRecordRecord) else post.record # author author = HumanAuthor.parse(post.author) + labels = HumanPost.parse_labels(post, author) embed = ( (post.embeds[0] if post.embeds else None) if isinstance(post, BskyViewRecordRecord) @@ -186,6 +211,7 @@ class HumanPost(BaseModel, frozen=False): repost_count=post.repost_count, uri=post.uri, author=author, + labels=labels, ) @staticmethod diff --git a/modules/bsky_api.py b/modules/bsky_api.py index 479eb0b..4062d62 100644 --- a/modules/bsky_api.py +++ b/modules/bsky_api.py @@ -16,7 +16,7 @@ class Reply(BaseModel): mid: Optional[int] = None -async def process_url(url: str, reply: Reply): +async def process_url(url: str, reply: Reply, override_hidden: bool): url = urlparse(url) if url.hostname and url.hostname in ["bsky.app"]: if url.path.find("profile") < 0: @@ -28,7 +28,7 @@ async def process_url(url: str, reply: Reply): )[0] try: post = await Timeline.fetch_post(author_handle, status_id) - await Timeline.send_to_user(reply, post) + await Timeline.send_to_user(reply, post, override_hidden) except Exception as e: print(e) elif url.path == f"/profile/{author_handle}": @@ -42,7 +42,8 @@ async def process_url(url: str, reply: Reply): @bot.on_message(filters.incoming & filters.text & filters.regex(r"bsky.app/")) async def bsky_share(_: Client, message: Message): - if not message.text: + text = message.text + if not text: return if ( message.sender_chat @@ -51,8 +52,13 @@ async def bsky_share(_: Client, message: Message): ): # 过滤绑定频道的转发 return + if text.startswith("~"): + return + override_hidden = False + if "no" in text or "不隐藏" in text: + override_hidden = True mid = message.id - if message.text.startswith("del") and message.chat.type == ChatType.CHANNEL: + if text.startswith("del") and message.chat.type == ChatType.CHANNEL: with contextlib.suppress(Exception): await message.delete() mid = None @@ -60,10 +66,10 @@ async def bsky_share(_: Client, message: Message): for num in range(len(message.entities)): entity = message.entities[num] if entity.type == MessageEntityType.URL: - url = message.text[entity.offset : entity.offset + entity.length] + url = text[entity.offset : entity.offset + entity.length] elif entity.type == MessageEntityType.TEXT_LINK: url = entity.url else: continue - await process_url(url, reply) + await process_url(url, reply, override_hidden) raise ContinuePropagation