nonebot-plugin-cs2match 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,197 @@
1
+ # Copyright (c) 2026 StarsetNight, XuanRikka
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ from typing import cast, Any
5
+
6
+ from nonebot import logger, get_driver, get_plugin_config, on_command, require
7
+ from nonebot.adapters.onebot.v11 import Message, GROUP_ADMIN, GROUP_OWNER, Bot, GroupMessageEvent
8
+ from nonebot.params import CommandArg
9
+ from nonebot.permission import SUPERUSER
10
+ from nonebot.plugin import PluginMetadata
11
+
12
+ from .config import Config
13
+ from .tools import PandaScoreClient, MonitorClient, MatchParser, typst_render
14
+ from .typst_template import help_text
15
+ from .rule import is_enabled
16
+ from .dynamic_config import DynamicConfigSystem, PriorityMode
17
+
18
+ require("nonebot_plugin_localstore")
19
+ from nonebot_plugin_localstore import get_plugin_data_file
20
+
21
+ driver = get_driver()
22
+ global_config = driver.config
23
+ config = get_plugin_config(Config) # 取自config.py中的静态配置
24
+
25
+ panda_client: PandaScoreClient | None = None
26
+ dynamic_config: DynamicConfigSystem | None = None # 取自插件内编写的DynamicConfigSystem
27
+ monitor_client: MonitorClient | None = None
28
+
29
+ # 注册插件
30
+ __plugin_meta__ = PluginMetadata(
31
+ name="CS2赛事助手",
32
+ description="实时追踪 Counter-Strike 2 职业赛事,开赛自动提醒、关键赛况与大比分异动推送",
33
+ usage=help_text,
34
+ config=Config,
35
+ extra={}
36
+ )
37
+
38
+ @driver.on_startup
39
+ async def on_startup_check():
40
+ global panda_client, dynamic_config
41
+ if config.pandascore_token is None:
42
+ logger.warning("pandascore_token未设置,CS2数据查询功能将不可用或受限。")
43
+ logger.info("请前往PandaScore官网注册获取Token,并在插件目录config.py中配置:"
44
+ "pandascore_token: str | None = <你的Token>")
45
+ return
46
+ panda_client = PandaScoreClient(config.pandascore_token)
47
+
48
+ config_path = get_plugin_data_file("config.json")
49
+ if not config_path.exists():
50
+ dynamic_config = await DynamicConfigSystem.new(config_path)
51
+ else:
52
+ dynamic_config = await DynamicConfigSystem.from_path(config_path)
53
+
54
+
55
+ get_help = on_command("cs2help", aliases={"cs2帮助"}, priority=10, block=True)
56
+ list_matches = on_command("matches", aliases={"比赛列表"}, rule=is_enabled, priority=10, block=True)
57
+ check_match = on_command("match", aliases={"比分"}, rule=is_enabled, priority=10, block=True)
58
+ monitor_match = on_command("monitor", aliases={"监视"}, rule=is_enabled,
59
+ permission=SUPERUSER | GROUP_OWNER | GROUP_ADMIN, priority=10, block=True)
60
+ whitelist_config = on_command("cs2whitelist", aliases={"白名单"}, rule=is_enabled,
61
+ permission=SUPERUSER | GROUP_OWNER | GROUP_ADMIN, priority=10, block=True)
62
+
63
+
64
+ @get_help.handle()
65
+ async def on_get_help():
66
+ await get_help.finish(await typst_render(help_text, "help"))
67
+
68
+
69
+ @list_matches.handle()
70
+ async def on_list_matches(args: Message = CommandArg()):
71
+ arg = args.extract_plain_text().strip()
72
+
73
+ client = cast(PandaScoreClient, panda_client)
74
+
75
+ func_map = {
76
+ "past": client.list_past_matches,
77
+ "running": client.list_running_matches,
78
+ "upcoming": client.list_upcoming_matches,
79
+ }
80
+
81
+ await list_matches.send("正在查询比赛列表,请稍候...")
82
+
83
+ func = func_map.get(arg, None)
84
+
85
+ if func is None:
86
+ func = client.list_matches
87
+ cache_key = "list_matches"
88
+ else:
89
+ cache_key = arg
90
+
91
+ try:
92
+ matches = await func()
93
+ except Exception as e:
94
+ await list_matches.finish(f"由于API调度故障,请求失败:{e}")
95
+ matches = [] # 哄类型检查器
96
+ _config = cast(DynamicConfigSystem, dynamic_config)
97
+
98
+ await list_matches.finish(
99
+ await typst_render(
100
+ MatchParser.prerender_list(matches, _config.config.priority_mode),
101
+ cache_key
102
+ )
103
+ )
104
+
105
+
106
+ @check_match.handle()
107
+ async def on_check_match(args: Message = CommandArg()):
108
+ slug = args.extract_plain_text().strip()
109
+
110
+ if not slug:
111
+ await check_match.finish("用法:match <slug>\n"
112
+ "slug可在查询比赛列表的单个比赛左下角中找到。")
113
+
114
+ client = cast(PandaScoreClient, panda_client)
115
+
116
+ await check_match.send(f"正在查询比赛({slug})\n请稍候...")
117
+
118
+ try:
119
+ matches = await client.list_running_matches() + await client.list_upcoming_matches()
120
+ except Exception as e:
121
+ await list_matches.finish(f"由于API调度故障,请求失败:{e}")
122
+ matches = [] # 哄类型检查器
123
+
124
+ match = next(
125
+ (m for m in matches if m.get("slug") == slug),
126
+ None,
127
+ )
128
+
129
+ if match is None:
130
+ await check_match.finish(f"未找到比赛:{slug}")
131
+
132
+ match = cast(dict[str, Any], match)
133
+
134
+ await check_match.finish(
135
+ await typst_render(
136
+ MatchParser.prerender_match(match),
137
+ "get_match",
138
+ )
139
+ )
140
+
141
+
142
+ @monitor_match.handle()
143
+ async def on_monitor_match(bot: Bot, event: GroupMessageEvent, args: Message = CommandArg()):
144
+ global monitor_client
145
+
146
+ slug = args.extract_plain_text().strip().lower()
147
+
148
+ if not slug:
149
+ await monitor_match.finish(
150
+ "用法:monitor <slug>\n"
151
+ "取消监视:monitor cancel"
152
+ )
153
+
154
+ client = cast(PandaScoreClient, panda_client) # 获取API的HTTP客户端
155
+
156
+ if monitor_client is None:
157
+ monitor_client = MonitorClient(client=client, bot=bot) #
158
+ assert monitor_client is not None
159
+
160
+ # 取消当前群比赛监视
161
+ if slug == "cancel":
162
+ monitor_client.remove_monitor(group_id=event.group_id)
163
+ await monitor_match.finish("已取消本群比赛监视。")
164
+
165
+ try:
166
+ matches = await client.list_matches()
167
+ except Exception as e:
168
+ await list_matches.finish(f"由于API调度故障,请求失败:{e}")
169
+ matches = [] # 哄类型检查器
170
+
171
+ match = next(
172
+ (m for m in matches if m.get("slug", "").lower() == slug),
173
+ None,
174
+ )
175
+
176
+ if match is None:
177
+ await monitor_match.finish(f"未找到比赛:{slug}")
178
+ match = cast(dict[str, Any], match)
179
+
180
+ if match.get("status", "unknown") in {"finished", "canceled"}:
181
+ await monitor_match.finish(f"比赛已结束/取消,不可监视:{slug}")
182
+
183
+ monitor_client.add_monitor(slug, event.group_id)
184
+ await monitor_match.finish(f"已开始监视比赛:{match.get('name', slug)}")
185
+
186
+
187
+ @whitelist_config.handle()
188
+ async def on_whitelist_config(args: Message = CommandArg()):
189
+ arg = args.extract_plain_text().strip().lower()
190
+ if arg not in ["on", "off"]:
191
+ await whitelist_config.finish("命令用法:whitelist <on/off>")
192
+ _config = cast(DynamicConfigSystem, dynamic_config)
193
+ _config.config.priority_mode = PriorityMode.WhitelistOnly if arg == "on" else PriorityMode.WhitelistFirst
194
+ await _config.save()
195
+ await whitelist_config.finish(f"仅白名单赛事模式被设置为{'开启' if arg == 'on' else '关闭'}。")
196
+
197
+
@@ -0,0 +1,21 @@
1
+ # Copyright (c) 2023 StarsetNight
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ from pydantic import BaseModel
5
+
6
+
7
+ class Config(BaseModel):
8
+ """Plugin Config Here"""
9
+ pandascore_token: str | None = None
10
+ serie_rules: list[tuple[str, int, list[str]]] = [
11
+ ("major", 100, ["major"]),
12
+ ("blast", 90, ["blast"]),
13
+ ("iem", 80, ["iem"]),
14
+ ("esl", 80, ["esl"]),
15
+ ("pgl", 70, ["pgl"]),
16
+ ("cac", 60, ["cac"]),
17
+ ("pnl", 50, ["pnl"]),
18
+ ] # 赛事系列,优先级,匹配赛事名称(小写)
19
+ client_timeout: int = 10
20
+ cache_ttl: float = 60
21
+ cache_max_size: int = 64
@@ -0,0 +1,39 @@
1
+ # Copyright (c) 2026 StarsetNight, XuanRikka
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ from __future__ import annotations
5
+ from enum import Enum
6
+ from pathlib import Path
7
+
8
+ import ayafileio
9
+ from pydantic import BaseModel
10
+
11
+ class PriorityMode(str, Enum):
12
+ WhitelistOnly = "whitelist-only"
13
+ WhitelistFirst = "whitelist-first"
14
+
15
+ class DynamicConfig(BaseModel):
16
+ priority_mode: PriorityMode = PriorityMode.WhitelistOnly
17
+
18
+ class DynamicConfigSystem:
19
+ def __init__(self, config: DynamicConfig, path: Path):
20
+ self.config = config
21
+ self.path = path
22
+
23
+ @classmethod
24
+ async def from_path(cls, path: Path) -> DynamicConfigSystem:
25
+ async with ayafileio.open(path, "r", encoding="utf-8") as f:
26
+ data = await f.readall()
27
+ config = DynamicConfig.model_validate_json(data)
28
+ return DynamicConfigSystem(config, path)
29
+
30
+ @classmethod
31
+ async def new(cls, path: Path) -> DynamicConfigSystem:
32
+ config = DynamicConfig()
33
+ async with ayafileio.open(path, "w", encoding="utf-8") as f:
34
+ await f.write(config.model_dump_json())
35
+ return DynamicConfigSystem(config, path)
36
+
37
+ async def save(self):
38
+ async with ayafileio.open(self.path, "w", encoding="utf-8") as f:
39
+ await f.write(self.config.model_dump_json())
@@ -0,0 +1,16 @@
1
+ # Copyright (c) 2026 StarsetNight, XuanRikka
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ import nonebot
5
+
6
+ from .config import Config
7
+
8
+ config = nonebot.get_plugin_config(Config)
9
+
10
+
11
+ async def is_enabled():
12
+ """
13
+ :return: 插件是否被启用
14
+ """
15
+ return config.pandascore_token is not None
16
+