实现 Post.content 相关扩展协议 (#553)

*  `post` 新增 `get_content()` 方法

* 🚨 make linter happy

* 💄 auto fix by pre-commit hooks

* 🐛 fix: 调整函数使用

* 💄 auto fix by pre-commit hooks

*  转用函数处理文本

* 💄 auto fix by pre-commit hooks

* 🔨 使用`Dict`存储`content_handlers`

* 💄 auto fix by pre-commit hooks

* 🎨 fix

* :arts: 简化函数使用

* 🐛 移除`Theme`的过时参数

* 🗑️ 复用 `self.plain_content`

* 💄 auto fix by pre-commit hooks

*  注册式装饰器写法

* 💄 auto fix by pre-commit hooks

* 🐛 fix

* 💄 auto fix by pre-commit hooks

* :feat: 通用纯文本处理函数

* 💄 auto fix by pre-commit hooks

* :downgrade: 复用`==`处理标题

* 🎨 简化(?)写法

*  测试修复

* ♻️ via ContentSupport extensions

* 🐛 fix test

* 💄 auto fix by pre-commit hooks

* 🐛 for clean text

* 🐛 fix

* 💄 auto fix by pre-commit hooks

* fix: for xml

* 💄 auto fix by pre-commit hooks

* chore: art

* 💄 auto fix by pre-commit hooks

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
Cateon Huo 2024-08-04 18:39:12 +08:00 committed by GitHub
parent 0c1012b0f4
commit 9e5dcb3912
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 363 additions and 46 deletions

View File

@ -1,3 +1,5 @@
import re
import html
from typing import Any
from functools import partial
@ -10,6 +12,7 @@ from nonebot.compat import type_validate_python
from ..post import Post
from ..utils import Site
from ..types import Target, RawPost, Category
from ..post.protocol import HTMLContentSupport
from .platform import NewMessage, StatusChange
@ -58,6 +61,39 @@ class ArknightsSite(Site):
schedule_setting = {"seconds": 30}
class ArknightsPost(Post, HTMLContentSupport):
def _cleantext(self, text: str, old_split="\n", new_split="\n") -> str:
"""清理文本:去掉所有多余的空格和换行"""
lines = text.strip().split(old_split)
cleaned_lines = [line.strip() for line in lines if line != ""]
return new_split.join(cleaned_lines)
async def get_html_content(self) -> str:
return self.content
async def get_plain_content(self) -> str:
content = html.unescape(self.content) # 转义HTML特殊字符
content = re.sub(
r'\<p style="text-align:center;"\>(.*?)\<strong\>(.*?)\<span style=(.*?)\>(.*?)\<\/span\>(.*?)\<\/strong\>(.*?)<\/p\>', # noqa: E501
r"==\4==\n",
content,
flags=re.DOTALL,
) # 去“标题型”p
content = re.sub(
r'\<p style="text-align:(left|right);"?\>(.*?)\<\/p\>',
r"\2\n",
content,
flags=re.DOTALL,
) # 去左右对齐的p
content = re.sub(r"\<p\>(.*?)\</p\>", r"\1\n", content, flags=re.DOTALL) # 去普通p
content = re.sub(r'\<a href="(.*?)" target="_blank">(.*?)\<\/a\>', r"\1", content, flags=re.DOTALL) # 去a
content = re.sub(r"<br/>", "\n", content) # 去br
content = re.sub(r"\<strong\>(.*?)\</strong\>", r"\1", content) # 去strong
content = re.sub(r'<span style="color:(#.*?)">(.*?)</span>', r"\2", content) # 去color
content = re.sub(r'<div class="media-wrap image-wrap">(.*?)</div>', "", content) # 去img
return self._cleantext(content)
class Arknights(NewMessage):
categories = {1: "游戏公告"}
platform_name = "arknights"
@ -108,7 +144,7 @@ class Arknights(NewMessage):
# 只有一张图片
title = title_escape(data.title)
return Post(
return ArknightsPost(
self,
content=data.content,
title=title,
@ -205,7 +241,7 @@ class MonsterSiren(NewMessage):
text = f'{raw_post["title"]}\n{soup.text.strip()}'
return Post(
self,
text,
content=text,
images=imgs,
url=url,
nickname="塞壬唱片新闻",
@ -246,7 +282,7 @@ class TerraHistoricusComic(NewMessage):
url = f'https://terra-historicus.hypergryph.com/comic/{raw_post["comicCid"]}/episode/{raw_post["episodeCid"]}'
return Post(
self,
raw_post["subtitle"],
content=raw_post["subtitle"],
title=f'{raw_post["title"]} - {raw_post["episodeShortTitle"]}',
images=[raw_post["coverUrl"]],
url=url,

View File

@ -465,7 +465,7 @@ class Bilibililive(StatusChange):
target_name = f"{raw_post.uname} {raw_post.area_name}"
return Post(
self,
"",
content="",
title=title,
url=url,
images=list(pic),
@ -550,7 +550,7 @@ class BilibiliBangumi(StatusChange):
title = lastest_episode["share_copy"]
return Post(
self,
content,
content=content,
title=title,
url=url,
images=list(pic),

View File

@ -44,4 +44,4 @@ class FF14(NewMessage):
title = raw_post["Title"]
text = raw_post["Summary"]
url = raw_post["Author"]
return Post(self, text, title=title, url=url, nickname="最终幻想XIV官方公告")
return Post(self, content=text, title=title, url=url, nickname="最终幻想XIV官方公告")

View File

@ -69,7 +69,7 @@ class NcmArtist(NewMessage):
target_name = raw_post["artist"]["name"]
pics = [raw_post["picUrl"]]
url = "https://music.163.com/#/album?id={}".format(raw_post["id"])
return Post(self, text, url=url, images=pics, nickname=target_name)
return Post(self, content=text, url=url, images=pics, nickname=target_name)
class NcmRadio(NewMessage):
@ -130,4 +130,4 @@ class NcmRadio(NewMessage):
target_name = raw_post["radio"]["name"]
pics = [raw_post["coverUrl"]]
url = "https://music.163.com/#/program/{}".format(raw_post["id"])
return Post(self, text, url=url, images=pics, nickname=target_name)
return Post(self, content=text, url=url, images=pics, nickname=target_name)

View File

@ -9,7 +9,7 @@ from bs4 import BeautifulSoup as bs
from ..post import Post
from .platform import NewMessage
from ..types import Target, RawPost
from ..utils import Site, text_similarity
from ..utils import Site, text_fletten, text_similarity
class RssSite(Site):
@ -18,6 +18,23 @@ class RssSite(Site):
schedule_setting = {"seconds": 30}
class RssPost(Post):
async def get_plain_content(self) -> str:
soup = bs(self.content, "html.parser")
for img in soup.find_all("img"):
img.replace_with("[图片]")
for br in soup.find_all("br"):
br.replace_with("\n")
for p in soup.find_all("p"):
p.insert_after("\n")
return text_fletten(soup.get_text())
class Rss(NewMessage):
categories = {}
enable_tag = False
@ -72,9 +89,9 @@ class Rss(NewMessage):
for media in raw_post["media_content"]:
if media.get("medium") == "image" and media.get("url"):
pics.append(media.get("url"))
return Post(
return RssPost(
self,
desc,
content=desc,
title=title,
url=raw_post.link,
images=pics,

View File

@ -183,7 +183,13 @@ class Weibo(NewMessage):
res.raise_for_status()
pics.append(res.content)
detail_url = f"https://weibo.com/{info['user']['id']}/{info['bid']}"
return Post(self, parsed_text, url=detail_url, images=pics, nickname=info["user"]["screen_name"])
return Post(
self,
content=parsed_text,
url=detail_url,
images=pics,
nickname=info["user"]["screen_name"],
)
async def parse(self, raw_post: RawPost) -> Post:
info = raw_post["mblog"]

View File

@ -10,6 +10,7 @@ from nonebot_plugin_saa import MessageSegmentFactory
from ..theme import theme_manager
from .abstract_post import AbstractPost
from ..plugin_config import plugin_config
from .protocol import PlainContentSupport
from ..theme.types import ThemeRenderError, ThemeRenderUnsupportError
if TYPE_CHECKING:
@ -17,7 +18,7 @@ if TYPE_CHECKING:
@dataclass
class Post(AbstractPost):
class Post(AbstractPost, PlainContentSupport):
"""最通用的Post理论上包含所有常用的数据
对于更特殊的需要可以考虑另外实现一个Post
@ -62,6 +63,12 @@ class Post(AbstractPost):
themes_by_priority.append("basic")
return themes_by_priority
async def get_content(self):
return self.content
async def get_plain_content(self):
return self.content
async def generate(self) -> list[MessageSegmentFactory]:
"""生成消息"""
themes = self.get_priority_themes()
@ -95,12 +102,13 @@ class Post(AbstractPost):
来源: <Platform {self.platform.platform_name}>
"""
post_format += "附加信息:\n"
for field in fields(self):
if field.name in ("content", "platform", "repost"):
for cls_field in fields(self):
if cls_field.name in ("content", "platform", "repost"):
continue
value = getattr(self, field.name)
if value is not None:
post_format += f"- {field.name}: {aRepr.repr(value)}\n"
else:
value = getattr(self, cls_field.name)
if value is not None:
post_format += f"- {cls_field.name}: {aRepr.repr(value)}\n"
if self.repost:
post_format += "\n转发:\n"

View File

@ -0,0 +1,16 @@
from typing import Protocol, runtime_checkable
@runtime_checkable
class PlainContentSupport(Protocol):
async def get_plain_content(self) -> str: ...
@runtime_checkable
class HTMLContentSupport(Protocol):
async def get_html_content(self) -> str: ...
@runtime_checkable
class MarkdownContentSupport(Protocol):
async def get_markdown_content(self) -> str: ...

View File

@ -10,7 +10,7 @@ from nonebot_bison.theme.utils import web_embed_image
from nonebot_bison.theme import Theme, ThemeRenderError, ThemeRenderUnsupportError
if TYPE_CHECKING:
from nonebot_bison.post import Post
from nonebot_bison.platform.arknights import ArknightsPost
@dataclass
@ -32,7 +32,7 @@ class ArknightsTheme(Theme):
template_path: Path = Path(__file__).parent / "templates"
template_name: str = "announce.html.jinja"
async def render(self, post: "Post"):
async def render(self, post: "ArknightsPost"):
from nonebot_plugin_htmlrender import template_to_pic
if not post.title:
@ -49,10 +49,9 @@ class ArknightsTheme(Theme):
raise ThemeRenderUnsupportError(
f"图片类型错误, 期望 str | Path | bytes | BytesIO | None, 实际为 {type(banner)}"
)
ark_data = ArkData(
announce_title=text_fletten(post.title),
content=post.content,
content=await post.get_content(),
banner_image_url=banner,
)

View File

@ -24,12 +24,15 @@ class BasicTheme(Theme):
text += f"{post.title}\n\n" if post.title else ""
text += post.content if len(post.content) < 500 else f"{post.content[:500]}..."
content = await post.get_plain_content()
text += content if len(content) < 500 else f"{content[:500]}..."
if rp := post.repost:
text += f"\n--------------\n转发自 {rp.nickname or ''}:\n"
text += f"{rp.title}\n\n" if rp.title else ""
text += rp.content if len(rp.content) < 500 else f"{rp.content[:500]}..."
rp_content = await rp.get_plain_content()
text += rp_content if len(rp_content) < 500 else f"{rp_content[:500]}..."
text += "\n--------------\n"

View File

@ -95,7 +95,7 @@ class CeobeCanteenTheme(Theme):
if post.images:
images = await self.merge_pics(post.images, http_client)
content = CeoboContent(text=post.content)
content = CeoboContent(text=await post.get_content())
retweet: CeoboRetweet | None = None
if post.repost:
@ -106,7 +106,9 @@ class CeobeCanteenTheme(Theme):
images.extend(repost_images)
repost_nickname = f"@{post.repost.nickname}:" if post.repost.nickname else ""
retweet = CeoboRetweet(image=repost_head_pic, content=post.repost.content, author=repost_nickname)
retweet = CeoboRetweet(
image=repost_head_pic, content=await post.repost.get_content(), author=repost_nickname
)
return (
CeobeCard(

View File

@ -5,6 +5,7 @@ from typing import TYPE_CHECKING, Literal
from nonebot_plugin_saa import Text, Image, MessageSegmentFactory
from nonebot_bison.theme import Theme, ThemeRenderError
from nonebot_bison.post.protocol import HTMLContentSupport
from nonebot_bison.utils import pic_merge, is_pics_mergable
if TYPE_CHECKING:
@ -30,16 +31,28 @@ class Ht2iTheme(Theme):
raise ThemeRenderError(f"渲染文本失败: {e}")
async def render(self, post: "Post"):
md_text = ""
md_text += f"## {post.title}\n\n" if post.title else ""
md_text += post.content if len(post.content) < 500 else f"{post.content[:500]}..."
if isinstance(post, HTMLContentSupport):
content = await post.get_html_content()
else:
content = await post.get_content()
md_text += content if len(content) < 500 else f"{content[:500]}..."
md_text += "\n\n"
if rp := post.repost:
md_text += f"> 转发自 {f'**{rp.nickname}**' if rp.nickname else ''}: \n"
md_text += f"> {rp.title} \n" if rp.title else ""
md_text += "> \n> " + rp.content if len(rp.content) < 500 else f"{rp.content[:500]}..." + " \n"
if isinstance(rp, HTMLContentSupport):
rp_content = await rp.get_html_content()
else:
rp_content = await rp.get_content()
md_text += (
"> \n> " + rp_content if len(rp_content) < 500 else f"{rp_content[:500]}..." + " \n" # noqa: E501
) # noqa: E501
md_text += "\n\n"
md_text += f"###### 来源: {post.platform.name} {post.nickname or ''}\n"

View File

@ -38,7 +38,6 @@ class Theme(ABC, BaseModel):
raise ThemeRenderUnsupportError(f"Theme [{self.name}] does not support render {post} by support check")
await self.prepare()
return await self.render(post)
def check_htmlrender_plugin_enable(self):

View File

@ -0,0 +1,18 @@
感谢您对《明日方舟》的关注与支持。《明日方舟》将于08月01日10:00 ~16:00的更新维护中对游戏内【公开招募】进行新增干员。具体新增干员及标签强制刷新注意事项如下
公开招募新增干员:
★★★★★★ 棘刺
★★★★★ 安哲拉
★★★★★ 贾维
★★★★★ 蜜蜡
★★★★ 孑
注意:
◆本次调整更新时,将对未开始进行招募的标签进行强制刷新
◆本次调整更新时,处于已开始招募状态的标签将不受本次刷新影响
◆为避免带来不必要的损失,请您在本次调整前尽快聘用您所招募的干员
◆本次新增干员不加入调整更新时处于已开始招募状态的公开招募中
◆本次强制刷新标签补偿:【招聘许可】*5、【加急许可】*5
◆补偿发放时间08月01日16:00
◆补偿发放对象08月01日16:00更新前所有注册并创建角色的玩家
本次调整时间不排除延迟进行的可能,如若延迟则请关注官网发布的具体调整时间。更多后续内容及最新消息请关注《明日方舟》官网、官方微博及微信公众号。
【明日方舟】运营组
2023年07月23日

View File

@ -0,0 +1,186 @@
==夏日嘉年华活动Part.1==
一、「夏日嘉年华」SideStory「火山旅梦」活动开启
活动时间08月01日 16:00 - 08月29日 03:59
解锁条件通关主线1-10
活动说明:活动期间将开放「火山旅梦」活动,玩家可通过活动关卡作战、汐斯塔风情街、活动任务以及活动商店来获取活动奖励
活动关卡将进行分段式开启:
◆<第一段>
开放时间08月01日 16:00~08月22日 03:59
开放关卡:
【温泉假日】
◆<第二段>
开放时间08月08日 16:00~08月22日 03:59
开放关卡:
【城市寻宝】
◆<第三段>
开放时间08月15日 16:00~08月22日 03:59
开放关卡:
【岩浆冲浪】
【汐斯塔风情街】
活动说明:活动期间,玩家可在“汐斯塔风情街”中完成商品进货和商品售卖获取奖励
主要奖励:峯联贸易物流补给(使用后随机获得指定养成素材)、【“纯白火山”之旅】家具(部分)、模组养成材料、高级养成素材、龙门币、作战记录等
【多利的任务单】
活动说明:活动期间,将开放【多利的任务单】,玩家可通过活动关卡作战等方式,完成【多利的任务单】中相关任务获取奖励
主要奖励:活动干员【★★★★★:苍苔】、【“纯白火山”之旅】家具(部分)、「毛绒绒生物的毛」、模组养成材料等
【“纯白火山”】
开放时间08月01日 16:00 - 08月29日 03:59
兑换说明:活动期间,玩家可通过活动收集「毛绒绒生物的毛」,消耗「毛绒绒生物的毛」兑换【“纯白火山”】内物品
主要奖励:【★★★★★:苍苔】信物、【“纯白火山”之旅】家具(部分)、寻访凭证、模组养成材料、高级养成素材、龙门币、作战记录、家具零件等
◆详细活动玩法介绍可关注后续官方微博相关内容
◆本次活动期间【情报处理室】将无法浏览当期活动剧情,活动结束后该活动剧情将收录至【情报处理室】中
二、「夏日嘉年华」,【云间清醒梦】限定寻访开启
活动时间08月01日 16:00 - 08月15日 03:59
活动说明:活动期间【限定寻访·夏季】-【云间清醒梦】寻访开启,该寻访中以下干员出现率上升
★★★★★★:纯烬艾雅法拉[限定] \ 琳琅诗怀雅占6★出率的70%
★★★★★★:百炼嘉维尔 [限定] \ 假日威龙陈 [限定] 在6★剩余出率【30%】中以5倍权值出率提升
★★★★★青枳占5★出率的50%
注意:
◆【云间清醒梦】为【限定寻访·夏季】系列寻访
◆【限定寻访·夏季】寻访为【限定寻访】
◆新增干员【琳琅诗怀雅】、【青枳】除加入【云间清醒梦】外将在08月03日04:00后加入并常驻【标准寻访】卡池
◆新增干员【纯烬艾雅法拉】为【限定寻访·夏季】限定干员,不会加入任何【标准寻访】
◆限定干员【纯烬艾雅法拉】在本次寻访结束后将不会在2024年07月01日前加入任何【限定寻访】
◆注意:该限定寻访详细说明请参照相关卡池公告说明
三、「夏日嘉年华」,特别登录活动开启
活动时间08月01日 16:00 - 08月15日 03:59
活动说明:玩家在活动期间进行登录即可在活动页面中领取登录奖励:[云过天空寻访凭证]*1
注意:
◆[云过天空寻访凭证] 仅可用于限定寻访【云间清醒梦】中进行一次十连寻访
◆该登录奖励活动期间内每个账号仅可领取一次
◆[云过天空寻访凭证] 过期时间为08月15日03:59
四、「夏日嘉年华」,限定寻访每日赠送
活动时间08月01日 16:00 - 08月15日 03:59
活动说明:活动期间,玩家每日可在【云间清醒梦】限定寻访中免费进行寻访一次
注意:
◆每日赠送寻访刷新时间为每日凌晨04:00
◆未进行使用的“每日赠送寻访”将在每日凌晨04:00 进行刷新,无法进行累计
五、「夏日嘉年华」新干员登场,信赖获取提升
新增干员:
★★★★★★:纯烬艾雅法拉[限定]
★★★★★★:琳琅诗怀雅
★★★★★:青枳
★★★★★:苍苔
注意:
◆新增干员【苍苔】仅在本次「火山旅梦」活动中获取,暂不加入【云间清醒梦】、任何【标准寻访】及【中坚寻访】
◆08月01日 16:00 - 08月22日 03:59期间以上新增干员在活动关卡内信赖获取提升
六、「夏日嘉年华」,“氤氲奇境”签到活动开启
活动时间08月01日 16:00 - 08月15日 03:59
活动说明活动期间内玩家累计活动签到10日可获得褐果专属时装、活动家具、 合成玉等活动奖励
第一日:应急理智浓缩液*1、龙门币 *30000
第二日:合成玉*200、招聘许可*5
第三日技巧概要·卷2*20、家具【“暖身浴”】*1
第四日:芯片助剂*1、家具零件*300
第五日:数据增补条*10、中级作战记录*20
第六日:合成玉*200、赤金*20
第七日:数据增补仪*3、技巧概要·卷3*8
第八日:龙门币 *30000、时装【珊瑚海岸系列-“悠然假日 HDm57”-褐果】*1
第九日:模组数据块*1、加急许可*5
第十日:合成玉*200、中级作战记录*20
注意:本次签到活动中领取的“应急理智浓缩液”到期时间请以游戏内显示为准
七、「夏日嘉年华」,【汐斯塔涂鸦墙】活动开启
活动时间08月01日 16:00 - 08月15日 03:59
活动说明在活动期间玩家每日登录游戏后均可获得2次可选签数。在【汐斯塔涂鸦墙】中消耗可选签数来进行游戏。在选取的心动券中心动券所对应的合成玉数量最多的1张心动券为当日最终奖励玩家需要用尽当日可选签数选取心动券方可领取奖励。
注意事项:
◆活动期间可进行抽取的天数最高为14个游戏日
◆心动券中可出现的合成玉数量为以下几档:
(合成玉*200 、合成玉*300 、合成玉*400 、合成玉*500 、合成玉*600、合成玉*800
◆当日心动券合成玉最终获得奖励不足400时不含400次日将增加一次可选签数。该次数不可累积叠加玩家每日最多可获得的可选签数为3次含每日登录赠送次数
◆当日所有可选签数将在次日凌晨04:00 进行刷新,无法累计
八、「夏日嘉年华」,【珊瑚海岸】系列新装上架
活动时间08月01日 16:00 - 08月29日 03:59
活动说明:活动期间以下干员新增时装将在时装商店上架并进行限时贩售:
◆【珊瑚海岸】系列 -“悠然假日 HD26”- 百炼嘉维尔
◆【珊瑚海岸】系列 -“夏卉 FA394”- 澄闪
◆【珊瑚海岸】系列 -“夏卉 FA062”- 桃金娘
九、「夏日嘉年华」,【珊瑚海岸】系列时装复刻上架
贩售时间08月01日 16:00 - 08月29日 03:59
活动说明:活动期间以下干员复刻时装将在时装商店上架并进行限时贩售
◆【珊瑚海岸】系列 -“夏卉 FA018”- 艾雅法拉
◆【珊瑚海岸】系列 -“夏卉 FA017”- 安洁莉娜
◆【珊瑚海岸】系列 -“静谧午夜 DN04”- 特米米
◆【珊瑚海岸】系列 -“静谧午夜DN06”- 泥岩
◆【珊瑚海岸】系列 -“轻风LB01”- 红
◆【珊瑚海岸】系列 -“轻风LB03”- 铸铁
◆【珊瑚海岸】系列 -“沙滩护卫GT002”- 孑
十、「夏日嘉年华」,【万重山】时装特别复刻
贩售时间08月01日 16:00 - 08月29日 03:59
活动说明:活动期间以下干员时装将在时装商店特别复刻并进行限时贩售
◆【斗争血脉】系列 - “万重山” - 假日威龙陈
十一、「夏日嘉年华」,【罗德岛风尚回顾】限时开启
活动时间08月01日 16:00 - 08月29日 03:59
活动说明:活动期间,【斗争血脉系列 - “乌云” - 山】【斗争血脉系列 - “熔锻铸匠” - 森蚺】【音律联觉系列 - “爆裂菲林” - 煌】等84款干员时装将在时装商店复刻上架并进行限时贩售
十二、「夏日嘉年华」,新增主题家具限时获取
活动说明:活动期间新增活动限定家具【“求索者之梦”科考小屋】系列与【“纯白火山”之旅】系列
【“求索者之梦”科考小屋】系列
获取方式:
◆家具商店售卖时间08月01日 16:00 - 08月29日 03:59
【“纯白火山”之旅】系列
获取方式:
◆「火山旅梦」活动奖励及兑换(部分)
◆家具商店限时售卖(部分)
◆家具商店售卖时间08月01日 16:00 - 08月29日 03:59
十三、「夏日嘉年华」,夏日组合包限时上架
活动说明:「夏日嘉年华」活动开启后,采购中心将限时售卖以下组合包
售卖时间08月01日 16:00 - 08月29日 03:59
【汐斯塔风物组合包】
组合包内容:至纯源石*13、烧结核凝晶*2、双极纳米片*2、聚合凝胶*2、半自然溶剂*6
【假日氤氲组合包】
组合包内容:合成玉*4500、龙门币*100000、芯片助剂*2、技巧概要·卷1*80、模组数据块*2
【温泉观光组合包】
组合包内容:至纯源石*24、十连寻访凭证*2、龙门币*200000、高级作战记录*20、中级作战记录*30、芯片助剂*2、模组数据块*2、切削原液*4
【火山狂欢组合包】
组合包内容:至纯源石*90、十连寻访凭证*1、龙门币*220000、高级作战记录*30、芯片助剂*4、模组数据块*4、数据增补仪*20、数据增补条*60
[诗怀雅的手提箱]
组合包内容:至纯源石*16、龙门币*700000
[特训意向礼包]
组合包内容:至纯源石*11、资深干员特训装置*1
资深干员特训装置使用该特训装置后可选择一名已晋升至精英阶段2的5星干员直接升至精英阶段2等级80。干员升级后不会返还已投入的养成资源。
[高级特训意向礼包]
组合包内容:至纯源石*19、高级资深干员特训装置*1
高级资深干员特训装置使用该特训装置后可选择一名已晋升至精英阶段2的6星干员直接升至精英阶段2等级90。干员升级后不会返还已投入的养成资源。
十四、「夏日嘉年华」,芯片&材料礼包上架
【医疗芯片礼包】
售卖时间08月01日 16:00 - 08月29日 03:59
组合包内容:医疗双芯片*4、医疗芯片*5、技巧概要·卷3*45、技巧概要·卷2*25、技巧概要·卷1*10
【特种芯片礼包】
售卖时间08月01日 16:00 - 08月29日 03:59
组合包内容:特种双芯片*4、特种芯片*5、技巧概要·卷3*45、技巧概要·卷2*25、技巧概要·卷1*10
[每月材料自选组合包]
售卖时间08月01日 16:00起常驻售卖每月限购两次
组合包内容:特级材料提货券*8、高级材料提货券*12
特级材料提货券:使用后可获得任一特级品质精英材料
高级材料提货券:使用后可获得任一高级品质精英材料
注意:
◆当月特级材料提货券与高级材料提货券过期时间为次月10日 03:59
◆特级材料提货券与高级材料提货券的材料可选范围会随后续材料种类更新而扩充
十五、「夏日嘉年华」,【公开招募】干员更新
更新时间08月01日 16:00
更新说明:更新后,以下干员将加入并常驻【公开招募】
★★★★★★:棘刺
★★★★★:安哲拉
★★★★★:贾维
★★★★★:蜜蜡
★★★★:孑
注意:
本次【公开招募】干员更新时,将对未开始进行招募的标签进行强制刷新,强制刷新注意事项及相关补偿请参照官方相关更新公告
==【珊瑚海岸系列 - “悠然假日 HD49” - 锡兰】==
活动时间08月18日 16:00 - 09月01日 03:59
活动说明:活动期间【联合行动】特选干员定向寻访开启,该寻访卡池列表中六星干员与五星干员仅出现以下干员
★★★★★★6★出率2%):鸿雪 / 斥罪 / 玛恩纳 / 老鲤
★★★★★5★出率8%):极光 / 洋灰 / 赤冬 / 絮雨 / 和弦 / 玫拉
注意:本次活动【联合行动】寻访为【标准寻访】
三、「夏日嘉年华」,中坚甄选开启
活动时间08月24日 16:00 - 09月07日 03:59
活动说明活动期间玩家可以在当期甄选的干员范围内自主选择并锁定2位六星干员和3位五星干员作为当期【中坚甄选】寻访出现概率提升的干员。相应地原【高级凭证区】和【通用凭证区】内可兑换的【中坚寻访】中出率上升的干员在【中坚甄选】期间将转变为在当期【中坚甄选】寻访获得概率提升的干员范围内自行选定1位六星干员和1位五星干员进入【高级凭证区】和【通用凭证区】内可供兑换
当期甄选的干员范围:
★★★★★★出率2%):能天使/安洁莉娜/风笛/闪灵/斯卡蒂/陈/赫拉格/煌/星熊/夜莺/阿/艾雅法拉
★★★★★出率8%):赫默/夜魔/天火/真理/槐琥/初雪/梅尔/守林人/白面鸮/临光/普罗旺斯/崖心/芙兰卡/蓝毒/雷蛇/可颂/食铁兽/苇草/布洛卡/灰喉/吽/惊蛰/慑砂/巫恋
注意:【中坚甄选】会继承切换前的常驻【中坚寻访】六星干员获得概率
四、【0011/韵】系列,新装限时上架
活动时间08月22日 04:00 - 09月05日 03:59
活动说明:活动期间以下干员时装将在时装商店上架并进行限时贩售:
◆【0011/韵】系列 -“棠云巧梦”- 子月
更多活动内容请持续关注《明日方舟》游戏内公告及官方公告。
【明日方舟】运营组
2023年07月23日

View File

@ -170,6 +170,7 @@ async def test_fetch_new(
):
from nonebot_bison.post import Post
from nonebot_bison.types import Target, SubUnit
from nonebot_bison.platform.arknights import ArknightsPost
ak_list_router = respx.get("https://ak-webview.hypergryph.com/api/game/bulletinList?target=IOS")
detail_router = respx.get("https://ak-webview.hypergryph.com/api/game/bulletin/5716")
@ -201,7 +202,7 @@ async def test_fetch_new(
res2 = await arknights.fetch_new_post(SubUnit(target, [dummy_user_subinfo]))
assert len(res2[0][1]) == 1
assert detail_router.called
post2: Post = res2[0][1][0]
post2: ArknightsPost = res2[0][1][0]
assert post2.platform.platform_name == "arknights"
assert post2.content
assert post2.title == "2023「夏日嘉年华」限时活动即将开启"
@ -212,7 +213,9 @@ async def test_fetch_new(
assert post2.timestamp
assert "arknights" == post2.get_priority_themes()[0]
# assert(post.pics == ['https://ak-fs.hypergryph.com/announce/images/20210623/e6f49aeb9547a2278678368a43b95b07.jpg'])
post2_plain_content = await post2.get_plain_content()
assert post2_plain_content.strip() == get_file("arknights-plaintext-807.txt").strip()
assert await post2.generate()
terra_list.mock(return_value=Response(200, json=get_json("terra-hist-1.json")))
res3 = await arknights.fetch_new_post(SubUnit(target, [dummy_user_subinfo]))
assert len(res3) == 1
@ -236,8 +239,8 @@ async def test_send_with_render(
monster_siren_list_0,
monster_siren_list_1,
):
from nonebot_bison.post import Post
from nonebot_bison.types import Target, SubUnit
from nonebot_bison.platform.arknights import ArknightsPost
ak_list_router = respx.get("https://ak-webview.hypergryph.com/api/game/bulletinList?target=IOS")
detail_router = respx.get("https://ak-webview.hypergryph.com/api/game/bulletin/8397")
@ -267,9 +270,12 @@ async def test_send_with_render(
res2 = await arknights.fetch_new_post(SubUnit(target, [dummy_user_subinfo]))
assert len(res2[0][1]) == 1
assert detail_router.called
post2: Post = res2[0][1][0]
post2: ArknightsPost = res2[0][1][0]
assert post2.platform.platform_name == "arknights"
assert "《明日方舟》将于08月01日10:00 ~16:00的更新维护中对游戏内【公开招募】进行新增干员。" in post2.content
assert post2.content
post2_plain_content = await post2.get_plain_content()
assert post2_plain_content.strip() == get_file("arknights-plaintext-805.txt").strip()
assert await post2.generate()
assert post2.title == "【公开招募】标签强制刷新通知"
assert post2.nickname == "明日方舟游戏内公告"
assert not post2.images

View File

@ -1,5 +1,6 @@
from time import time
from typing import Any
from inspect import cleandoc
import pytest
from nonebug.app import App
@ -48,7 +49,7 @@ def mock_platform(app: App):
async def parse(self, raw_post: "RawPost") -> "Post":
return Post(
self,
raw_post["text"],
content=raw_post["text"],
url="http://t.tt/" + str(self.get_id(raw_post)),
nickname="MockNick",
)
@ -67,12 +68,15 @@ def mock_platform(app: App):
async def test_display(mock_platform):
from nonebot_bison.post import Post
post = Post(
mock_platform,
post1_content = cleandoc(
"Rebum delenit iusto augue in rebum sanctus diam stet clita voluptua amet tempor sea in.\n"
"Vel ullamcorper dolore clita eos amet tempor velit amet in.\n"
"Vero hendrerit vero diam et lorem blandit ex diam ex amet.\n"
"Voluptua et sed diam erat et diam lorem lorem no euismod sadipscing rebum feugiat est elitr autem.\n",
)
post1 = Post(
mock_platform,
content=post1_content,
title="Ipsum consectetuer voluptua eirmod aliquyam dolore eu volutpat ipsum ipsum eirmod nulla.",
images=[
b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x06\x00\x00\x00\x1f\x15\xc4\x89",
@ -87,8 +91,8 @@ async def test_display(mock_platform):
description="Labore amet ut invidunt dolor consectetuer ipsum sadipscing sed minim diam rebum justo tincidunt.",
)
assert (
str(post)
== rf"""## Post: {id(post):X} ##
str(post1)
== rf"""## Post: {id(post1):X} ##
Rebum delenit iusto augue in rebum sanctus diam stet clita voluptua amet tempor sea in.
Vel ullamcorper dolore clita eos amet tempor velit amet in.
@ -106,12 +110,16 @@ Vero hendrerit vero diam et lorem blandit ex diam ex...
- description: 'Labore amet ut invidunt dolor consectetuer ipsum sadipscing sed minim diam rebum justo tincidunt.'
"""
) # noqa: E501
post2 = Post(
mock_platform,
post2_content = cleandoc(
"Rebum delenit iusto augue in rebum sanctus diam stet clita voluptua amet tempor sea in.\n"
"Vel ullamcorper dolore clita eos amet tempor velit amet in.\n"
"Vero hendrerit vero diam et lorem blandit ex diam ex amet.\n"
"Voluptua et sed diam erat et diam lorem lorem no euismod sadipscing rebum feugiat est elitr autem.\n",
)
post2 = Post(
mock_platform,
content=post2_content,
title="Ipsum consectetuer voluptua eirmod aliquyam dolore eu volutpat ipsum ipsum eirmod nulla.",
images=[
b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x06\x00\x00\x00\x1f\x15\xc4\x89",
@ -124,7 +132,7 @@ Vero hendrerit vero diam et lorem blandit ex diam ex...
),
nickname="Mock2",
description="Labore amet ut invidunt dolor consectetuer ipsum sadipscing sed minim diam rebum justo tincidunt.",
repost=post,
repost=post1,
)
assert (
str(post2)
@ -146,7 +154,7 @@ Vero hendrerit vero diam et lorem blandit ex diam ex...
- description: 'Labore amet ut invidunt dolor consectetuer ipsum sadipscing sed minim diam rebum justo tincidunt.'
转发:
## Post: {id(post):X} ##
## Post: {id(post1):X} ##
Rebum delenit iusto augue in rebum sanctus diam stet clita voluptua amet tempor sea in.
Vel ullamcorper dolore clita eos amet tempor velit amet in.

View File

@ -65,7 +65,7 @@ def mock_platform(app: App):
async def parse(self, raw_post: "RawPost") -> "Post":
return Post(
self,
raw_post["text"],
content=raw_post["text"],
url="http://t.tt/" + str(self.get_id(raw_post)),
nickname="Mock",
)
@ -88,7 +88,7 @@ def mock_post(app: App, mock_platform):
return Post(
m := mock_platform(ProcessContext(DefaultClientManager())),
"text",
content="text",
title="title",
images=["http://t.tt/1.jpg"],
timestamp=1234567890,
@ -98,7 +98,7 @@ def mock_post(app: App, mock_platform):
description="description",
repost=Post(
m,
"repost",
content="repost",
title="repost-title",
images=["http://t.tt/2.jpg"],
timestamp=1234567891,