zhenxun_bot/zhenxun/services/scheduler/targeting.py
Rumio 70bde00757
feat(core): 增强定时任务与群组标签管理,重构调度核心 (#2068)
*  feat(core): 更新群组信息、Markdown 样式与 Pydantic 兼容层

- 【group】添加更新所有群组信息指令,并同步群组控制台数据
- 【markdown】支持合并 Markdown 的 CSS 来源
- 【pydantic-compat】提供 model_validate 兼容函数

*  feat(core): 增强定时任务与群组标签管理,重构调度核心

 新功能

* **标签 (tags)**: 引入群组标签服务。
    * 支持静态标签和动态标签 (基于 Alconna 规则自动匹配群信息)。
    * 支持黑名单模式及 `@all` 特殊标签。
    * 提供 `tag_manage` 超级用户插件 (list, create, edit, delete 等)。
    * 群成员变动时自动失效动态标签缓存。
* **调度 (scheduler)**: 增强定时任务。
    * 重构 `ScheduledJob` 模型,支持 `TAG`, `ALL_GROUPS` 等多种目标类型。
    * 新增任务别名 (`name`)、创建者、权限、来源等字段。
    * 支持一次性任务 (`schedule_once`) 和 Alconna 命令行参数 (`--params-cli`)。
    * 新增执行选项 (`jitter`, `spread`) 和并发策略 (`ALLOW`, `SKIP`, `QUEUE`)。
    * 支持批量获取任务状态。

♻️ 重构优化

* **调度器核心**:
    * 拆分 `service.py` 为 `manager.py` (API) 和 `types.py` (模型)。
    * 合并 `adapter.py` / `job.py` 至 `engine.py` (统一调度引擎)。
    * 引入 `targeting.py` 模块管理任务目标解析。
* **调度器插件 (scheduler_admin)**:
    * 迁移命令参数校验逻辑至 `ArparmaBehavior`。
    * 引入 `dependencies.py` 和 `data_source.py` 解耦业务逻辑与依赖注入。
    * 适配新的任务目标类型展示。

* 🐛 fix(tag): 修复黑名单标签解析逻辑并优化标签详情展示

*  feat(scheduler): 为多目标定时任务添加固定间隔串行执行选项

*  feat(schedulerAdmin): 允许定时任务删除、暂停、恢复命令支持多ID操作

* 🚨 auto fix by pre-commit hooks

---------

Co-authored-by: webjoin111 <455457521@qq.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-11-03 10:53:40 +08:00

165 lines
5.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
目标解析与选择器 (Targeting)
提供用于解析任务目标和批量操作目标的 ScheduleTargeter 类。
"""
from collections.abc import Callable, Coroutine
from typing import Any
from nonebot.adapters import Bot
from zhenxun.services.tags import tag_manager
__all__ = [
"ScheduleTargeter",
"_resolve_all_groups",
"_resolve_global_or_user",
"_resolve_group",
"_resolve_tag",
"_resolve_user",
]
async def _resolve_group(target_identifier: str, bot: Bot) -> list[str | None]:
return [target_identifier]
async def _resolve_tag(target_identifier: str, bot: Bot) -> list[str | None]:
result = await tag_manager.resolve_tag_to_group_ids(target_identifier)
return result # type: ignore
async def _resolve_user(target_identifier: str, bot: Bot) -> list[str | None]:
return [target_identifier]
async def _resolve_all_groups(target_identifier: str, bot: Bot) -> list[str | None]:
result = await tag_manager.resolve_tag_to_group_ids("@all", bot=bot)
return result
async def _resolve_global_or_user(target_identifier: str, bot: Bot) -> list[str | None]:
return [None]
class ScheduleTargeter:
"""
一个用于构建和执行定时任务批量操作的目标选择器。
"""
def __init__(self, manager: Any, **filters: Any):
"""
初始化目标选择器
参数:
manager: SchedulerManager 实例。
**filters: 过滤条件支持plugin_name、group_id、bot_id等字段。
"""
self._manager = manager
self._filters = {k: v for k, v in filters.items() if v is not None}
async def _get_schedules(self):
"""
根据过滤器获取任务
返回:
list[ScheduledJob]: 符合过滤条件的任务列表。
"""
from .repository import ScheduleRepository
query = ScheduleRepository.filter(**self._filters)
return await query.all()
def _generate_target_description(self) -> str:
"""
根据过滤条件生成友好的目标描述
返回:
str: 描述目标的友好字符串。
"""
if "id" in self._filters:
return f"任务 ID {self._filters['id']}"
parts = []
if "target_descriptor" in self._filters:
descriptor = self._filters["target_descriptor"]
if descriptor == self._manager.ALL_GROUPS:
parts.append("所有群组中")
elif descriptor.startswith("tag:"):
parts.append(f"标签 '{descriptor[4:]}'")
else:
parts.append(f"{descriptor}")
if "plugin_name" in self._filters:
parts.append(f"插件 '{self._filters['plugin_name']}'")
if not parts:
return "所有"
return "".join(parts)
async def _apply_operation(
self,
operation_func: Callable[[int], Coroutine[Any, Any, tuple[bool, str]]],
operation_name: str,
) -> tuple[int, str]:
"""通用的操作应用模板"""
schedules = await self._get_schedules()
if not schedules:
target_desc = self._generate_target_description()
return 0, f"没有找到{target_desc}可供{operation_name}的任务。"
success_count = 0
for schedule in schedules:
success, _ = await operation_func(schedule.id)
if success:
success_count += 1
target_desc = self._generate_target_description()
return (
success_count,
f"成功{operation_name}{target_desc} {success_count} 个任务。",
)
async def pause(self) -> tuple[int, str]:
"""
暂停匹配的定时任务
返回:
tuple[int, str]: (成功暂停的任务数量, 操作结果消息)。
"""
return await self._apply_operation(self._manager.pause_schedule, "暂停")
async def resume(self) -> tuple[int, str]:
"""
恢复匹配的定时任务
返回:
tuple[int, str]: (成功恢复的任务数量, 操作结果消息)。
"""
return await self._apply_operation(self._manager.resume_schedule, "恢复")
async def remove(self) -> tuple[int, str]:
"""
移除匹配的定时任务
返回:
tuple[int, str]: (成功移除的任务数量, 操作结果消息)。
"""
from .engine import APSchedulerAdapter
from .repository import ScheduleRepository
schedules = await self._get_schedules()
if not schedules:
target_desc = self._generate_target_description()
return 0, f"没有找到{target_desc}可供移除的任务。"
for schedule in schedules:
APSchedulerAdapter.remove_job(schedule.id)
query = ScheduleRepository.filter(**self._filters)
count = await query.delete()
target_desc = self._generate_target_description()
return count, f"成功移除了{target_desc} {count} 个任务。"