feat: bsky hidden nsfw content

This commit is contained in:
xtaodada 2024-10-22 21:04:59 +08:00
parent c2f6c1255c
commit e729d6e93d
Signed by: xtaodada
GPG Key ID: 4CBB3F4FA8C85659
3 changed files with 49 additions and 10 deletions

View File

@ -59,7 +59,7 @@ class Timeline:
) )
@staticmethod @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 = [] data = []
images = post.images images = post.images
for idx, image in enumerate(images): for idx, image in enumerate(images):
@ -68,6 +68,7 @@ class Timeline:
image, image,
caption=text if idx == 0 else None, caption=text if idx == 0 else None,
parse_mode=ParseMode.HTML, parse_mode=ParseMode.HTML,
has_spoiler=has_spoiler,
) )
) )
return data return data
@ -89,8 +90,11 @@ class Timeline:
@staticmethod @staticmethod
@flood_wait() @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) text = Timeline.get_post_text(post)
need_spoiler = post.need_spoiler
if need_spoiler and override_hidden:
need_spoiler = False
if post.gif: if post.gif:
return await bot.send_animation( return await bot.send_animation(
reply.cid, reply.cid,
@ -98,6 +102,7 @@ class Timeline:
caption=text, caption=text,
reply_to_message_id=reply.mid, reply_to_message_id=reply.mid,
parse_mode=ParseMode.HTML, parse_mode=ParseMode.HTML,
has_spoiler=need_spoiler,
reply_markup=Timeline.get_button(post), reply_markup=Timeline.get_button(post),
) )
elif post.video: elif post.video:
@ -108,6 +113,7 @@ class Timeline:
thumb=post.video_thumbnail, thumb=post.video_thumbnail,
reply_to_message_id=reply.mid, reply_to_message_id=reply.mid,
parse_mode=ParseMode.HTML, parse_mode=ParseMode.HTML,
has_spoiler=need_spoiler,
reply_markup=Timeline.get_button(post), reply_markup=Timeline.get_button(post),
) )
elif not post.images: elif not post.images:
@ -126,12 +132,13 @@ class Timeline:
caption=text, caption=text,
reply_to_message_id=reply.mid, reply_to_message_id=reply.mid,
parse_mode=ParseMode.HTML, parse_mode=ParseMode.HTML,
has_spoiler=need_spoiler,
reply_markup=Timeline.get_button(post), reply_markup=Timeline.get_button(post),
) )
else: else:
await bot.send_media_group( await bot.send_media_group(
reply.cid, reply.cid,
Timeline.get_media_group(text, post), Timeline.get_media_group(text, post, need_spoiler),
reply_to_message_id=reply.mid, reply_to_message_id=reply.mid,
) )

View File

@ -1,4 +1,4 @@
from typing import TYPE_CHECKING, Optional, Union from typing import TYPE_CHECKING, Optional, Union, List
from datetime import datetime from datetime import datetime
@ -28,6 +28,7 @@ if TYPE_CHECKING:
TZ = pytz.timezone("Asia/Shanghai") TZ = pytz.timezone("Asia/Shanghai")
XRPC_DOMAIN = "bsky.social" XRPC_DOMAIN = "bsky.social"
LABELERS = ["did:plc:ar7c4by46qjdydhdevvrndac"]
class HumanAuthor(BaseModel): class HumanAuthor(BaseModel):
@ -113,6 +114,8 @@ class HumanPost(BaseModel, frozen=False):
author: HumanAuthor author: HumanAuthor
labels: List[str]
is_quote: bool = False is_quote: bool = False
is_reply: bool = False is_reply: bool = False
is_repost: bool = False is_repost: bool = False
@ -137,11 +140,33 @@ class HumanPost(BaseModel, frozen=False):
return "回复" return "回复"
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 @staticmethod
def parse_view(post: Union["PostView", "BskyViewRecordRecord"]) -> "HumanPost": def parse_view(post: Union["PostView", "BskyViewRecordRecord"]) -> "HumanPost":
record = post.value if isinstance(post, BskyViewRecordRecord) else post.record record = post.value if isinstance(post, BskyViewRecordRecord) else post.record
# author # author
author = HumanAuthor.parse(post.author) author = HumanAuthor.parse(post.author)
labels = HumanPost.parse_labels(post, author)
embed = ( embed = (
(post.embeds[0] if post.embeds else None) (post.embeds[0] if post.embeds else None)
if isinstance(post, BskyViewRecordRecord) if isinstance(post, BskyViewRecordRecord)
@ -186,6 +211,7 @@ class HumanPost(BaseModel, frozen=False):
repost_count=post.repost_count, repost_count=post.repost_count,
uri=post.uri, uri=post.uri,
author=author, author=author,
labels=labels,
) )
@staticmethod @staticmethod

View File

@ -16,7 +16,7 @@ class Reply(BaseModel):
mid: Optional[int] = None 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) url = urlparse(url)
if url.hostname and url.hostname in ["bsky.app"]: if url.hostname and url.hostname in ["bsky.app"]:
if url.path.find("profile") < 0: if url.path.find("profile") < 0:
@ -28,7 +28,7 @@ async def process_url(url: str, reply: Reply):
)[0] )[0]
try: try:
post = await Timeline.fetch_post(author_handle, status_id) 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: except Exception as e:
print(e) print(e)
elif url.path == f"/profile/{author_handle}": 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/")) @bot.on_message(filters.incoming & filters.text & filters.regex(r"bsky.app/"))
async def bsky_share(_: Client, message: Message): async def bsky_share(_: Client, message: Message):
if not message.text: text = message.text
if not text:
return return
if ( if (
message.sender_chat message.sender_chat
@ -51,8 +52,13 @@ async def bsky_share(_: Client, message: Message):
): ):
# 过滤绑定频道的转发 # 过滤绑定频道的转发
return return
if text.startswith("~"):
return
override_hidden = False
if "no" in text or "不隐藏" in text:
override_hidden = True
mid = message.id 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): with contextlib.suppress(Exception):
await message.delete() await message.delete()
mid = None mid = None
@ -60,10 +66,10 @@ async def bsky_share(_: Client, message: Message):
for num in range(len(message.entities)): for num in range(len(message.entities)):
entity = message.entities[num] entity = message.entities[num]
if entity.type == MessageEntityType.URL: 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: elif entity.type == MessageEntityType.TEXT_LINK:
url = entity.url url = entity.url
else: else:
continue continue
await process_url(url, reply) await process_url(url, reply, override_hidden)
raise ContinuePropagation raise ContinuePropagation