diff --git a/backend/src/module/notification/providers/bark.py b/backend/src/module/notification/providers/bark.py index dd4e9a12..d2fe5a48 100644 --- a/backend/src/module/notification/providers/bark.py +++ b/backend/src/module/notification/providers/bark.py @@ -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" diff --git a/backend/src/module/notification/providers/wecom.py b/backend/src/module/notification/providers/wecom.py index b36875ea..b995b00b 100644 --- a/backend/src/module/notification/providers/wecom.py +++ b/backend/src/module/notification/providers/wecom.py @@ -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: diff --git a/backend/src/module/update/v4.py b/backend/src/module/update/v4.py index fa312741..1cda4003 100644 --- a/backend/src/module/update/v4.py +++ b/backend/src/module/update/v4.py @@ -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] diff --git a/backend/src/test/test_update_v4.py b/backend/src/test/test_update_v4.py index ae1c8679..950e7267 100644 --- a/backend/src/test/test_update_v4.py +++ b/backend/src/test/test_update_v4.py @@ -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()