nonebot-plugin-cs2match 1.0.0__tar.gz

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,106 @@
1
+ Metadata-Version: 2.4
2
+ Name: nonebot-plugin-cs2match
3
+ Version: 1.0.0
4
+ Summary: 实时追踪 Counter-Strike 2 职业赛事,开赛自动提醒、关键赛况与大比分异动推送。
5
+ Keywords: nonebot,bot,counter-strike,cs2
6
+ Author: StarsetNight
7
+ Author-email: StarsetNight <starsetnight@outlook.com>
8
+ License-Expression: MIT
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Requires-Dist: aiohttp>=3.13.5
14
+ Requires-Dist: ayafileio>=1.4.6
15
+ Requires-Dist: nonebot-adapter-onebot>=2.4.6
16
+ Requires-Dist: nonebot-plugin-localstore>=0.7.4
17
+ Requires-Dist: nonebot2>=2.4.4
18
+ Requires-Dist: pydantic>=2.13.4
19
+ Requires-Dist: python-dotenv>=1.2.1
20
+ Requires-Dist: typst>=0.15.0
21
+ Requires-Python: >=3.10, <4.0
22
+ Project-URL: Homepage, https://github.com/StarsetNight/nonebot-plugin-cs2match
23
+ Project-URL: Repository, https://github.com/StarsetNight/nonebot-plugin-cs2match
24
+ Description-Content-Type: text/markdown
25
+
26
+ # nonebot-plugin-cs2match
27
+
28
+ _✨ CS2赛事查询 ✨_
29
+
30
+
31
+ <a href="./LICENSE">
32
+ <img src="https://img.shields.io/github/license/StarsetNight/nonebot-plugin-cs2match.svg" alt="license">
33
+ </a>
34
+ <a href="https://pypi.python.org/pypi/nonebot-plugin-cs2match">
35
+ <img src="https://img.shields.io/pypi/v/nonebot-plugin-cs2match.svg" alt="pypi">
36
+ </a>
37
+ <img src="https://img.shields.io/badge/python-3.10+-blue.svg" alt="python">
38
+
39
+ ## 📖 介绍
40
+
41
+ 实时追踪 Counter-Strike 2 职业赛事,进行开赛自动提醒与大比分异动推送。
42
+
43
+ ## 💿 安装
44
+
45
+ <details open>
46
+ <summary>使用 nb-cli 安装</summary>
47
+ 在 nonebot2 项目的根目录下打开命令行, 输入以下指令即可安装
48
+
49
+ nb plugin install nonebot-plugin-cs2match
50
+
51
+ </details>
52
+
53
+ <details>
54
+ <summary>使用包管理器安装</summary>
55
+ 在 nonebot2 项目的插件目录下, 打开命令行, 根据你使用的包管理器, 输入相应的安装命令
56
+
57
+ <details>
58
+ <summary>pip</summary>
59
+
60
+ pip install nonebot-plugin-cs2match
61
+ </details>
62
+ <details>
63
+ <summary>pdm</summary>
64
+
65
+ pdm add nonebot-plugin-cs2match
66
+ </details>
67
+ <details>
68
+ <summary>poetry</summary>
69
+
70
+ poetry add nonebot-plugin-cs2match
71
+ </details>
72
+ <details>
73
+ <summary>conda</summary>
74
+
75
+ conda install nonebot-plugin-cs2match
76
+ </details>
77
+
78
+ 打开 nonebot2 项目根目录下的 `pyproject.toml` 文件, 在 `[tool.nonebot]` 部分追加写入
79
+
80
+ plugins = ["nonebot_plugin_cs2match"]
81
+
82
+ </details>
83
+
84
+ ## ⚙️ 配置
85
+
86
+ 在插件的`config.py`文件中修改下表中的配置
87
+
88
+ | 配置项 | 必配置 | 默认值 | 说明 |
89
+ |:----------------:|:---:|:---:|:-----------------------------:|
90
+ | pandascore_token | 是 | 无 | PandaScore提供商获取的Token |
91
+ | serie_rules | 否 | 见文件 | 按照优先级给赛事系列排序(同时也是赛事系列白名单) |
92
+ | client_timeout | 否 | 10 | API调用客户端最大超时限制,单位为秒 |
93
+ | cache_ttl | 否 | 60 | api请求临时缓存存活时间及比赛监视检测间隔时间,单位为秒 |
94
+ | cache_max_size | 否 | 64 | 临时渲染缓存队列中最多存储数 |
95
+
96
+ 部分运行时配置(DynamicConfig)仅开放运行时修改,详情查阅[指令表](#指令表)
97
+
98
+ ## 🎉 使用
99
+ ### 指令表
100
+ | 指令 | 权限 | 需要@ | 范围 | 说明 |
101
+ |:---------------------------------:|:------------:|:---:|:--:|:---------------------------------------------------:|
102
+ | `cs2help` / `cs2帮助` | 所有人 | 否 | 任何 | 获取插件命令用法 |
103
+ | `matches [past/running/upcoming]` | 所有人 | 否 | 任何 | 比赛列表获取。`matches`可用`比赛列表`代替。 |
104
+ | `match <id>` | 所有人 | 否 | 任何 | 比赛大比分获取。`match`可用`比分`代替。 |
105
+ | `monitor <id>` | 群管/SUPERUSER | 否 | 群聊 | 监视比赛开始、大比分变动、结束,参数为“cancel”时取消监听。`monitor`可用`监视`代替。 |
106
+ | `cs2whitelist <on/off>` | 群管/SUPERUSER | 否 | 任何 | 设置比赛列表是否仅显示白名单赛事系列。`cs2whitelist`可用`白名单`代替。 |
@@ -0,0 +1,81 @@
1
+ # nonebot-plugin-cs2match
2
+
3
+ _✨ CS2赛事查询 ✨_
4
+
5
+
6
+ <a href="./LICENSE">
7
+ <img src="https://img.shields.io/github/license/StarsetNight/nonebot-plugin-cs2match.svg" alt="license">
8
+ </a>
9
+ <a href="https://pypi.python.org/pypi/nonebot-plugin-cs2match">
10
+ <img src="https://img.shields.io/pypi/v/nonebot-plugin-cs2match.svg" alt="pypi">
11
+ </a>
12
+ <img src="https://img.shields.io/badge/python-3.10+-blue.svg" alt="python">
13
+
14
+ ## 📖 介绍
15
+
16
+ 实时追踪 Counter-Strike 2 职业赛事,进行开赛自动提醒与大比分异动推送。
17
+
18
+ ## 💿 安装
19
+
20
+ <details open>
21
+ <summary>使用 nb-cli 安装</summary>
22
+ 在 nonebot2 项目的根目录下打开命令行, 输入以下指令即可安装
23
+
24
+ nb plugin install nonebot-plugin-cs2match
25
+
26
+ </details>
27
+
28
+ <details>
29
+ <summary>使用包管理器安装</summary>
30
+ 在 nonebot2 项目的插件目录下, 打开命令行, 根据你使用的包管理器, 输入相应的安装命令
31
+
32
+ <details>
33
+ <summary>pip</summary>
34
+
35
+ pip install nonebot-plugin-cs2match
36
+ </details>
37
+ <details>
38
+ <summary>pdm</summary>
39
+
40
+ pdm add nonebot-plugin-cs2match
41
+ </details>
42
+ <details>
43
+ <summary>poetry</summary>
44
+
45
+ poetry add nonebot-plugin-cs2match
46
+ </details>
47
+ <details>
48
+ <summary>conda</summary>
49
+
50
+ conda install nonebot-plugin-cs2match
51
+ </details>
52
+
53
+ 打开 nonebot2 项目根目录下的 `pyproject.toml` 文件, 在 `[tool.nonebot]` 部分追加写入
54
+
55
+ plugins = ["nonebot_plugin_cs2match"]
56
+
57
+ </details>
58
+
59
+ ## ⚙️ 配置
60
+
61
+ 在插件的`config.py`文件中修改下表中的配置
62
+
63
+ | 配置项 | 必配置 | 默认值 | 说明 |
64
+ |:----------------:|:---:|:---:|:-----------------------------:|
65
+ | pandascore_token | 是 | 无 | PandaScore提供商获取的Token |
66
+ | serie_rules | 否 | 见文件 | 按照优先级给赛事系列排序(同时也是赛事系列白名单) |
67
+ | client_timeout | 否 | 10 | API调用客户端最大超时限制,单位为秒 |
68
+ | cache_ttl | 否 | 60 | api请求临时缓存存活时间及比赛监视检测间隔时间,单位为秒 |
69
+ | cache_max_size | 否 | 64 | 临时渲染缓存队列中最多存储数 |
70
+
71
+ 部分运行时配置(DynamicConfig)仅开放运行时修改,详情查阅[指令表](#指令表)
72
+
73
+ ## 🎉 使用
74
+ ### 指令表
75
+ | 指令 | 权限 | 需要@ | 范围 | 说明 |
76
+ |:---------------------------------:|:------------:|:---:|:--:|:---------------------------------------------------:|
77
+ | `cs2help` / `cs2帮助` | 所有人 | 否 | 任何 | 获取插件命令用法 |
78
+ | `matches [past/running/upcoming]` | 所有人 | 否 | 任何 | 比赛列表获取。`matches`可用`比赛列表`代替。 |
79
+ | `match <id>` | 所有人 | 否 | 任何 | 比赛大比分获取。`match`可用`比分`代替。 |
80
+ | `monitor <id>` | 群管/SUPERUSER | 否 | 群聊 | 监视比赛开始、大比分变动、结束,参数为“cancel”时取消监听。`monitor`可用`监视`代替。 |
81
+ | `cs2whitelist <on/off>` | 群管/SUPERUSER | 否 | 任何 | 设置比赛列表是否仅显示白名单赛事系列。`cs2whitelist`可用`白名单`代替。 |
@@ -0,0 +1,39 @@
1
+ [project]
2
+ name = "nonebot-plugin-cs2match"
3
+ version = "1.0.0"
4
+ description = "实时追踪 Counter-Strike 2 职业赛事,开赛自动提醒、关键赛况与大比分异动推送。"
5
+ license = "MIT"
6
+ readme = "README.md"
7
+ keywords = ["nonebot", "bot", "counter-strike", "cs2"]
8
+ classifiers = [
9
+ "Programming Language :: Python :: 3",
10
+ "License :: OSI Approved :: MIT License",
11
+ "Operating System :: OS Independent",
12
+ "Development Status :: 5 - Production/Stable"
13
+ ]
14
+ authors = [
15
+ { name = "StarsetNight", email = "starsetnight@outlook.com" }
16
+ ]
17
+ requires-python = ">=3.10,<4.0"
18
+ dependencies = [
19
+ "aiohttp>=3.13.5",
20
+ "ayafileio>=1.4.6",
21
+ "nonebot-adapter-onebot>=2.4.6",
22
+ "nonebot-plugin-localstore>=0.7.4",
23
+ "nonebot2>=2.4.4",
24
+ "pydantic>=2.13.4",
25
+ "python-dotenv>=1.2.1",
26
+ "typst>=0.15.0",
27
+ ]
28
+
29
+ [project.urls]
30
+ Homepage = "https://github.com/StarsetNight/nonebot-plugin-cs2match"
31
+ Repository = "https://github.com/StarsetNight/nonebot-plugin-cs2match"
32
+
33
+ [[tool.uv.index]]
34
+ url = "https://mirrors.huaweicloud.com/repository/pypi/simple/"
35
+ default = true
36
+
37
+ [build-system]
38
+ requires = ["uv_build>=0.9.27,<0.10.0"]
39
+ build-backend = "uv_build"
@@ -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
+