@@ -1,5 +1,16 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## beta 1.2.0.1019 - 2026-10-19 17:05:47
|
||||||
|
|
||||||
|
- 优化 Taffy 群聊语气:回复更短、更像群友,减少 AI 式自我解释,并保留主动插话、冷门问答和更新状态提示。
|
||||||
|
- Add a chat-specific reply style layer that removes common AI-like padding,
|
||||||
|
keeps direct unknown-subject clarification, and splits long casual replies
|
||||||
|
into shorter lines.
|
||||||
|
- Apply the style rule to direct chat and cloud-agent replies while leaving
|
||||||
|
structured local command output unchanged.
|
||||||
|
- This 1.2 release grants each existing account both 200 Mora and
|
||||||
|
1 primogem.
|
||||||
|
|
||||||
## beta 1.1.2.1019 - 2026-10-19 16:38:10
|
## beta 1.1.2.1019 - 2026-10-19 16:38:10
|
||||||
|
|
||||||
- 新增低频主动插话、随机冷门知识问答和更新生命周期提示:Taffy 会偶尔自然冒泡,抛出限时答题奖励摩拉,并在停机、上线和更新就绪时主动说明状态。
|
- 新增低频主动插话、随机冷门知识问答和更新生命周期提示:Taffy 会偶尔自然冒泡,抛出限时答题奖励摩拉,并在停机、上线和更新就绪时主动说明状态。
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ JWeBot 是运行于 Linux 的单群微信自动回复系统。它通过本地 OC
|
|||||||
观察选定的微信窗口,将消息写入持久化 Edge 账本,调用经过审核的命令插件,
|
观察选定的微信窗口,将消息写入持久化 Edge 账本,调用经过审核的命令插件,
|
||||||
并通过带视觉确认的 GUI 发送链路回复。
|
并通过带视觉确认的 GUI 发送链路回复。
|
||||||
|
|
||||||
当前版本:`beta 1.1.2.1019`,更新时间:2026-10-19 16:38:10。
|
当前版本:`beta 1.2.0.1019`,更新时间:2026-10-19 17:05:47。
|
||||||
|
|
||||||
1.0 Beta 收口了当前稳定功能:本地命令插件、人物与上下文索引、
|
1.0 Beta 收口了当前稳定功能:本地命令插件、人物与上下文索引、
|
||||||
Steam 状态与时长查询、虚拟摩拉游戏、多人彩票、21 点、CS Major
|
Steam 状态与时长查询、虚拟摩拉游戏、多人彩票、21 点、CS Major
|
||||||
@@ -269,10 +269,10 @@ visually confirmed GUI bridge.
|
|||||||
Current version:
|
Current version:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
beta 1.1.2.1019
|
beta 1.2.0.1019
|
||||||
```
|
```
|
||||||
|
|
||||||
Updated: 2026-10-19 16:38:10.
|
Updated: 2026-10-19 17:05:47.
|
||||||
|
|
||||||
The 1.0 Beta baseline consolidates the stable local command set, person and
|
The 1.0 Beta baseline consolidates the stable local command set, person and
|
||||||
context indexes, Steam presence/playtime queries, virtual coin games,
|
context indexes, Steam presence/playtime queries, virtual coin games,
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ COMPENSATION_AMOUNT = 200
|
|||||||
PRIMOGEM_COMPENSATION_RELEASES = {
|
PRIMOGEM_COMPENSATION_RELEASES = {
|
||||||
"beta 1.0.5.1016": 1,
|
"beta 1.0.5.1016": 1,
|
||||||
"beta 1.1.0.1016": 1,
|
"beta 1.1.0.1016": 1,
|
||||||
|
"beta 1.2.0.1019": 1,
|
||||||
}
|
}
|
||||||
MAX_BALANCE = 1_000_000_000
|
MAX_BALANCE = 1_000_000_000
|
||||||
MAX_SUMMARY_CHARS = 300
|
MAX_SUMMARY_CHARS = 300
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "jwebot-config"
|
name = "jwebot-config"
|
||||||
version = "1.1.2b1+1019"
|
version = "1.2.0b1+1019"
|
||||||
description = "Profile-based configuration compiler for JWeBot"
|
description = "Profile-based configuration compiler for JWeBot"
|
||||||
requires-python = ">=3.9"
|
requires-python = ">=3.9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
|||||||
@@ -36,4 +36,4 @@ __all__ = [
|
|||||||
"write_default_profile",
|
"write_default_profile",
|
||||||
]
|
]
|
||||||
|
|
||||||
__version__ = "1.1.2b1+1019"
|
__version__ = "1.2.0b1+1019"
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "jwebot-extension-host"
|
name = "jwebot-extension-host"
|
||||||
version = "1.1.2b1+1019"
|
version = "1.2.0b1+1019"
|
||||||
description = "Single-release, single-invocation process host for JWeBot extensions"
|
description = "Single-release, single-invocation process host for JWeBot extensions"
|
||||||
requires-python = ">=3.9"
|
requires-python = ">=3.9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"jwebot-sdk==1.1.2b1+1019",
|
"jwebot-sdk==1.2.0b1+1019",
|
||||||
"jwebot-kernel==1.1.2b1+1019",
|
"jwebot-kernel==1.2.0b1+1019",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
|
|||||||
@@ -16,4 +16,4 @@ __all__ = [
|
|||||||
"process_request",
|
"process_request",
|
||||||
]
|
]
|
||||||
|
|
||||||
__version__ = "1.1.2b1+1019"
|
__version__ = "1.2.0b1+1019"
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "jwebot-kernel"
|
name = "jwebot-kernel"
|
||||||
version = "1.1.2b1+1019"
|
version = "1.2.0b1+1019"
|
||||||
description = "Stable routing and extension authority for JWeBot"
|
description = "Stable routing and extension authority for JWeBot"
|
||||||
requires-python = ">=3.9"
|
requires-python = ">=3.9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"jwebot-config==1.1.2b1+1019",
|
"jwebot-config==1.2.0b1+1019",
|
||||||
"jwebot-sdk==1.1.2b1+1019",
|
"jwebot-sdk==1.2.0b1+1019",
|
||||||
"jwebot-official-extensions==1.1.2b1+1019",
|
"jwebot-official-extensions==1.2.0b1+1019",
|
||||||
"tomli>=2; python_version < '3.11'",
|
"tomli>=2; python_version < '3.11'",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -107,4 +107,4 @@ __all__ = [
|
|||||||
"verify_installed_release_signature",
|
"verify_installed_release_signature",
|
||||||
]
|
]
|
||||||
|
|
||||||
__version__ = "1.1.2b1+1019"
|
__version__ = "1.2.0b1+1019"
|
||||||
|
|||||||
@@ -200,6 +200,38 @@ BOT_IDENTITY_NOTE = (
|
|||||||
"你是 JWeBot。显示名和别名只用于当前群的会话上下文,"
|
"你是 JWeBot。显示名和别名只用于当前群的会话上下文,"
|
||||||
"不代表身份认证或权限。"
|
"不代表身份认证或权限。"
|
||||||
)
|
)
|
||||||
|
TAFFY_CHAT_STYLE_NOTE = (
|
||||||
|
"语气硬规则:像群友一样短句回复,优先 1 到 3 句;"
|
||||||
|
"能一句自然反问就只反问。遇到不认识的人、梗或对象,"
|
||||||
|
"直接问“X 是啥?”或“谁啊?”,不要铺垫。"
|
||||||
|
"不要说自己只是 AI、机器人、语言模型,不要写能力边界、免责声明、"
|
||||||
|
"自我介绍或“不能凭空给自己打分”这类自证。"
|
||||||
|
"长回复要拆成短句,别一句话塞满。"
|
||||||
|
)
|
||||||
|
CHAT_REPLY_REDUNDANT_LINE = re.compile(
|
||||||
|
r"(?:"
|
||||||
|
r"作为(?:一个)?(?:AI|人工智能|语言模型)|"
|
||||||
|
r"(?:AI|人工智能|语言模型)[,,。;;\s]*(?:无法|不能|没有办法)|"
|
||||||
|
r"我(?:只是|就是)(?:一个|个)?(?:会唠嗑|聊天|机器人|AI|人工智能)|"
|
||||||
|
r"反正我就是|会唠嗑|聊聊天|记记小账|别的功能想都别想|"
|
||||||
|
r"不能凭空|凭空.{0,12}打(?:满)?分|"
|
||||||
|
r"(?:无法|不能|没有办法).{0,10}(?:比较|评价|打分)|"
|
||||||
|
r"不要主动声明无法认证身份|不代表身份认证"
|
||||||
|
r")",
|
||||||
|
re.IGNORECASE,
|
||||||
|
)
|
||||||
|
UNKNOWN_CLARIFICATION_PATTERNS = (
|
||||||
|
re.compile(
|
||||||
|
r"(?P<subject>[\w\u4e00-\u9fff]{1,24}?)(?:是|算是|叫)?"
|
||||||
|
r"(?P<question>啥|什么|谁)"
|
||||||
|
r"[^。!?!?;;\n]{0,20}(?:不清楚|不知道|不认识|没听过)"
|
||||||
|
),
|
||||||
|
re.compile(
|
||||||
|
r"(?:不清楚|不知道|不认识|没听过)"
|
||||||
|
r"[^。!?!?;;\n]{0,12}(?P<subject>[\w\u4e00-\u9fff]{1,24}?)"
|
||||||
|
r"(?:是|算是|叫)?(?P<question>啥|什么|谁)"
|
||||||
|
),
|
||||||
|
)
|
||||||
TRIGGER = re.compile(
|
TRIGGER = re.compile(
|
||||||
rf"^\s*[@@]\s*{re.escape(BOT_MENTION)}"
|
rf"^\s*[@@]\s*{re.escape(BOT_MENTION)}"
|
||||||
r"(?:\s*[::,,])?\s*(\S.*)$",
|
r"(?:\s*[::,,])?\s*(\S.*)$",
|
||||||
@@ -734,6 +766,63 @@ def clean_reply(value: object) -> str:
|
|||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
def unknown_subject_clarification(value: str) -> str:
|
||||||
|
text = normalize_text(value)
|
||||||
|
for pattern in UNKNOWN_CLARIFICATION_PATTERNS:
|
||||||
|
match = pattern.search(text)
|
||||||
|
if match is None:
|
||||||
|
continue
|
||||||
|
subject = match.group("subject").strip("_")
|
||||||
|
if not subject or subject in {"这", "这个", "那", "那个", "他", "她", "它"}:
|
||||||
|
continue
|
||||||
|
if match.group("question") == "谁":
|
||||||
|
return f"{subject}是谁"
|
||||||
|
return f"{subject}是啥"
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def split_chat_reply_line(line: str) -> list[str]:
|
||||||
|
separators = re.findall(r"[。!?!?;;,,]+", line)
|
||||||
|
if len(line) <= 34 and len(separators) < 2:
|
||||||
|
return [line]
|
||||||
|
fragments = [
|
||||||
|
normalize_text(fragment)
|
||||||
|
for fragment in re.split(r"[。!?!?;;]+|[,,]+", line)
|
||||||
|
if normalize_text(fragment)
|
||||||
|
]
|
||||||
|
if 1 < len(fragments) <= 6 and all(len(fragment) <= 46 for fragment in fragments):
|
||||||
|
return fragments
|
||||||
|
return [line]
|
||||||
|
|
||||||
|
|
||||||
|
def style_chat_reply_text(value: object) -> str:
|
||||||
|
raw_lines = [
|
||||||
|
normalize_text(line)
|
||||||
|
for line in str(value or "").splitlines()
|
||||||
|
if normalize_text(line)
|
||||||
|
]
|
||||||
|
if not raw_lines:
|
||||||
|
return ""
|
||||||
|
clarification = unknown_subject_clarification(" ".join(raw_lines))
|
||||||
|
if clarification:
|
||||||
|
return clarification
|
||||||
|
filtered = [
|
||||||
|
line
|
||||||
|
for line in raw_lines
|
||||||
|
if not CHAT_REPLY_REDUNDANT_LINE.search(line)
|
||||||
|
] or raw_lines[:1]
|
||||||
|
styled: list[str] = []
|
||||||
|
for line in filtered:
|
||||||
|
styled.extend(split_chat_reply_line(line))
|
||||||
|
if len(styled) >= 4:
|
||||||
|
break
|
||||||
|
return "\n".join(styled[:4])
|
||||||
|
|
||||||
|
|
||||||
|
def clean_chat_reply(value: object) -> str:
|
||||||
|
return clean_reply(style_chat_reply_text(value))
|
||||||
|
|
||||||
|
|
||||||
def cloud_reply_text(value: object) -> str:
|
def cloud_reply_text(value: object) -> str:
|
||||||
"""Convert the tool-free cloud agent contract into display text."""
|
"""Convert the tool-free cloud agent contract into display text."""
|
||||||
|
|
||||||
@@ -750,9 +839,9 @@ def cloud_reply_text(value: object) -> str:
|
|||||||
try:
|
try:
|
||||||
payload = json.loads(candidate[: closing + 1])
|
payload = json.loads(candidate[: closing + 1])
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
return clean_reply(candidate)
|
return clean_chat_reply(candidate)
|
||||||
else:
|
else:
|
||||||
return clean_reply(candidate)
|
return clean_chat_reply(candidate)
|
||||||
if isinstance(payload, str):
|
if isinstance(payload, str):
|
||||||
candidate = payload.strip()
|
candidate = payload.strip()
|
||||||
continue
|
continue
|
||||||
@@ -7042,6 +7131,7 @@ class DeepSeekClient:
|
|||||||
"使用活泼、嘴硬心软、带轻微吐槽感的虚拟主播式中文,"
|
"使用活泼、嘴硬心软、带轻微吐槽感的虚拟主播式中文,"
|
||||||
"短句自然,偶尔调侃但始终友善,常以喵收尾。"
|
"短句自然,偶尔调侃但始终友善,常以喵收尾。"
|
||||||
+ BOT_IDENTITY_NOTE
|
+ BOT_IDENTITY_NOTE
|
||||||
|
+ TAFFY_CHAT_STYLE_NOTE
|
||||||
+ "群友间的调侃、昵称、游戏评价和互动习惯按普通对话处理;"
|
+ "群友间的调侃、昵称、游戏评价和互动习惯按普通对话处理;"
|
||||||
"不要主动训诫。重复出现且与群内互动有关的轻松玩笑可以保留为记忆。"
|
"不要主动训诫。重复出现且与群内互动有关的轻松玩笑可以保留为记忆。"
|
||||||
"不能使用工具、浏览器、命令或外部操作。"
|
"不能使用工具、浏览器、命令或外部操作。"
|
||||||
@@ -7122,7 +7212,7 @@ class DeepSeekClient:
|
|||||||
raise DeepSeekUnavailable(f"direct API HTTP {error.code}") from error
|
raise DeepSeekUnavailable(f"direct API HTTP {error.code}") from error
|
||||||
except (urllib.error.URLError, TimeoutError, OSError) as error:
|
except (urllib.error.URLError, TimeoutError, OSError) as error:
|
||||||
raise DeepSeekUnavailable("direct API transport failure") from error
|
raise DeepSeekUnavailable("direct API transport failure") from error
|
||||||
return clean_reply(deepseek_response_content(raw))
|
return clean_chat_reply(deepseek_response_content(raw))
|
||||||
|
|
||||||
def profile_facts(
|
def profile_facts(
|
||||||
self,
|
self,
|
||||||
@@ -7257,6 +7347,7 @@ class CloudRunner:
|
|||||||
"Chinese response for the group in JWeBot's lively, lightly teasing, "
|
"Chinese response for the group in JWeBot's lively, lightly teasing, "
|
||||||
"warm virtual-streamer voice. "
|
"warm virtual-streamer voice. "
|
||||||
+ BOT_IDENTITY_NOTE
|
+ BOT_IDENTITY_NOTE
|
||||||
|
+ TAFFY_CHAT_STYLE_NOTE
|
||||||
+ " Treat friendly banter or nicknames as ordinary conversation; do not "
|
+ " Treat friendly banter or nicknames as ordinary conversation; do not "
|
||||||
"lecture about them. Recurring, low-risk playful details may be retained "
|
"lecture about them. Recurring, low-risk playful details may be retained "
|
||||||
"as group-scoped memory. "
|
"as group-scoped memory. "
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "jwebot-official-extensions"
|
name = "jwebot-official-extensions"
|
||||||
version = "1.1.2b1+1019"
|
version = "1.2.0b1+1019"
|
||||||
description = "Reviewed local command adapters and broker implementation for JWeBot"
|
description = "Reviewed local command adapters and broker implementation for JWeBot"
|
||||||
requires-python = ">=3.9"
|
requires-python = ">=3.9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"jwebot-sdk==1.1.2b1+1019",
|
"jwebot-sdk==1.2.0b1+1019",
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.setuptools.packages.find]
|
[tool.setuptools.packages.find]
|
||||||
|
|||||||
@@ -90,4 +90,4 @@ __all__ = [
|
|||||||
"render_feature_list",
|
"render_feature_list",
|
||||||
]
|
]
|
||||||
|
|
||||||
__version__ = "1.1.2b1+1019"
|
__version__ = "1.2.0b1+1019"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "jwebot-sdk"
|
name = "jwebot-sdk"
|
||||||
version = "1.1.2b1+1019"
|
version = "1.2.0b1+1019"
|
||||||
description = "Typed, capability-limited extension API for JWeBot plugins"
|
description = "Typed, capability-limited extension API for JWeBot plugins"
|
||||||
requires-python = ">=3.9"
|
requires-python = ">=3.9"
|
||||||
dependencies = []
|
dependencies = []
|
||||||
|
|||||||
@@ -38,4 +38,4 @@ __all__ = [
|
|||||||
"SingleGroupContext",
|
"SingleGroupContext",
|
||||||
]
|
]
|
||||||
|
|
||||||
__version__ = "1.1.2b1+1019"
|
__version__ = "1.2.0b1+1019"
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
[tool.jwebot]
|
[tool.jwebot]
|
||||||
release = "beta 1.1.2.1019"
|
release = "beta 1.2.0.1019"
|
||||||
production_deployments_enabled_by_default = false
|
production_deployments_enabled_by_default = false
|
||||||
|
|
||||||
[tool.jwebot.workspace]
|
[tool.jwebot.workspace]
|
||||||
|
|||||||
@@ -219,6 +219,27 @@ class RoutingTests(unittest.TestCase):
|
|||||||
self.assertEqual(len(MODULE.clean_reply("好" * 900)), 900)
|
self.assertEqual(len(MODULE.clean_reply("好" * 900)), 900)
|
||||||
self.assertEqual(MODULE.FAST_ACKNOWLEDGEMENT, "喵......")
|
self.assertEqual(MODULE.FAST_ACKNOWLEDGEMENT, "喵......")
|
||||||
|
|
||||||
|
def test_chat_reply_style_removes_ai_like_padding(self):
|
||||||
|
self.assertEqual(
|
||||||
|
MODULE.clean_chat_reply(
|
||||||
|
"李神是啥我都不清楚,怎么比,总不能凭空给自己打满分吧\n"
|
||||||
|
"反正我就是个会唠嗑的,聊聊天、记记小账还行,"
|
||||||
|
"别的功能想都别想喵"
|
||||||
|
),
|
||||||
|
"李神是啥喵",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
MODULE.clean_chat_reply("作为AI我无法比较。\n李神是谁?"),
|
||||||
|
"李神是谁喵",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
MODULE.clean_chat_reply(
|
||||||
|
"这波像是脑子知道要撤,但是手完全没同意,"
|
||||||
|
"而且还顺手把队友也拖下水了。"
|
||||||
|
),
|
||||||
|
"这波像是脑子知道要撤\n但是手完全没同意\n而且还顺手把队友也拖下水了喵",
|
||||||
|
)
|
||||||
|
|
||||||
def test_profile_memory_rejects_bare_versions_but_keeps_audio_context(self):
|
def test_profile_memory_rejects_bare_versions_but_keeps_audio_context(self):
|
||||||
self.assertFalse(MODULE.profile_fact_is_safe("5.1"))
|
self.assertFalse(MODULE.profile_fact_is_safe("5.1"))
|
||||||
self.assertTrue(MODULE.profile_fact_is_safe("最近在研究 5.1 音响配置"))
|
self.assertTrue(MODULE.profile_fact_is_safe("最近在研究 5.1 音响配置"))
|
||||||
@@ -925,9 +946,9 @@ class LedgerAndApplicationTests(unittest.TestCase):
|
|||||||
self.assertEqual(version["route"], "local")
|
self.assertEqual(version["route"], "local")
|
||||||
self.assertIn("JWeBot 版本", version["text"])
|
self.assertIn("JWeBot 版本", version["text"])
|
||||||
self.assertIn("kernel", version["text"])
|
self.assertIn("kernel", version["text"])
|
||||||
self.assertIn("最新版本:beta 1.1.2.1019", version["text"])
|
self.assertIn("最新版本:beta 1.2.0.1019", version["text"])
|
||||||
self.assertIn("更新时间:2026-10-19 16:38:10", version["text"])
|
self.assertIn("更新时间:2026-10-19 17:05:47", version["text"])
|
||||||
self.assertIn("更新摘要:新增低频主动插话、随机冷门知识问答", version["text"])
|
self.assertIn("更新摘要:优化 Taffy 群聊语气", version["text"])
|
||||||
self.assertEqual(debug["route"], "local")
|
self.assertEqual(debug["route"], "local")
|
||||||
self.assertIn("调试埋点", debug["text"])
|
self.assertIn("调试埋点", debug["text"])
|
||||||
self.assertEqual(balance["route"], "local")
|
self.assertEqual(balance["route"], "local")
|
||||||
@@ -2175,6 +2196,10 @@ class DirectClientTests(unittest.TestCase):
|
|||||||
self.assertNotIn("temperature", payload)
|
self.assertNotIn("temperature", payload)
|
||||||
self.assertEqual(request.get_header("Authorization"), "Bearer test-key")
|
self.assertEqual(request.get_header("Authorization"), "Bearer test-key")
|
||||||
self.assertIn(MODULE.BOT_IDENTITY_NOTE, payload["messages"][0]["content"])
|
self.assertIn(MODULE.BOT_IDENTITY_NOTE, payload["messages"][0]["content"])
|
||||||
|
self.assertIn(
|
||||||
|
MODULE.TAFFY_CHAT_STYLE_NOTE,
|
||||||
|
payload["messages"][0]["content"],
|
||||||
|
)
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
any(
|
any(
|
||||||
message["content"].startswith("CURRENT_GROUP_SENDER_LABEL:")
|
message["content"].startswith("CURRENT_GROUP_SENDER_LABEL:")
|
||||||
@@ -2763,6 +2788,23 @@ class CloudRunnerTests(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(MODULE.cloud_reply_text(nested), "嵌套内容喵")
|
self.assertEqual(MODULE.cloud_reply_text(nested), "嵌套内容喵")
|
||||||
|
|
||||||
|
def test_cloud_protocol_applies_chat_reply_style(self):
|
||||||
|
self.assertEqual(
|
||||||
|
MODULE.cloud_reply_text(
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"route": "reply",
|
||||||
|
"text": (
|
||||||
|
"李神是啥我都不清楚,怎么比,"
|
||||||
|
"总不能凭空给自己打满分吧"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
ensure_ascii=False,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"李神是啥喵",
|
||||||
|
)
|
||||||
|
|
||||||
def test_cloud_protocol_ignores_a_suffix_after_the_json_object(self):
|
def test_cloud_protocol_ignores_a_suffix_after_the_json_object(self):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
MODULE.cloud_reply_text(
|
MODULE.cloud_reply_text(
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ SPEC.loader.exec_module(MODULE)
|
|||||||
|
|
||||||
|
|
||||||
class ReleaseUpdateTests(unittest.TestCase):
|
class ReleaseUpdateTests(unittest.TestCase):
|
||||||
release = "beta 1.1.2.1019"
|
release = "beta 1.2.0.1019"
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.temporary = tempfile.TemporaryDirectory()
|
self.temporary = tempfile.TemporaryDirectory()
|
||||||
@@ -111,12 +111,15 @@ class ReleaseUpdateTests(unittest.TestCase):
|
|||||||
self.assertEqual(summary, "第一行更新摘要, 同一条摘要可以换行。")
|
self.assertEqual(summary, "第一行更新摘要, 同一条摘要可以换行。")
|
||||||
announcement = MODULE.build_announcement(self.release, summary)
|
announcement = MODULE.build_announcement(self.release, summary)
|
||||||
self.assertIn(f"{self.release} 更新现已完成", announcement)
|
self.assertIn(f"{self.release} 更新现已完成", announcement)
|
||||||
self.assertIn("更新补偿:摩拉 x200", announcement)
|
self.assertIn("更新补偿:摩拉 x200,原石 x1", announcement)
|
||||||
self.assertNotIn("原石 x1", announcement)
|
|
||||||
self.assertNotIn("第二条", announcement)
|
self.assertNotIn("第二条", announcement)
|
||||||
|
|
||||||
def test_policy_release_announces_primogem(self):
|
def test_policy_release_announces_primogem(self):
|
||||||
for release in ("beta 1.0.5.1016", "beta 1.1.0.1016"):
|
for release in (
|
||||||
|
"beta 1.0.5.1016",
|
||||||
|
"beta 1.1.0.1016",
|
||||||
|
"beta 1.2.0.1019",
|
||||||
|
):
|
||||||
self.assertEqual(MODULE.primogem_compensation_amount(release), 1)
|
self.assertEqual(MODULE.primogem_compensation_amount(release), 1)
|
||||||
announcement = MODULE.build_announcement(release, "原石系统上线。")
|
announcement = MODULE.build_announcement(release, "原石系统上线。")
|
||||||
self.assertIn("更新补偿:摩拉 x200,原石 x1", announcement)
|
self.assertIn("更新补偿:摩拉 x200,原石 x1", announcement)
|
||||||
@@ -184,7 +187,7 @@ class ReleaseUpdateTests(unittest.TestCase):
|
|||||||
self.assertIn("更新摩拉账户并修复重复回复问题", delivered[0][2])
|
self.assertIn("更新摩拉账户并修复重复回复问题", delivered[0][2])
|
||||||
self.assertEqual([state["balance"] for state in self.account_states()], [1200, 250])
|
self.assertEqual([state["balance"] for state in self.account_states()], [1200, 250])
|
||||||
for state in self.account_states():
|
for state in self.account_states():
|
||||||
self.assertEqual(state["primogems"], 0)
|
self.assertEqual(state["primogems"], 1)
|
||||||
matches = [
|
matches = [
|
||||||
event
|
event
|
||||||
for event in state["coin_events"]
|
for event in state["coin_events"]
|
||||||
@@ -198,7 +201,9 @@ class ReleaseUpdateTests(unittest.TestCase):
|
|||||||
for event in state["primogem_events"]
|
for event in state["primogem_events"]
|
||||||
if event["reason"] == f"{self.release} 版本更新补偿"
|
if event["reason"] == f"{self.release} 版本更新补偿"
|
||||||
]
|
]
|
||||||
self.assertEqual(primogem_matches, [])
|
self.assertEqual(len(primogem_matches), 1)
|
||||||
|
self.assertEqual(primogem_matches[0]["delta"], 1)
|
||||||
|
self.assertEqual(primogem_matches[0]["created_at"], applied_at.timestamp())
|
||||||
self.assertEqual(self.announcement_marker()["state"], "delivered")
|
self.assertEqual(self.announcement_marker()["state"], "delivered")
|
||||||
self.assertEqual(len(list(self.backups.glob("*.sqlite3"))), 1)
|
self.assertEqual(len(list(self.backups.glob("*.sqlite3"))), 1)
|
||||||
|
|
||||||
@@ -216,7 +221,7 @@ class ReleaseUpdateTests(unittest.TestCase):
|
|||||||
deliverer=uncertain,
|
deliverer=uncertain,
|
||||||
)
|
)
|
||||||
self.assertEqual([state["balance"] for state in self.account_states()], [1200, 250])
|
self.assertEqual([state["balance"] for state in self.account_states()], [1200, 250])
|
||||||
self.assertEqual([state["primogems"] for state in self.account_states()], [0, 0])
|
self.assertEqual([state["primogems"] for state in self.account_states()], [1, 1])
|
||||||
self.assertEqual(self.announcement_marker()["state"], "uncertain")
|
self.assertEqual(self.announcement_marker()["state"], "uncertain")
|
||||||
|
|
||||||
called = []
|
called = []
|
||||||
@@ -247,7 +252,7 @@ class ReleaseUpdateTests(unittest.TestCase):
|
|||||||
self.assertEqual(result["compensation"], "already_applied")
|
self.assertEqual(result["compensation"], "already_applied")
|
||||||
self.assertEqual(len(called), 1)
|
self.assertEqual(len(called), 1)
|
||||||
self.assertEqual([state["balance"] for state in self.account_states()], [1200, 250])
|
self.assertEqual([state["balance"] for state in self.account_states()], [1200, 250])
|
||||||
self.assertEqual([state["primogems"] for state in self.account_states()], [0, 0])
|
self.assertEqual([state["primogems"] for state in self.account_states()], [1, 1])
|
||||||
self.assertEqual(self.announcement_marker()["state"], "delivered")
|
self.assertEqual(self.announcement_marker()["state"], "delivered")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user