refactor(4.0-P7): 移除 Bark token 与 WeCom chat_id 旧字段别名,v3 迁移器改写为新字段

渠道不再读取旧字段。迁移器把 Bark 的 token 移到 device_key,WeCom 的 chat_id 移到 webhook_url,新字段已有值时丢弃旧值。
This commit is contained in:
Estrella Pan
2026-10-08 14:23:12 +02:00
parent ab9801b63a
commit 2d524fd13d
4 changed files with 83 additions and 4 deletions

View File

@@ -19,8 +19,7 @@ class BarkProvider(NotificationProvider):
def __init__(self, config: "ProviderConfig"):
super().__init__(config)
# Support both legacy token field and new device_key field
self.device_key = config.device_key or config.token
self.device_key = config.device_key
server_url = config.server_url or self.DEFAULT_SERVER
self.notification_url = f"{server_url.rstrip('/')}/push"

View File

@@ -17,8 +17,7 @@ class WecomProvider(NotificationProvider):
def __init__(self, config: "ProviderConfig"):
super().__init__(config)
# Support both webhook_url and legacy chat_id field
self.notification_url = config.webhook_url or config.chat_id
self.notification_url = config.webhook_url
self.token = config.token
async def send(self, notification: Notification) -> bool:

View File

@@ -7,6 +7,8 @@
- ``bangumi_manage.rename_method`` → ``plugins.slots.rename_strategy``
(废弃的 ``normal`` 改为 ``none``)
- ``bangumi_manage.revision_conflict_policy`` → ``plugins.slots.conflict_policy``
- 通知渠道的旧字段别名:Bark 的 ``token`` → ``device_key``,WeCom 的 ``chat_id``
→ ``webhook_url``(渠道实现不再读旧字段)
在 ``Settings`` 读写配置文件之前运行。改写前把原文件备份为 ``<文件名>.v3.bak``;
失败时从备份恢复并抛出 :class:`ConfigMigrationError`,拒绝启动。
@@ -32,6 +34,13 @@ _SLOT_FIELDS = {
}
# 渠道类型 → (旧字段, 新字段);3.x 的渠道实现以 ``新字段 or 旧字段`` 读取
_PROVIDER_ALIASES = {
"bark": ("token", "device_key"),
"wecom": ("chat_id", "webhook_url"),
}
class ConfigMigrationError(RuntimeError):
"""配置迁移失败;配置文件已从备份恢复。"""
@@ -50,6 +59,26 @@ def migrate_v3_dict(config: dict[str, Any]) -> list[str]:
下载器字段合并到已有的 ``default`` 实例上,没有时新建;未出现的字段保持原样。
"""
return _migrate_plugin_fields(config) + _migrate_provider_aliases(config)
def _migrate_provider_aliases(config: dict[str, Any]) -> list[str]:
moved = []
providers = _section(config, "notification").get("providers")
for i, provider in enumerate(providers if isinstance(providers, list) else []):
if not isinstance(provider, dict):
continue
old, new = _PROVIDER_ALIASES.get(
str(provider.get("type", "")).lower(), ("", "")
)
if old in provider:
legacy = provider.pop(old)
provider[new] = provider.get(new) or legacy
moved.append(f"notification.providers[{i}].{old}")
return moved
def _migrate_plugin_fields(config: dict[str, Any]) -> list[str]:
downloader = _section(config, "downloader")
bangumi_manage = _section(config, "bangumi_manage")
moved = [f"bangumi_manage.{key}" for key in _SLOT_FIELDS if key in bangumi_manage]

View File

@@ -145,3 +145,55 @@ def test_migrate_v3_config_existing_backup_kept_new_backup_beside_it(v3_config):
assert first.read_text() == "original 3.3 config"
second = v3_config.with_name("config.json.v3.bak.1")
assert second.read_bytes() == FIXTURE.read_bytes()
@pytest.mark.parametrize(
"provider, expected",
[
# Bark 旧版把 device key 放在 token
({"type": "bark", "token": "k"}, {"type": "bark", "device_key": "k"}),
(
{"type": "bark", "token": "old", "device_key": "new"},
{"type": "bark", "device_key": "new"},
),
# WeCom 旧版把 webhook 地址放在 chat_id
(
{"type": "wecom", "chat_id": "https://hook", "token": "key"},
{"type": "wecom", "webhook_url": "https://hook", "token": "key"},
),
(
{"type": "WeCom", "chat_id": "old", "webhook_url": "new"},
{"type": "WeCom", "webhook_url": "new"},
),
],
)
def test_migrate_v3_dict_moves_legacy_notification_aliases(provider, expected):
config: dict[str, Any] = {"notification": {"providers": [provider]}}
moved = migrate_v3_dict(config)
assert config["notification"]["providers"] == [expected]
assert len(moved) == 1 and moved[0].startswith("notification.providers[0].")
# 只动通知渠道:没有顺带创建 plugins 段
assert "plugins" not in config
def test_migrate_v3_dict_keeps_other_providers_token_and_chat_id():
telegram = {"type": "telegram", "token": "t", "chat_id": "1"}
config: dict[str, Any] = {"notification": {"providers": [dict(telegram)]}}
assert migrate_v3_dict(config) == []
assert config["notification"]["providers"] == [telegram]
def test_migrate_v3_config_rewrites_file_with_legacy_bark_token(tmp_path):
path = tmp_path / "config.json"
path.write_text(
json.dumps({"notification": {"providers": [{"type": "bark", "token": "k"}]}})
)
assert migrate_v3_config(path) is True
saved = json.loads(path.read_text())
assert saved["notification"]["providers"] == [{"type": "bark", "device_key": "k"}]
assert path.with_name("config.json.v3.bak").exists()