dragon-contest 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.
- dragon_contest-1.0.0/PKG-INFO +12 -0
- dragon_contest-1.0.0/README.md +0 -0
- dragon_contest-1.0.0/pyproject.toml +38 -0
- dragon_contest-1.0.0/src/dragon_contest/__init__.py +58 -0
- dragon_contest-1.0.0/src/dragon_contest/commands/__init__.py +1 -0
- dragon_contest-1.0.0/src/dragon_contest/commands/admin.py +241 -0
- dragon_contest-1.0.0/src/dragon_contest/commands/command_registry.py +174 -0
- dragon_contest-1.0.0/src/dragon_contest/commands/signup.py +120 -0
- dragon_contest-1.0.0/src/dragon_contest/config.py +7 -0
- dragon_contest-1.0.0/src/dragon_contest/contest_runner.py +108 -0
- dragon_contest-1.0.0/src/dragon_contest/models.py +46 -0
- dragon_contest-1.0.0/src/dragon_contest/openai_client.py +44 -0
- dragon_contest-1.0.0/src/dragon_contest/utils.py +184 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: dragon-contest
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Requires-Dist: ruff>=0.14.0
|
|
6
|
+
Requires-Dist: nonebot2>=2.0.0b10,<3.0.0
|
|
7
|
+
Requires-Dist: nonebot-plugin-alconna>=0.4.3,<0.6.0
|
|
8
|
+
Requires-Dist: nonebot-plugin-orm>=0.6.0,<0.7.0
|
|
9
|
+
Requires-Dist: nonebot-plugin-apscheduler>=0.3.5,<0.6.0
|
|
10
|
+
Requires-Python: >=3.10, <4.0
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
File without changes
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "dragon-contest"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
description = "Add your description here"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10, <4.0"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"ruff>=0.14.0",
|
|
9
|
+
"nonebot2>=2.0.0b10,<3.0.0",
|
|
10
|
+
"nonebot-plugin-alconna>=0.4.3,<0.6.0",
|
|
11
|
+
"nonebot-plugin-orm>=0.6.0,<0.7.0",
|
|
12
|
+
"nonebot-plugin-apscheduler>=0.3.5,<0.6.0",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
[build-system]
|
|
17
|
+
requires = ["uv_build>=0.8.17,<0.9.0"]
|
|
18
|
+
build-backend = "uv_build"
|
|
19
|
+
|
|
20
|
+
[dependency-groups]
|
|
21
|
+
dev = [
|
|
22
|
+
"ruff>=0.14.0",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[tool.ruff]
|
|
26
|
+
line-length = 88
|
|
27
|
+
target-version = "py310"
|
|
28
|
+
|
|
29
|
+
[tool.ruff.lint]
|
|
30
|
+
select = ["E", "W", "F", "UP", "C", "T", "PYI", "PT", "Q"]
|
|
31
|
+
ignore = ["E402", "C901"]
|
|
32
|
+
fixable = ["ALL"]
|
|
33
|
+
|
|
34
|
+
[tool.ruff.format]
|
|
35
|
+
quote-style = "double"
|
|
36
|
+
indent-style = "space"
|
|
37
|
+
skip-magic-trailing-comma = false
|
|
38
|
+
line-ending = "auto"
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from nonebot import require
|
|
2
|
+
from nonebot.log import logger
|
|
3
|
+
from nonebot.plugin import PluginMetadata, inherit_supported_adapters
|
|
4
|
+
|
|
5
|
+
require("nonebot_plugin_orm")
|
|
6
|
+
require("nonebot_plugin_alconna")
|
|
7
|
+
require("nonebot_plugin_apscheduler")
|
|
8
|
+
|
|
9
|
+
from .commands.command_registry import (
|
|
10
|
+
plugin_config,
|
|
11
|
+
dragon_contest_command,
|
|
12
|
+
join_dragon_contest_command,
|
|
13
|
+
cancel_dragon_contest_command,
|
|
14
|
+
revise_dragon_name_command,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
__plugin_meta__ = PluginMetadata(
|
|
19
|
+
name="dragon-contest",
|
|
20
|
+
description="龙龙大赛插件",
|
|
21
|
+
usage="/加入龙龙大赛 <龙龙名称>",
|
|
22
|
+
type="application",
|
|
23
|
+
homepage="https://github.com/lyqgzbl/dragon-contest",
|
|
24
|
+
supported_adapters=inherit_supported_adapters("nonebot_plugin_alconna"),
|
|
25
|
+
extra={
|
|
26
|
+
"author": "lyqgzbl <admin@lyqgzbl.com",
|
|
27
|
+
"version": "1.0.0",
|
|
28
|
+
},
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
if not plugin_config.dc_github_token:
|
|
33
|
+
logger.opt(colors=True).warning(
|
|
34
|
+
"<yellow>缺失必要配置项 'dc_github_token',已禁用龙龙大赛插件</yellow>"
|
|
35
|
+
)
|
|
36
|
+
openai_handler = None
|
|
37
|
+
else:
|
|
38
|
+
from .openai_client import OpenAIHandler
|
|
39
|
+
openai_handler = OpenAIHandler(
|
|
40
|
+
api_key=plugin_config.dc_github_token,
|
|
41
|
+
endpoint="https://models.github.ai/inference",
|
|
42
|
+
model_name="openai/gpt-4.1-mini",
|
|
43
|
+
temperature=0.7,
|
|
44
|
+
top_p=0.9,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
from .commands import admin as _admin # noqa: F401
|
|
49
|
+
from .commands import signup as _signup # noqa: F401
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
__all__ = [
|
|
53
|
+
"dragon_contest_command",
|
|
54
|
+
"join_dragon_contest_command",
|
|
55
|
+
"cancel_dragon_contest_command",
|
|
56
|
+
"revise_dragon_name_command",
|
|
57
|
+
"openai_handler",
|
|
58
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Command handlers for dragon-contest plugin."""
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
|
|
3
|
+
from nonebot.log import logger
|
|
4
|
+
from sqlalchemy import delete, select, func
|
|
5
|
+
from sqlalchemy.exc import IntegrityError
|
|
6
|
+
from nonebot_plugin_orm import async_scoped_session
|
|
7
|
+
from nonebot_plugin_alconna import MsgTarget, Target
|
|
8
|
+
|
|
9
|
+
from ..models import ContestStatus, DragonContest, DragonContestPlayer
|
|
10
|
+
from ..utils import (
|
|
11
|
+
get_current_contest,
|
|
12
|
+
get_or_create_config,
|
|
13
|
+
register_contest_start_job,
|
|
14
|
+
)
|
|
15
|
+
from ..commands.command_registry import (
|
|
16
|
+
dragon_contest_command,
|
|
17
|
+
add_dragon_contest_player_command,
|
|
18
|
+
remove_dragon_contest_player_command,
|
|
19
|
+
list_dragon_contest_player_command,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dragon_contest_command.assign("create")
|
|
24
|
+
async def handle_create_contest(
|
|
25
|
+
time: str,
|
|
26
|
+
limit: int | None,
|
|
27
|
+
sess: async_scoped_session,
|
|
28
|
+
target: MsgTarget,
|
|
29
|
+
):
|
|
30
|
+
try:
|
|
31
|
+
dt = datetime.strptime(time, "%Y/%m/%d %H:%M")
|
|
32
|
+
except ValueError:
|
|
33
|
+
await dragon_contest_command.finish("时间格式错误,请使用YYYY/MM/DD HH:MM格式")
|
|
34
|
+
return
|
|
35
|
+
start_ts = int(dt.timestamp())
|
|
36
|
+
config = await get_or_create_config(sess)
|
|
37
|
+
contest_limit = limit if limit is not None else config.default_limit
|
|
38
|
+
if contest_limit < 1 or contest_limit > 32:
|
|
39
|
+
await dragon_contest_command.finish("参加人数限制必须在 1~32 之间")
|
|
40
|
+
contest = DragonContest(
|
|
41
|
+
start_ts=start_ts,
|
|
42
|
+
limit=contest_limit,
|
|
43
|
+
target=Target.dump(target),
|
|
44
|
+
)
|
|
45
|
+
if contest.start_ts <= int(datetime.now().timestamp()):
|
|
46
|
+
await dragon_contest_command.finish("比赛时间必须在未来")
|
|
47
|
+
sess.add(contest)
|
|
48
|
+
try:
|
|
49
|
+
await sess.commit()
|
|
50
|
+
register_contest_start_job(contest)
|
|
51
|
+
except IntegrityError:
|
|
52
|
+
await sess.rollback()
|
|
53
|
+
await dragon_contest_command.finish("创建比赛失败,请检查参数")
|
|
54
|
+
except Exception as e:
|
|
55
|
+
await sess.rollback()
|
|
56
|
+
logger.exception(e)
|
|
57
|
+
await dragon_contest_command.finish("创建比赛失败,请查看日志")
|
|
58
|
+
await dragon_contest_command.finish(
|
|
59
|
+
"已创建龙龙大赛\n"
|
|
60
|
+
f"时间:{time}\n"
|
|
61
|
+
f"人数限制:{contest_limit}\n"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dragon_contest_command.assign("delete")
|
|
66
|
+
async def handle_delete_contest(id: int, sess: async_scoped_session):
|
|
67
|
+
contest = await sess.get(DragonContest, id)
|
|
68
|
+
if not contest:
|
|
69
|
+
await dragon_contest_command.finish(f"未找到ID为 {id} 的龙龙大赛")
|
|
70
|
+
if contest.status == ContestStatus.RUNNING.value:
|
|
71
|
+
await dragon_contest_command.finish("无法删除正在进行中的龙龙大赛")
|
|
72
|
+
await sess.execute(
|
|
73
|
+
delete(DragonContestPlayer).where(DragonContestPlayer.contest_id == id)
|
|
74
|
+
)
|
|
75
|
+
await sess.delete(contest)
|
|
76
|
+
try:
|
|
77
|
+
await sess.commit()
|
|
78
|
+
except Exception as e:
|
|
79
|
+
await sess.rollback()
|
|
80
|
+
logger.exception(e)
|
|
81
|
+
await dragon_contest_command.finish("删除比赛失败,请查看日志")
|
|
82
|
+
await dragon_contest_command.finish(
|
|
83
|
+
f"已删除龙龙大赛(ID: {id})及其所有参赛数据"
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@dragon_contest_command.assign("list")
|
|
88
|
+
async def handle_list_contests(sess: async_scoped_session):
|
|
89
|
+
contest = (
|
|
90
|
+
await sess.scalars(
|
|
91
|
+
select(DragonContest).order_by(DragonContest.start_ts.desc())
|
|
92
|
+
)
|
|
93
|
+
).all()
|
|
94
|
+
if not contest:
|
|
95
|
+
await dragon_contest_command.finish("当前没有正在进行的龙龙大赛")
|
|
96
|
+
lines = ["当前正在进行的龙龙大赛:\n"]
|
|
97
|
+
for c in contest:
|
|
98
|
+
dt = datetime.fromtimestamp(c.start_ts)
|
|
99
|
+
lines.append(
|
|
100
|
+
f"ID: {c.id}\n"
|
|
101
|
+
f"时间:{dt:%Y-%m-%d %H:%M}\n"
|
|
102
|
+
f"人数限制:{c.limit}\n"
|
|
103
|
+
f"━━━━━━━━━━━━━━"
|
|
104
|
+
)
|
|
105
|
+
await dragon_contest_command.finish("\n".join(lines))
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@dragon_contest_command.assign("status")
|
|
109
|
+
async def handle_contest_status(sess: async_scoped_session):
|
|
110
|
+
contest = await get_current_contest(sess)
|
|
111
|
+
if not contest:
|
|
112
|
+
await dragon_contest_command.finish("当前没有正在进行的龙龙大赛")
|
|
113
|
+
count = await sess.scalar(
|
|
114
|
+
select(func.count())
|
|
115
|
+
.select_from(DragonContestPlayer)
|
|
116
|
+
.where(DragonContestPlayer.contest_id == contest.id)
|
|
117
|
+
) or 0
|
|
118
|
+
phase = "进行中" if contest.status == ContestStatus.RUNNING.value else "报名中"
|
|
119
|
+
await dragon_contest_command.finish(
|
|
120
|
+
"龙龙大赛\n\n"
|
|
121
|
+
f"当前阶段:{phase}\n"
|
|
122
|
+
f"时间:{datetime.fromtimestamp(contest.start_ts):%Y-%m-%d %H:%M}\n"
|
|
123
|
+
f"报名人数:{count}/{contest.limit}"
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@add_dragon_contest_player_command.handle()
|
|
128
|
+
async def handle_add_dragon_contest_player(
|
|
129
|
+
name: str,
|
|
130
|
+
user_id: str,
|
|
131
|
+
sess: async_scoped_session,
|
|
132
|
+
):
|
|
133
|
+
contest = await get_current_contest(sess)
|
|
134
|
+
if not contest:
|
|
135
|
+
await add_dragon_contest_player_command.finish("当前没有可报名的龙龙大赛")
|
|
136
|
+
if contest.status != ContestStatus.SIGNUP.value:
|
|
137
|
+
await add_dragon_contest_player_command.finish(
|
|
138
|
+
"当前阶段的龙龙大赛已无法进行该操作"
|
|
139
|
+
)
|
|
140
|
+
count = await sess.scalar(
|
|
141
|
+
select(func.count())
|
|
142
|
+
.select_from(DragonContestPlayer)
|
|
143
|
+
.where(
|
|
144
|
+
DragonContestPlayer.contest_id == contest.id,
|
|
145
|
+
DragonContestPlayer.eliminated.is_(False),
|
|
146
|
+
)
|
|
147
|
+
) or 0
|
|
148
|
+
if count >= contest.limit:
|
|
149
|
+
await add_dragon_contest_player_command.finish("本次龙龙大赛报名人数已满")
|
|
150
|
+
exists = await sess.scalar(
|
|
151
|
+
select(func.count())
|
|
152
|
+
.select_from(DragonContestPlayer)
|
|
153
|
+
.where(
|
|
154
|
+
DragonContestPlayer.contest_id == contest.id,
|
|
155
|
+
DragonContestPlayer.user_id == user_id,
|
|
156
|
+
)
|
|
157
|
+
)
|
|
158
|
+
if exists:
|
|
159
|
+
await add_dragon_contest_player_command.finish("该用户已在参赛名单中")
|
|
160
|
+
player = DragonContestPlayer(
|
|
161
|
+
contest_id=contest.id,
|
|
162
|
+
user_id=user_id,
|
|
163
|
+
dragon_name=name,
|
|
164
|
+
)
|
|
165
|
+
sess.add(player)
|
|
166
|
+
try:
|
|
167
|
+
await sess.commit()
|
|
168
|
+
except IntegrityError:
|
|
169
|
+
await sess.rollback()
|
|
170
|
+
await add_dragon_contest_player_command.finish("添加失败,请查看日志")
|
|
171
|
+
except Exception as e:
|
|
172
|
+
await sess.rollback()
|
|
173
|
+
logger.exception(e)
|
|
174
|
+
await add_dragon_contest_player_command.finish("添加失败,请查看日志")
|
|
175
|
+
await add_dragon_contest_player_command.finish(
|
|
176
|
+
"已成功添加参赛者\n"
|
|
177
|
+
f"用户ID:{user_id}\n"
|
|
178
|
+
f"龙龙名称:{name}"
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
@remove_dragon_contest_player_command.handle()
|
|
183
|
+
async def handle_remove_dragon_contest_player(user_id: str, sess: async_scoped_session):
|
|
184
|
+
contest = await get_current_contest(sess)
|
|
185
|
+
if not contest:
|
|
186
|
+
await remove_dragon_contest_player_command.finish("当前没有龙龙大赛")
|
|
187
|
+
if contest.status != ContestStatus.SIGNUP.value:
|
|
188
|
+
await remove_dragon_contest_player_command.finish(
|
|
189
|
+
"当前阶段的龙龙大赛已无法进行该操作"
|
|
190
|
+
)
|
|
191
|
+
player = await sess.scalar(
|
|
192
|
+
select(DragonContestPlayer)
|
|
193
|
+
.where(
|
|
194
|
+
DragonContestPlayer.contest_id == contest.id,
|
|
195
|
+
DragonContestPlayer.user_id == user_id,
|
|
196
|
+
)
|
|
197
|
+
)
|
|
198
|
+
if not player:
|
|
199
|
+
await remove_dragon_contest_player_command.finish("该用户不在参赛名单中")
|
|
200
|
+
await sess.delete(player)
|
|
201
|
+
try:
|
|
202
|
+
await sess.commit()
|
|
203
|
+
except Exception as e:
|
|
204
|
+
await sess.rollback()
|
|
205
|
+
logger.exception(e)
|
|
206
|
+
await remove_dragon_contest_player_command.finish(
|
|
207
|
+
"移除参赛者失败,请查看日志"
|
|
208
|
+
)
|
|
209
|
+
await remove_dragon_contest_player_command.finish(
|
|
210
|
+
"已成功移除参赛者\n"
|
|
211
|
+
f"用户ID:{user_id}\n"
|
|
212
|
+
f"龙龙名称:{player.dragon_name}"
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
@list_dragon_contest_player_command.handle()
|
|
217
|
+
async def handle_list_dragon_contest_player(sess: async_scoped_session):
|
|
218
|
+
contest = await get_current_contest(sess)
|
|
219
|
+
if not contest:
|
|
220
|
+
await list_dragon_contest_player_command.finish("当前没有龙龙大赛")
|
|
221
|
+
players = (
|
|
222
|
+
await sess.scalars(
|
|
223
|
+
select(DragonContestPlayer)
|
|
224
|
+
.where(DragonContestPlayer.contest_id == contest.id)
|
|
225
|
+
.order_by(DragonContestPlayer.id)
|
|
226
|
+
)
|
|
227
|
+
).all()
|
|
228
|
+
if not players:
|
|
229
|
+
await list_dragon_contest_player_command.finish("当前没有参赛者")
|
|
230
|
+
lines = [
|
|
231
|
+
"龙龙大赛参赛名单\n",
|
|
232
|
+
f"比赛时间:{datetime.fromtimestamp(contest.start_ts):%Y-%m-%d %H:%M}\n",
|
|
233
|
+
]
|
|
234
|
+
for idx, p in enumerate(players, start=1):
|
|
235
|
+
status = "❌ 已淘汰" if p.eliminated else "✅ 在赛"
|
|
236
|
+
lines.append(
|
|
237
|
+
f"{idx}. {p.dragon_name}\n"
|
|
238
|
+
f" 用户ID:{p.user_id}\n"
|
|
239
|
+
f" 状态:{status}\n"
|
|
240
|
+
)
|
|
241
|
+
await list_dragon_contest_player_command.finish("\n".join(lines))
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
from nonebot import get_plugin_config
|
|
2
|
+
from nonebot.permission import SUPERUSER
|
|
3
|
+
from nonebot.rule import Rule
|
|
4
|
+
from arclet.alconna import Args, Alconna, CommandMeta, Option, Subcommand
|
|
5
|
+
from nonebot_plugin_alconna import on_alconna
|
|
6
|
+
|
|
7
|
+
from ..config import Config
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
plugin_config = get_plugin_config(Config)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def is_enable() -> Rule:
|
|
14
|
+
def _rule() -> bool:
|
|
15
|
+
return bool(plugin_config.dc_github_token)
|
|
16
|
+
return Rule(_rule)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
dragon_contest_command = on_alconna(
|
|
20
|
+
Alconna(
|
|
21
|
+
"dragon_contest",
|
|
22
|
+
Subcommand(
|
|
23
|
+
"create|创建",
|
|
24
|
+
Args["time#比赛时间", str],
|
|
25
|
+
Option(
|
|
26
|
+
"-n|number",
|
|
27
|
+
Args["limit#参加人数限制,最大值为 32", int],
|
|
28
|
+
),
|
|
29
|
+
help_text="创建一个新的龙龙大赛,时间格式为YYYY/MM/DD HH:MM",
|
|
30
|
+
),
|
|
31
|
+
Subcommand(
|
|
32
|
+
"delete|删除",
|
|
33
|
+
Args["id#比赛ID", int],
|
|
34
|
+
help_text="删除指定ID的龙龙大赛",
|
|
35
|
+
),
|
|
36
|
+
Subcommand(
|
|
37
|
+
"list|列表",
|
|
38
|
+
help_text="列出所有正在进行的龙龙大赛",
|
|
39
|
+
),
|
|
40
|
+
Subcommand(
|
|
41
|
+
"status|状态",
|
|
42
|
+
help_text="显示龙龙大赛报名状态",
|
|
43
|
+
),
|
|
44
|
+
meta=CommandMeta(
|
|
45
|
+
compact=True,
|
|
46
|
+
description="龙龙大赛管理系统",
|
|
47
|
+
usage="使用样例: /龙龙大赛 创建 2025/08/05 22:00",
|
|
48
|
+
example="/龙龙大赛 创建 2025/08/05 22:00",
|
|
49
|
+
),
|
|
50
|
+
),
|
|
51
|
+
permission=SUPERUSER,
|
|
52
|
+
aliases={"龙龙大赛"},
|
|
53
|
+
use_cmd_start=True,
|
|
54
|
+
rule=is_enable(),
|
|
55
|
+
priority=10,
|
|
56
|
+
block=True,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
add_dragon_contest_player_command = on_alconna(
|
|
61
|
+
Alconna(
|
|
62
|
+
"添加龙龙大赛参赛者",
|
|
63
|
+
Args["user_id#用户ID", str],
|
|
64
|
+
Args["name#龙龙名称", str],
|
|
65
|
+
meta=CommandMeta(
|
|
66
|
+
compact=True,
|
|
67
|
+
description="为当前龙龙大赛添加参赛者",
|
|
68
|
+
usage="/添加龙龙大赛参赛者 用户ID 龙龙名称",
|
|
69
|
+
example="/添加龙龙大赛参赛者 12345678 我的龙龙",
|
|
70
|
+
),
|
|
71
|
+
),
|
|
72
|
+
permission=SUPERUSER,
|
|
73
|
+
use_cmd_start=True,
|
|
74
|
+
priority=10,
|
|
75
|
+
block=True,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
remove_dragon_contest_player_command = on_alconna(
|
|
80
|
+
Alconna(
|
|
81
|
+
"移除龙龙大赛参赛者",
|
|
82
|
+
Args["user_id#用户ID", str],
|
|
83
|
+
meta=CommandMeta(
|
|
84
|
+
compact=True,
|
|
85
|
+
description="移除当前龙龙大赛的参赛者",
|
|
86
|
+
usage="/移除龙龙大赛参赛者 用户ID",
|
|
87
|
+
example="/移除龙龙大赛参赛者 12345678",
|
|
88
|
+
),
|
|
89
|
+
),
|
|
90
|
+
permission=SUPERUSER,
|
|
91
|
+
use_cmd_start=True,
|
|
92
|
+
priority=10,
|
|
93
|
+
block=True,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
list_dragon_contest_player_command = on_alconna(
|
|
98
|
+
Alconna(
|
|
99
|
+
"龙龙大赛参赛名单",
|
|
100
|
+
meta=CommandMeta(
|
|
101
|
+
compact=True,
|
|
102
|
+
description="查看当前龙龙大赛的参赛名单",
|
|
103
|
+
usage="/龙龙大赛参赛名单",
|
|
104
|
+
example="/龙龙大赛参赛名单",
|
|
105
|
+
),
|
|
106
|
+
),
|
|
107
|
+
permission=SUPERUSER,
|
|
108
|
+
use_cmd_start=True,
|
|
109
|
+
priority=10,
|
|
110
|
+
block=True,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
join_dragon_contest_command = on_alconna(
|
|
115
|
+
Alconna(
|
|
116
|
+
"加入龙龙大赛",
|
|
117
|
+
Args["name#龙龙名称", str],
|
|
118
|
+
meta=CommandMeta(
|
|
119
|
+
compact=True,
|
|
120
|
+
description="加入龙龙大赛",
|
|
121
|
+
usage="/加入龙龙大赛",
|
|
122
|
+
example="/加入龙龙大赛 玉涛性欲姬",
|
|
123
|
+
),
|
|
124
|
+
),
|
|
125
|
+
use_cmd_start=True,
|
|
126
|
+
priority=10,
|
|
127
|
+
block=True,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
cancel_dragon_contest_command = on_alconna(
|
|
132
|
+
Alconna(
|
|
133
|
+
"取消报名",
|
|
134
|
+
meta=CommandMeta(
|
|
135
|
+
compact=True,
|
|
136
|
+
description="取消已报名的龙龙大赛",
|
|
137
|
+
usage="/取消报名",
|
|
138
|
+
example="/取消报名",
|
|
139
|
+
),
|
|
140
|
+
),
|
|
141
|
+
use_cmd_start=True,
|
|
142
|
+
priority=10,
|
|
143
|
+
block=True,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
revise_dragon_name_command = on_alconna(
|
|
148
|
+
Alconna(
|
|
149
|
+
"修改龙龙名称",
|
|
150
|
+
Args["name#新的龙龙名称", str],
|
|
151
|
+
meta=CommandMeta(
|
|
152
|
+
compact=True,
|
|
153
|
+
description="修改已报名的龙龙大赛",
|
|
154
|
+
usage="/修改龙龙名称",
|
|
155
|
+
example="/修改龙龙名称 新的龙龙名称",
|
|
156
|
+
),
|
|
157
|
+
),
|
|
158
|
+
use_cmd_start=True,
|
|
159
|
+
priority=10,
|
|
160
|
+
block=True,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
__all__ = [
|
|
165
|
+
"plugin_config",
|
|
166
|
+
"is_enable",
|
|
167
|
+
"dragon_contest_command",
|
|
168
|
+
"add_dragon_contest_player_command",
|
|
169
|
+
"remove_dragon_contest_player_command",
|
|
170
|
+
"list_dragon_contest_player_command",
|
|
171
|
+
"join_dragon_contest_command",
|
|
172
|
+
"cancel_dragon_contest_command",
|
|
173
|
+
"revise_dragon_name_command",
|
|
174
|
+
]
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
|
|
3
|
+
from nonebot.adapters import Event
|
|
4
|
+
from nonebot.log import logger
|
|
5
|
+
from sqlalchemy import delete, select, func
|
|
6
|
+
from sqlalchemy.exc import IntegrityError
|
|
7
|
+
from nonebot_plugin_orm import async_scoped_session
|
|
8
|
+
|
|
9
|
+
from ..models import DragonContestPlayer
|
|
10
|
+
from ..utils import get_signup_contest
|
|
11
|
+
from ..commands.command_registry import (
|
|
12
|
+
cancel_dragon_contest_command,
|
|
13
|
+
join_dragon_contest_command,
|
|
14
|
+
revise_dragon_name_command,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@join_dragon_contest_command.handle()
|
|
19
|
+
async def handle_join_contest(
|
|
20
|
+
name: str,
|
|
21
|
+
sess: async_scoped_session,
|
|
22
|
+
event: Event,
|
|
23
|
+
):
|
|
24
|
+
contest = await get_signup_contest(sess)
|
|
25
|
+
if not contest:
|
|
26
|
+
await join_dragon_contest_command.finish("当前阶段的龙龙大赛已无法进行该操作")
|
|
27
|
+
current_count = await sess.scalar(
|
|
28
|
+
select(func.count())
|
|
29
|
+
.select_from(DragonContestPlayer)
|
|
30
|
+
.where(DragonContestPlayer.contest_id == contest.id)
|
|
31
|
+
) or 0
|
|
32
|
+
if current_count >= contest.limit:
|
|
33
|
+
await join_dragon_contest_command.finish("本次龙龙大赛报名人数已满")
|
|
34
|
+
player = DragonContestPlayer(
|
|
35
|
+
contest_id=contest.id,
|
|
36
|
+
user_id=str(event.get_user_id()),
|
|
37
|
+
dragon_name=name,
|
|
38
|
+
)
|
|
39
|
+
sess.add(player)
|
|
40
|
+
try:
|
|
41
|
+
await sess.commit()
|
|
42
|
+
except IntegrityError:
|
|
43
|
+
await sess.rollback()
|
|
44
|
+
await join_dragon_contest_command.finish("你已经报名过本次龙龙大赛")
|
|
45
|
+
except Exception as e:
|
|
46
|
+
await sess.rollback()
|
|
47
|
+
logger.exception(e)
|
|
48
|
+
await join_dragon_contest_command.finish("加入比赛失败,请查看日志")
|
|
49
|
+
dt = datetime.fromtimestamp(contest.start_ts)
|
|
50
|
+
await join_dragon_contest_command.finish(
|
|
51
|
+
"报名成功!\n"
|
|
52
|
+
f"龙龙名称:{name}\n"
|
|
53
|
+
f"比赛时间:{dt:%Y-%m-%d %H:%M}"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@cancel_dragon_contest_command.handle()
|
|
58
|
+
async def handle_cancel_contest(
|
|
59
|
+
sess: async_scoped_session,
|
|
60
|
+
event: Event,
|
|
61
|
+
):
|
|
62
|
+
contest = await get_signup_contest(sess)
|
|
63
|
+
if not contest:
|
|
64
|
+
await cancel_dragon_contest_command.finish("当前阶段的龙龙大赛已无法进行该操作")
|
|
65
|
+
stmt = (
|
|
66
|
+
delete(DragonContestPlayer)
|
|
67
|
+
.where(
|
|
68
|
+
DragonContestPlayer.contest_id == contest.id,
|
|
69
|
+
DragonContestPlayer.user_id == str(event.get_user_id()),
|
|
70
|
+
)
|
|
71
|
+
.returning(DragonContestPlayer.id)
|
|
72
|
+
)
|
|
73
|
+
result = await sess.execute(stmt)
|
|
74
|
+
deleted_ids = result.scalars().all()
|
|
75
|
+
if not deleted_ids:
|
|
76
|
+
await cancel_dragon_contest_command.finish("你尚未报名本次龙龙大赛")
|
|
77
|
+
try:
|
|
78
|
+
await sess.commit()
|
|
79
|
+
except Exception as e:
|
|
80
|
+
await sess.rollback()
|
|
81
|
+
logger.exception(e)
|
|
82
|
+
await cancel_dragon_contest_command.finish("取消报名失败,请查看日志")
|
|
83
|
+
await cancel_dragon_contest_command.finish("已取消本次龙龙大赛的报名")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@revise_dragon_name_command.handle()
|
|
87
|
+
async def handle_revise_dragon_name(
|
|
88
|
+
name: str,
|
|
89
|
+
sess: async_scoped_session,
|
|
90
|
+
event: Event,
|
|
91
|
+
):
|
|
92
|
+
contest = await get_signup_contest(sess)
|
|
93
|
+
if not contest:
|
|
94
|
+
await revise_dragon_name_command.finish("当前阶段的龙龙大赛已无法进行该操作")
|
|
95
|
+
if datetime.now().timestamp() > contest.start_ts - 600:
|
|
96
|
+
await revise_dragon_name_command.finish(
|
|
97
|
+
"距离比赛开始不足10分钟,无法修改龙龙名称"
|
|
98
|
+
)
|
|
99
|
+
player = await sess.scalar(
|
|
100
|
+
select(DragonContestPlayer)
|
|
101
|
+
.where(
|
|
102
|
+
DragonContestPlayer.contest_id == contest.id,
|
|
103
|
+
DragonContestPlayer.user_id == str(event.get_user_id()),
|
|
104
|
+
)
|
|
105
|
+
)
|
|
106
|
+
if not player:
|
|
107
|
+
await revise_dragon_name_command.finish("你尚未报名本次龙龙大赛")
|
|
108
|
+
player.dragon_name = name
|
|
109
|
+
try:
|
|
110
|
+
await sess.commit()
|
|
111
|
+
except Exception as e:
|
|
112
|
+
await sess.rollback()
|
|
113
|
+
logger.exception(e)
|
|
114
|
+
await revise_dragon_name_command.finish("修改龙龙名称失败,请查看日志")
|
|
115
|
+
dt = datetime.fromtimestamp(contest.start_ts)
|
|
116
|
+
await revise_dragon_name_command.finish(
|
|
117
|
+
"龙龙名称修改成功\n"
|
|
118
|
+
f"新名称:{name}\n"
|
|
119
|
+
f"比赛时间:{dt:%Y-%m-%d %H:%M}"
|
|
120
|
+
)
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
from sqlalchemy import select
|
|
2
|
+
from nonebot import get_bot
|
|
3
|
+
from nonebot.log import logger
|
|
4
|
+
from nonebot_plugin_orm import get_session
|
|
5
|
+
from nonebot_plugin_alconna import MsgTarget, Target, UniMessage
|
|
6
|
+
|
|
7
|
+
from .utils import run_single_battle
|
|
8
|
+
from .models import DragonContest, DragonContestPlayer, ContestStatus
|
|
9
|
+
|
|
10
|
+
async def run_contest(contest_id: int):
|
|
11
|
+
async with get_session() as sess:
|
|
12
|
+
contest = await sess.get(DragonContest, contest_id)
|
|
13
|
+
if not contest:
|
|
14
|
+
return
|
|
15
|
+
if not contest.status != ContestStatus.RUNNING.value:
|
|
16
|
+
return
|
|
17
|
+
try:
|
|
18
|
+
target: MsgTarget = Target.load(contest.target)
|
|
19
|
+
except Exception:
|
|
20
|
+
logger.opt(colors=True).error(
|
|
21
|
+
f"<red>比赛ID {contest_id} 的目标反序列化失败,比赛取消</red>"
|
|
22
|
+
)
|
|
23
|
+
contest.status = ContestStatus.FINISHED.value
|
|
24
|
+
await sess.commit()
|
|
25
|
+
return
|
|
26
|
+
try:
|
|
27
|
+
bot = get_bot(target.self_id)
|
|
28
|
+
except Exception:
|
|
29
|
+
logger.opt(colors=True).warning("<yellow>未找到可用的机器人实例,此任务将被跳过</yellow>")
|
|
30
|
+
contest.status = ContestStatus.FINISHED.value
|
|
31
|
+
await sess.commit()
|
|
32
|
+
return
|
|
33
|
+
players = (
|
|
34
|
+
await sess.scalars(
|
|
35
|
+
select(DragonContestPlayer)
|
|
36
|
+
.where(
|
|
37
|
+
DragonContestPlayer.contest_id == contest_id,
|
|
38
|
+
DragonContestPlayer.eliminated.is_(False),
|
|
39
|
+
)
|
|
40
|
+
)
|
|
41
|
+
).all()
|
|
42
|
+
if len(players) < 2:
|
|
43
|
+
await UniMessage.text("比赛人数不足,比赛取消").send(target=target, bot=bot)
|
|
44
|
+
contest.status = ContestStatus.FINISHED.value
|
|
45
|
+
await sess.commit()
|
|
46
|
+
return
|
|
47
|
+
contest.status = ContestStatus.RUNNING.value
|
|
48
|
+
await sess.commit()
|
|
49
|
+
await UniMessage.text(
|
|
50
|
+
f"龙龙大赛开始,本次参赛人数为: {len(players)}"
|
|
51
|
+
).send(target=target, bot=bot)
|
|
52
|
+
round_no = contest.current_round or 1
|
|
53
|
+
while True:
|
|
54
|
+
alive_players = (
|
|
55
|
+
await sess.scalars(
|
|
56
|
+
select(DragonContestPlayer)
|
|
57
|
+
.where(
|
|
58
|
+
DragonContestPlayer.contest_id == contest_id,
|
|
59
|
+
DragonContestPlayer.eliminated.is_(False),
|
|
60
|
+
)
|
|
61
|
+
)
|
|
62
|
+
).all()
|
|
63
|
+
if len(alive_players) <= 1:
|
|
64
|
+
break
|
|
65
|
+
await UniMessage.text(
|
|
66
|
+
f"第 {round_no} 轮比赛开始,当前剩余选手: {len(alive_players)}"
|
|
67
|
+
).send(target=target, bot=bot)
|
|
68
|
+
for i in range(0, len(alive_players), 2):
|
|
69
|
+
if i + 1 >= len(alive_players):
|
|
70
|
+
await UniMessage.text(
|
|
71
|
+
f"选手 {alive_players[i].dragon_name} 获得轮空,直接晋级下一轮"
|
|
72
|
+
).send(target=target, bot=bot)
|
|
73
|
+
continue
|
|
74
|
+
p1 = alive_players[i]
|
|
75
|
+
p2 = alive_players[i + 1]
|
|
76
|
+
winner, loser, reason = await run_single_battle(p1, p2, round_no)
|
|
77
|
+
loser.eliminated = True
|
|
78
|
+
sess.add(loser)
|
|
79
|
+
await UniMessage.text(
|
|
80
|
+
f"对战结果:\n"
|
|
81
|
+
f"选手1:{p1.dragon_name}\n"
|
|
82
|
+
f"选手2:{p2.dragon_name}\n"
|
|
83
|
+
f"获胜者:{winner.dragon_name}\n"
|
|
84
|
+
f"失败者:{loser.dragon_name}\n"
|
|
85
|
+
f"理由:{reason}\n"
|
|
86
|
+
).send(target=target, bot=bot)
|
|
87
|
+
await sess.commit()
|
|
88
|
+
round_no +=1
|
|
89
|
+
contest.current_round = round_no
|
|
90
|
+
sess.add(contest)
|
|
91
|
+
await sess.commit()
|
|
92
|
+
champion = (
|
|
93
|
+
await sess.scalars(
|
|
94
|
+
select(DragonContestPlayer)
|
|
95
|
+
.where(
|
|
96
|
+
DragonContestPlayer.contest_id == contest_id,
|
|
97
|
+
DragonContestPlayer.eliminated.is_(False),
|
|
98
|
+
)
|
|
99
|
+
)
|
|
100
|
+
).first()
|
|
101
|
+
if champion:
|
|
102
|
+
await UniMessage.text(
|
|
103
|
+
"龙龙大赛圆满结束\n"
|
|
104
|
+
f"本届龙龙大赛冠军为: {champion.dragon_name}!"
|
|
105
|
+
).send(target=target, bot=bot)
|
|
106
|
+
contest.status = ContestStatus.FINISHED.value
|
|
107
|
+
sess.add(contest)
|
|
108
|
+
await sess.commit()
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from nonebot_plugin_orm import Model
|
|
3
|
+
from sqlalchemy.orm import Mapped, mapped_column
|
|
4
|
+
from sqlalchemy import ForeignKey, UniqueConstraint, String, JSON
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class DragonContestConfig(Model):
|
|
8
|
+
__tablename__ = "dragon_contest_config"
|
|
9
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
10
|
+
default_limit: Mapped[int] = mapped_column(nullable=False)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ContestStatus(str, Enum):
|
|
14
|
+
SIGNUP = "signup"
|
|
15
|
+
RUNNING = "running"
|
|
16
|
+
FINISHED = "finished"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class DragonContest(Model):
|
|
20
|
+
__tablename__ = "dragon_contest"
|
|
21
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
22
|
+
start_ts: Mapped[int] = mapped_column(index=True)
|
|
23
|
+
limit: Mapped[int] = mapped_column(nullable=False)
|
|
24
|
+
target: Mapped[dict] = mapped_column(JSON, nullable=False)
|
|
25
|
+
current_round: Mapped[int] = mapped_column(default=1)
|
|
26
|
+
status: Mapped[str] = mapped_column(
|
|
27
|
+
String(16),
|
|
28
|
+
default=ContestStatus.SIGNUP.value,
|
|
29
|
+
nullable=False,
|
|
30
|
+
index=True,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class DragonContestPlayer(Model):
|
|
35
|
+
__tablename__ = "dragon_contest_player"
|
|
36
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
37
|
+
contest_id: Mapped[int] = mapped_column(
|
|
38
|
+
ForeignKey("dragon_contest.id"),
|
|
39
|
+
index=True,
|
|
40
|
+
)
|
|
41
|
+
user_id: Mapped[str] = mapped_column(index=True)
|
|
42
|
+
dragon_name: Mapped[str] = mapped_column(String(32))
|
|
43
|
+
eliminated: Mapped[bool] = mapped_column(default=False)
|
|
44
|
+
__table_args__ = (
|
|
45
|
+
UniqueConstraint("contest_id", "user_id"),
|
|
46
|
+
)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from collections.abc import Mapping, Sequence
|
|
2
|
+
from typing import Any, cast
|
|
3
|
+
from azure.ai.inference.aio import ChatCompletionsClient
|
|
4
|
+
from azure.ai.inference.models import ChatRequestMessage
|
|
5
|
+
from azure.core.credentials import AzureKeyCredential
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class OpenAIHandler:
|
|
9
|
+
def __init__(
|
|
10
|
+
self,
|
|
11
|
+
api_key: str,
|
|
12
|
+
endpoint: str,
|
|
13
|
+
model_name: str,
|
|
14
|
+
temperature: float,
|
|
15
|
+
top_p: float,
|
|
16
|
+
):
|
|
17
|
+
self.client = ChatCompletionsClient(
|
|
18
|
+
endpoint=endpoint,
|
|
19
|
+
credential=AzureKeyCredential(api_key)
|
|
20
|
+
)
|
|
21
|
+
self.api_key = api_key
|
|
22
|
+
self.endpoint = endpoint
|
|
23
|
+
self.model_name = model_name
|
|
24
|
+
self.temperature = temperature
|
|
25
|
+
self.top_p = top_p
|
|
26
|
+
|
|
27
|
+
async def get_response(
|
|
28
|
+
self,
|
|
29
|
+
messages: Sequence[ChatRequestMessage | Mapping[str, Any]],
|
|
30
|
+
) -> str:
|
|
31
|
+
request_messages = cast(list[ChatRequestMessage], list(messages))
|
|
32
|
+
response = await self.client.complete(
|
|
33
|
+
messages=request_messages,
|
|
34
|
+
model=self.model_name,
|
|
35
|
+
temperature=self.temperature,
|
|
36
|
+
top_p=self.top_p,
|
|
37
|
+
)
|
|
38
|
+
choices = response.choices
|
|
39
|
+
if not choices:
|
|
40
|
+
raise ValueError("GitHub Models returned no choices")
|
|
41
|
+
content = choices[0].message.content
|
|
42
|
+
if not content:
|
|
43
|
+
raise ValueError("GitHub Models returned empty content")
|
|
44
|
+
return content
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import json
|
|
3
|
+
import random
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
|
|
6
|
+
from sqlalchemy import select
|
|
7
|
+
from nonebot import get_plugin_config, get_driver
|
|
8
|
+
from nonebot.log import logger
|
|
9
|
+
from nonebot_plugin_apscheduler import scheduler
|
|
10
|
+
from nonebot_plugin_orm import async_scoped_session, get_session
|
|
11
|
+
|
|
12
|
+
from .config import Config
|
|
13
|
+
from .models import (
|
|
14
|
+
ContestStatus,
|
|
15
|
+
DragonContest,
|
|
16
|
+
DragonContestConfig,
|
|
17
|
+
DragonContestPlayer,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
driver = get_driver()
|
|
22
|
+
plugin_config = get_plugin_config(Config)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
async def get_signup_contest(sess: async_scoped_session) -> DragonContest | None:
|
|
26
|
+
now_ts = int(datetime.now().timestamp())
|
|
27
|
+
signup_end = now_ts + plugin_config.dc_signup_before_seconds
|
|
28
|
+
return await sess.scalar(
|
|
29
|
+
select(DragonContest)
|
|
30
|
+
.where(
|
|
31
|
+
DragonContest.start_ts >= now_ts,
|
|
32
|
+
DragonContest.start_ts <= signup_end,
|
|
33
|
+
DragonContest.status == ContestStatus.SIGNUP.value,
|
|
34
|
+
)
|
|
35
|
+
.order_by(DragonContest.start_ts.asc())
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
async def get_active_contest(sess: async_scoped_session) -> DragonContest | None:
|
|
40
|
+
now_ts = int(datetime.now().timestamp())
|
|
41
|
+
return await sess.scalar(
|
|
42
|
+
select(DragonContest)
|
|
43
|
+
.where(
|
|
44
|
+
DragonContest.start_ts <= now_ts,
|
|
45
|
+
DragonContest.status == ContestStatus.RUNNING.value,
|
|
46
|
+
)
|
|
47
|
+
.order_by(DragonContest.start_ts.desc())
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
async def get_current_contest(sess: async_scoped_session) -> DragonContest | None:
|
|
52
|
+
contest = await get_active_contest(sess)
|
|
53
|
+
if contest:
|
|
54
|
+
return contest
|
|
55
|
+
return await get_signup_contest(sess)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
async def get_or_create_config(sess: async_scoped_session) -> DragonContestConfig:
|
|
59
|
+
config = await sess.get(DragonContestConfig, 1)
|
|
60
|
+
if not config:
|
|
61
|
+
config = DragonContestConfig(
|
|
62
|
+
id=1,
|
|
63
|
+
default_limit=plugin_config.dc_default_dragon_number,
|
|
64
|
+
)
|
|
65
|
+
sess.add(config)
|
|
66
|
+
await sess.commit()
|
|
67
|
+
return config
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def register_contest_start_job(contest: DragonContest):
|
|
71
|
+
scheduler.add_job(
|
|
72
|
+
on_contest_start,
|
|
73
|
+
trigger="date",
|
|
74
|
+
run_date=datetime.fromtimestamp(contest.start_ts),
|
|
75
|
+
args=[contest.id],
|
|
76
|
+
id=f"dragon_contest_start_{contest.id}",
|
|
77
|
+
replace_existing=True,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
async def on_contest_start(contest_id: int):
|
|
82
|
+
async with get_session() as sess:
|
|
83
|
+
contest = await sess.get(DragonContest, contest_id)
|
|
84
|
+
if not contest:
|
|
85
|
+
return
|
|
86
|
+
if contest.status == ContestStatus.RUNNING.value:
|
|
87
|
+
return
|
|
88
|
+
contest.status = ContestStatus.RUNNING.value
|
|
89
|
+
sess.add(contest)
|
|
90
|
+
try:
|
|
91
|
+
await sess.commit()
|
|
92
|
+
except Exception as e:
|
|
93
|
+
await sess.rollback()
|
|
94
|
+
logger.exception(e)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
async def run_single_battle(
|
|
98
|
+
p1: DragonContestPlayer,
|
|
99
|
+
p2: DragonContestPlayer,
|
|
100
|
+
round: int,
|
|
101
|
+
):
|
|
102
|
+
from . import openai_handler
|
|
103
|
+
if openai_handler is None:
|
|
104
|
+
winner, loser = random.sample([p1, p2], 2)
|
|
105
|
+
return (
|
|
106
|
+
winner,
|
|
107
|
+
loser,
|
|
108
|
+
"未配置 OpenAI,随机决出胜负"
|
|
109
|
+
)
|
|
110
|
+
system_prompt = (
|
|
111
|
+
"你是龙龙大赛的裁判。"
|
|
112
|
+
"只输出一段 JSON,不要输出任何额外文本。"
|
|
113
|
+
"JSON 格式如下:"
|
|
114
|
+
'{"winner":"p1|p2","reason":"..."}'
|
|
115
|
+
)
|
|
116
|
+
user_prompt = (
|
|
117
|
+
f"第 {round} 回合对战:\n"
|
|
118
|
+
f"选手1:{p1.dragon_name}\n"
|
|
119
|
+
f"选手2:{p2.dragon_name}\n\n"
|
|
120
|
+
"请从战斗力、智慧、速度、防御、技能等方面综合比较,"
|
|
121
|
+
"给出更强者,并用一句话说明原因。"
|
|
122
|
+
)
|
|
123
|
+
messages = [
|
|
124
|
+
{"role": "system", "content": system_prompt},
|
|
125
|
+
{"role": "user", "content": user_prompt},
|
|
126
|
+
]
|
|
127
|
+
try:
|
|
128
|
+
content = await openai_handler.get_response(messages)
|
|
129
|
+
except Exception:
|
|
130
|
+
logger.exception("调用 OpenAI 接口失败,随机决出胜负")
|
|
131
|
+
winner, loser = random.sample([p1, p2], 2)
|
|
132
|
+
return (
|
|
133
|
+
winner,
|
|
134
|
+
loser,
|
|
135
|
+
"调用 OpenAI 接口失败,随机决出胜负"
|
|
136
|
+
)
|
|
137
|
+
match = re.search(r"\{.*\}", content, flags=re.S)
|
|
138
|
+
if not match:
|
|
139
|
+
winner, loser = random.sample([p1, p2], 2)
|
|
140
|
+
return (
|
|
141
|
+
winner,
|
|
142
|
+
loser,
|
|
143
|
+
f"AI 输出格式错误,随机决出胜负。原始输出:{content}"
|
|
144
|
+
)
|
|
145
|
+
try:
|
|
146
|
+
data = json.loads(match.group(0))
|
|
147
|
+
except Exception:
|
|
148
|
+
winner, loser = random.sample([p1, p2], 2)
|
|
149
|
+
return (
|
|
150
|
+
winner,
|
|
151
|
+
loser,
|
|
152
|
+
f"输出解析失败,随机决出胜负。原始输出:{content}"
|
|
153
|
+
)
|
|
154
|
+
winner_flag = str(data.get("winner", "")).strip().lower()
|
|
155
|
+
reason = str(data.get("reason", "")).strip()
|
|
156
|
+
if not reason:
|
|
157
|
+
reason = "AI 未给出明确理由"
|
|
158
|
+
if winner_flag == "p1":
|
|
159
|
+
return p1, p2, reason
|
|
160
|
+
elif winner_flag == "p2":
|
|
161
|
+
return p2, p1, reason
|
|
162
|
+
winner, loser = random.sample([p1, p2], 2)
|
|
163
|
+
return (
|
|
164
|
+
winner,
|
|
165
|
+
loser,
|
|
166
|
+
f"AI 输出胜者不明确,随机决出胜负。原始输出:{content}"
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@driver.on_startup
|
|
171
|
+
async def restore_contest_start_jobs():
|
|
172
|
+
async with get_session() as sess:
|
|
173
|
+
now_ts = int(datetime.now().timestamp())
|
|
174
|
+
contests = await sess.scalars(
|
|
175
|
+
select(DragonContest)
|
|
176
|
+
.where(
|
|
177
|
+
DragonContest.start_ts > now_ts,
|
|
178
|
+
DragonContest.status == ContestStatus.SIGNUP.value,
|
|
179
|
+
)
|
|
180
|
+
)
|
|
181
|
+
for contest in contests:
|
|
182
|
+
if contest.start_ts <= now_ts:
|
|
183
|
+
continue
|
|
184
|
+
register_contest_start_job(contest)
|