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.
- nonebot_plugin_cs2match/__init__.py +197 -0
- nonebot_plugin_cs2match/config.py +21 -0
- nonebot_plugin_cs2match/dynamic_config.py +39 -0
- nonebot_plugin_cs2match/rule.py +16 -0
- nonebot_plugin_cs2match/tools.py +522 -0
- nonebot_plugin_cs2match/typst_template.py +556 -0
- nonebot_plugin_cs2match-1.0.0.dist-info/METADATA +106 -0
- nonebot_plugin_cs2match-1.0.0.dist-info/RECORD +9 -0
- nonebot_plugin_cs2match-1.0.0.dist-info/WHEEL +4 -0
|
@@ -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
|
+
|