nonebot-plugin-werewolf 1.1.7__py3-none-any.whl → 1.1.9__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_werewolf/__init__.py +1 -1
- nonebot_plugin_werewolf/config.py +74 -15
- nonebot_plugin_werewolf/constant.py +59 -46
- nonebot_plugin_werewolf/exception.py +2 -4
- nonebot_plugin_werewolf/game.py +200 -171
- nonebot_plugin_werewolf/matchers/__init__.py +1 -0
- nonebot_plugin_werewolf/matchers/depends.py +4 -4
- nonebot_plugin_werewolf/matchers/edit_behavior.py +217 -0
- nonebot_plugin_werewolf/matchers/edit_preset.py +11 -11
- nonebot_plugin_werewolf/matchers/message_in_game.py +3 -1
- nonebot_plugin_werewolf/matchers/poke/chronocat_poke.py +8 -5
- nonebot_plugin_werewolf/matchers/poke/ob11_poke.py +3 -3
- nonebot_plugin_werewolf/matchers/start_game.py +214 -175
- nonebot_plugin_werewolf/matchers/superuser_ops.py +3 -3
- nonebot_plugin_werewolf/models.py +46 -22
- nonebot_plugin_werewolf/player.py +366 -0
- nonebot_plugin_werewolf/player_set.py +40 -22
- nonebot_plugin_werewolf/players/__init__.py +1 -2
- nonebot_plugin_werewolf/players/civilian.py +3 -3
- nonebot_plugin_werewolf/players/guard.py +27 -20
- nonebot_plugin_werewolf/players/hunter.py +6 -5
- nonebot_plugin_werewolf/players/idiot.py +27 -19
- nonebot_plugin_werewolf/players/jester.py +29 -0
- nonebot_plugin_werewolf/players/prophet.py +20 -14
- nonebot_plugin_werewolf/players/shooter.py +54 -0
- nonebot_plugin_werewolf/players/werewolf.py +88 -29
- nonebot_plugin_werewolf/players/witch.py +48 -24
- nonebot_plugin_werewolf/players/wolfking.py +14 -8
- nonebot_plugin_werewolf/utils.py +107 -8
- {nonebot_plugin_werewolf-1.1.7.dist-info → nonebot_plugin_werewolf-1.1.9.dist-info}/METADATA +30 -20
- nonebot_plugin_werewolf-1.1.9.dist-info/RECORD +35 -0
- {nonebot_plugin_werewolf-1.1.7.dist-info → nonebot_plugin_werewolf-1.1.9.dist-info}/WHEEL +1 -1
- nonebot_plugin_werewolf/players/can_shoot.py +0 -54
- nonebot_plugin_werewolf/players/joker.py +0 -25
- nonebot_plugin_werewolf/players/player.py +0 -226
- nonebot_plugin_werewolf-1.1.7.dist-info/RECORD +0 -34
- {nonebot_plugin_werewolf-1.1.7.dist-info → nonebot_plugin_werewolf-1.1.9.dist-info/licenses}/LICENSE +0 -0
- {nonebot_plugin_werewolf-1.1.7.dist-info → nonebot_plugin_werewolf-1.1.9.dist-info}/top_level.txt +0 -0
@@ -1,23 +1,29 @@
|
|
1
|
-
from nonebot_plugin_alconna.uniseg import UniMessage
|
2
1
|
from typing_extensions import override
|
3
2
|
|
4
|
-
from ..constant import
|
3
|
+
from ..constant import stop_command_prompt
|
5
4
|
from ..models import Role, RoleGroup
|
6
|
-
from
|
5
|
+
from ..player import InteractProvider, Player
|
7
6
|
|
8
7
|
|
9
|
-
|
10
|
-
class Prophet(Player):
|
8
|
+
class ProphetInteractProvider(InteractProvider["Prophet"]):
|
11
9
|
@override
|
12
10
|
async def interact(self) -> None:
|
13
|
-
players = self.game.players.alive().exclude(self)
|
14
|
-
await self.send(
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
11
|
+
players = self.game.players.alive().exclude(self.p)
|
12
|
+
await self.p.send(
|
13
|
+
"💫请选择需要查验身份的玩家:\n"
|
14
|
+
f"{players.show()}\n\n"
|
15
|
+
"🔮发送编号选择玩家\n"
|
16
|
+
f"❌发送 “{stop_command_prompt()}” 结束回合(不查验身份)",
|
17
|
+
stop_btn_label="结束回合",
|
18
|
+
select_players=players,
|
19
19
|
)
|
20
20
|
|
21
|
-
if selected := await self.
|
22
|
-
result = "狼人" if selected.role_group == RoleGroup.
|
23
|
-
await self.send(f"✏️玩家 {selected.name} 的阵营是『{result}』")
|
21
|
+
if selected := await self.p.select_player(players, stop_btn_label="结束回合"):
|
22
|
+
result = "狼人" if selected.role_group == RoleGroup.WEREWOLF else "好人"
|
23
|
+
await self.p.send(f"✏️玩家 {selected.name} 的阵营是『{result}』")
|
24
|
+
|
25
|
+
|
26
|
+
class Prophet(Player):
|
27
|
+
role = Role.PROPHET
|
28
|
+
role_group = RoleGroup.GOODGUY
|
29
|
+
interact_provider = ProphetInteractProvider
|
@@ -0,0 +1,54 @@
|
|
1
|
+
from typing_extensions import override
|
2
|
+
|
3
|
+
from nonebot_plugin_alconna.uniseg import UniMessage
|
4
|
+
|
5
|
+
from ..constant import stop_command_prompt
|
6
|
+
from ..models import KillReason
|
7
|
+
from ..player import KillProvider, Player
|
8
|
+
|
9
|
+
|
10
|
+
class ShooterKillProvider(KillProvider["Player"]):
|
11
|
+
@override
|
12
|
+
async def post_kill(self) -> None:
|
13
|
+
if self.kill_info and self.kill_info.reason == KillReason.POISON:
|
14
|
+
await self.p.send("⚠️你昨晚被女巫毒杀,无法使用技能")
|
15
|
+
return await super().post_kill()
|
16
|
+
|
17
|
+
await self.game.send(
|
18
|
+
UniMessage.text("🕵️玩家 ")
|
19
|
+
.at(self.user_id)
|
20
|
+
.text(" 死了\n请在私聊决定射杀目标...")
|
21
|
+
)
|
22
|
+
|
23
|
+
self.game.state.shooter = None
|
24
|
+
shoot = await self.shoot()
|
25
|
+
msg = UniMessage.text("玩家 ").at(self.user_id).text(" ")
|
26
|
+
if shoot is not None:
|
27
|
+
self.game.state.shooter = self.p
|
28
|
+
await self.game.send("🔫" + msg.text("射杀了玩家 ").at(shoot.user_id))
|
29
|
+
await shoot.kill(KillReason.SHOOT, self.p)
|
30
|
+
self.selected = shoot
|
31
|
+
else:
|
32
|
+
await self.game.send("ℹ️" + msg.text("选择了取消技能"))
|
33
|
+
|
34
|
+
return await super().post_kill()
|
35
|
+
|
36
|
+
async def shoot(self) -> Player | None:
|
37
|
+
players = self.game.players.alive().exclude(self.p)
|
38
|
+
await self.p.send(
|
39
|
+
"💫请选择需要射杀的玩家:\n"
|
40
|
+
f"{players.show()}\n\n"
|
41
|
+
"🔫发送编号选择玩家\n"
|
42
|
+
f"❌发送 “{stop_command_prompt()}” 取消技能",
|
43
|
+
stop_btn_label="取消技能",
|
44
|
+
select_players=players,
|
45
|
+
)
|
46
|
+
|
47
|
+
if selected := await self.p.select_player(
|
48
|
+
players,
|
49
|
+
on_stop="ℹ️已取消技能,回合结束",
|
50
|
+
stop_btn_label="取消技能",
|
51
|
+
):
|
52
|
+
await self.p.send(f"🎯选择射杀的玩家: {selected.name}")
|
53
|
+
|
54
|
+
return selected
|
@@ -1,55 +1,58 @@
|
|
1
|
+
import secrets
|
1
2
|
from typing import TYPE_CHECKING
|
3
|
+
from typing_extensions import override
|
2
4
|
|
3
5
|
import anyio
|
4
6
|
from nonebot_plugin_alconna.uniseg import UniMessage
|
5
|
-
from typing_extensions import override
|
6
7
|
|
7
|
-
from ..constant import STOP_COMMAND,
|
8
|
+
from ..constant import STOP_COMMAND, stop_command_prompt
|
8
9
|
from ..models import Role, RoleGroup
|
9
|
-
from ..
|
10
|
-
from
|
10
|
+
from ..player import InteractProvider, NotifyProvider, Player
|
11
|
+
from ..utils import ObjectStream, as_player_set, check_index
|
11
12
|
|
12
13
|
if TYPE_CHECKING:
|
13
14
|
from ..player_set import PlayerSet
|
14
15
|
|
15
16
|
|
16
|
-
|
17
|
-
class Werewolf(Player):
|
17
|
+
class WerewolfInteractProvider(InteractProvider["Werewolf"]):
|
18
18
|
stream: ObjectStream[str | UniMessage]
|
19
19
|
|
20
20
|
@override
|
21
|
-
async def
|
22
|
-
|
23
|
-
partners = self.game.players.alive().select(RoleGroup.Werewolf).exclude(self)
|
24
|
-
if partners:
|
25
|
-
await self.send(
|
26
|
-
"🐺你的队友:\n"
|
27
|
-
+ "\n".join(f" {p.role_name}: {p.name}" for p in partners)
|
28
|
-
)
|
21
|
+
async def before(self) -> None:
|
22
|
+
self.game.state.werewolf_start()
|
29
23
|
|
30
|
-
async def
|
24
|
+
async def handle_interact(self, players: "PlayerSet") -> None:
|
31
25
|
self.selected = None
|
32
26
|
|
33
27
|
while True:
|
34
|
-
input_msg = await self.receive()
|
28
|
+
input_msg = await self.p.receive()
|
35
29
|
text = input_msg.extract_plain_text()
|
36
30
|
index = check_index(text, len(players))
|
37
31
|
if index is not None:
|
38
32
|
self.selected = players[index - 1]
|
39
33
|
msg = f"当前选择玩家: {self.selected.name}"
|
40
|
-
await self.send(
|
41
|
-
|
34
|
+
await self.p.send(
|
35
|
+
f"🎯{msg}\n发送 “{stop_command_prompt()}” 结束回合",
|
36
|
+
stop_btn_label="结束回合",
|
37
|
+
select_players=players,
|
38
|
+
)
|
39
|
+
await self.stream.send(f"📝队友 {self.p.name} {msg}")
|
42
40
|
if text == STOP_COMMAND:
|
43
41
|
if self.selected is not None:
|
44
|
-
await self.send("✅你已结束当前回合")
|
45
|
-
await self.stream.send(f"📝队友 {self.name} 结束当前回合")
|
42
|
+
await self.p.send("✅你已结束当前回合")
|
43
|
+
await self.stream.send(f"📝队友 {self.p.name} 结束当前回合")
|
46
44
|
self.stream.close()
|
47
45
|
return
|
48
|
-
await self.send(
|
46
|
+
await self.p.send(
|
47
|
+
"⚠️当前未选择玩家,无法结束回合",
|
48
|
+
select_players=players,
|
49
|
+
)
|
49
50
|
else:
|
50
|
-
await self.stream.send(
|
51
|
+
await self.stream.send(
|
52
|
+
UniMessage.text(f"💬队友 {self.p.name}:\n") + input_msg
|
53
|
+
)
|
51
54
|
|
52
|
-
async def
|
55
|
+
async def handle_broadcast(self, partners: "PlayerSet") -> None:
|
53
56
|
while not self.stream.closed:
|
54
57
|
try:
|
55
58
|
message = await self.stream.recv()
|
@@ -61,7 +64,7 @@ class Werewolf(Player):
|
|
61
64
|
@override
|
62
65
|
async def interact(self) -> None:
|
63
66
|
players = self.game.players.alive()
|
64
|
-
partners = players.select(RoleGroup.
|
67
|
+
partners = players.select(RoleGroup.WEREWOLF).exclude(self.p)
|
65
68
|
|
66
69
|
msg = UniMessage()
|
67
70
|
if partners:
|
@@ -70,19 +73,75 @@ class Werewolf(Player):
|
|
70
73
|
.text("\n".join(f" {p.role_name}: {p.name}" for p in partners))
|
71
74
|
.text("\n所有私聊消息将被转发至队友\n\n")
|
72
75
|
)
|
73
|
-
await self.send(
|
76
|
+
await self.p.send(
|
74
77
|
msg.text("💫请选择今晚的目标:\n")
|
75
78
|
.text(players.show())
|
76
79
|
.text("\n\n🔪发送编号选择玩家")
|
77
|
-
.text(f"\n❌发送 “{
|
78
|
-
.text("\n\n⚠️意见未统一将空刀")
|
80
|
+
.text(f"\n❌发送 “{stop_command_prompt()}” 结束回合")
|
81
|
+
.text("\n\n⚠️意见未统一将空刀"),
|
82
|
+
select_players=players,
|
79
83
|
)
|
80
84
|
|
81
85
|
self.stream = ObjectStream[str | UniMessage](8)
|
82
86
|
|
83
87
|
try:
|
84
88
|
async with anyio.create_task_group() as tg:
|
85
|
-
tg.start_soon(self.
|
86
|
-
tg.start_soon(self.
|
89
|
+
tg.start_soon(self.handle_interact, players)
|
90
|
+
tg.start_soon(self.handle_broadcast, partners)
|
87
91
|
finally:
|
88
92
|
del self.stream
|
93
|
+
|
94
|
+
async def finalize(self) -> None:
|
95
|
+
w = self.game.players.alive().select(RoleGroup.WEREWOLF)
|
96
|
+
match w.player_selected().shuffled:
|
97
|
+
case []:
|
98
|
+
await w.broadcast("⚠️狼人未选择目标,此晚空刀")
|
99
|
+
case [killed]:
|
100
|
+
self.game.state.killed = killed
|
101
|
+
await w.broadcast(f"🔪今晚选择的目标为: {killed.name}")
|
102
|
+
case [killed, *_] if self.game.behavior.werewolf_multi_select:
|
103
|
+
self.game.state.killed = killed
|
104
|
+
await w.broadcast(
|
105
|
+
"⚠️狼人阵营意见未统一,随机选择目标\n\n"
|
106
|
+
f"🔪今晚选择的目标为: {killed.name}"
|
107
|
+
)
|
108
|
+
case players:
|
109
|
+
await w.broadcast(
|
110
|
+
f"⚠️狼人阵营意见未统一,此晚空刀\n\n"
|
111
|
+
f"📝选择的玩家:\n{as_player_set(*players).show()}"
|
112
|
+
)
|
113
|
+
|
114
|
+
@override
|
115
|
+
async def after(self) -> None:
|
116
|
+
if self.game.state.werewolf_end():
|
117
|
+
await self.finalize()
|
118
|
+
|
119
|
+
if not self.game.players.alive().select(Role.WITCH):
|
120
|
+
await anyio.sleep(5 + secrets.randbelow(15))
|
121
|
+
|
122
|
+
|
123
|
+
class WerewolfNotifyProvider(NotifyProvider["Werewolf"]):
|
124
|
+
@override
|
125
|
+
def message(self, message: UniMessage) -> UniMessage:
|
126
|
+
if (
|
127
|
+
partners := self.game.players.alive()
|
128
|
+
.select(RoleGroup.WEREWOLF)
|
129
|
+
.exclude(self.p)
|
130
|
+
):
|
131
|
+
message = message.text(
|
132
|
+
"\n🐺你的队友:\n\n"
|
133
|
+
+ "".join(f" {p.role_name}: {p.name}\n" for p in partners)
|
134
|
+
)
|
135
|
+
return message
|
136
|
+
|
137
|
+
|
138
|
+
class Werewolf(Player):
|
139
|
+
role = Role.WEREWOLF
|
140
|
+
role_group = RoleGroup.WEREWOLF
|
141
|
+
interact_provider = WerewolfInteractProvider
|
142
|
+
notify_provider = WerewolfNotifyProvider
|
143
|
+
|
144
|
+
@property
|
145
|
+
@override
|
146
|
+
def interact_timeout(self) -> float:
|
147
|
+
return self.game.behavior.timeout.werewolf
|
@@ -1,42 +1,52 @@
|
|
1
|
-
from nonebot_plugin_alconna.uniseg import UniMessage
|
2
1
|
from typing_extensions import override
|
3
2
|
|
4
|
-
from
|
3
|
+
from nonebot_plugin_alconna.uniseg import UniMessage
|
4
|
+
|
5
|
+
from ..constant import stop_command_prompt
|
5
6
|
from ..models import Role, RoleGroup
|
7
|
+
from ..player import InteractProvider, Player
|
6
8
|
from ..utils import as_player_set
|
7
|
-
from .player import Player
|
8
9
|
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
11
|
+
class WitchInteractProvider(InteractProvider["Witch"]):
|
12
|
+
antidote = InteractProvider.proxy(bool)
|
13
|
+
poison = InteractProvider.proxy(bool)
|
14
|
+
|
15
|
+
@override
|
16
|
+
async def before(self) -> None:
|
17
|
+
await self.p.send("ℹ️请等待狼人决定目标...")
|
18
|
+
await self.game.state.werewolf_finished.wait()
|
14
19
|
|
15
20
|
async def handle_killed(self) -> bool:
|
16
21
|
if (killed := self.game.state.killed) is None:
|
17
|
-
await self.send("ℹ️今晚没有人被刀")
|
22
|
+
await self.p.send("ℹ️今晚没有人被刀")
|
18
23
|
return False
|
19
24
|
|
20
25
|
msg = UniMessage.text(f"🔪今晚 {killed.name} 被刀了\n\n")
|
21
26
|
|
22
27
|
if not self.antidote:
|
23
|
-
await self.send(msg.text("⚙️你已经用过解药了"))
|
28
|
+
await self.p.send(msg.text("⚙️你已经用过解药了"))
|
24
29
|
return False
|
25
30
|
|
26
|
-
msg.text(f"✏️使用解药请发送 “1”\n❌不使用解药请发送 “{
|
27
|
-
await self.send(
|
31
|
+
msg.text(f"✏️使用解药请发送 “1”\n❌不使用解药请发送 “{stop_command_prompt()}”")
|
32
|
+
await self.p.send(
|
33
|
+
msg,
|
34
|
+
stop_btn_label="不使用解药",
|
35
|
+
select_players=as_player_set(killed),
|
36
|
+
)
|
28
37
|
|
29
|
-
if not await self.
|
38
|
+
if not await self.p.select_player(
|
30
39
|
as_player_set(killed),
|
31
40
|
on_stop=f"ℹ️你选择不对 {killed.name} 使用解药",
|
32
|
-
on_index_error=f"⚠️输入错误: 请输入 “1” 或 “{
|
41
|
+
on_index_error=f"⚠️输入错误: 请输入 “1” 或 “{stop_command_prompt()}”",
|
42
|
+
stop_btn_label="不使用解药",
|
33
43
|
):
|
34
44
|
return False
|
35
45
|
|
36
46
|
self.antidote = False
|
37
47
|
self.selected = killed
|
38
48
|
self.game.state.antidote.add(killed)
|
39
|
-
await self.send(f"✅你对 {killed.name} 使用了解药,回合结束")
|
49
|
+
await self.p.send(f"✅你对 {killed.name} 使用了解药,回合结束")
|
40
50
|
return True
|
41
51
|
|
42
52
|
@override
|
@@ -45,23 +55,37 @@ class Witch(Player):
|
|
45
55
|
return
|
46
56
|
|
47
57
|
if not self.poison:
|
48
|
-
await self.send("⚙️你没有可以使用的药水,回合结束")
|
58
|
+
await self.p.send("⚙️你没有可以使用的药水,回合结束")
|
49
59
|
return
|
50
60
|
|
51
61
|
players = self.game.players.alive()
|
52
|
-
await self.send(
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
62
|
+
await self.p.send(
|
63
|
+
"💫你有一瓶毒药\n"
|
64
|
+
"玩家列表:\n"
|
65
|
+
f"{players.show()}\n\n"
|
66
|
+
"🧪发送玩家编号使用毒药\n"
|
67
|
+
f"❌发送 “{stop_command_prompt()}” 结束回合(不使用药水)",
|
68
|
+
stop_btn_label="结束回合",
|
69
|
+
select_players=players,
|
58
70
|
)
|
59
71
|
|
60
|
-
if selected := await self.
|
72
|
+
if selected := await self.p.select_player(
|
61
73
|
players,
|
62
74
|
on_stop="ℹ️你选择不使用毒药,回合结束",
|
75
|
+
stop_btn_label="结束回合",
|
63
76
|
):
|
64
77
|
self.poison = False
|
65
78
|
self.selected = selected
|
66
|
-
self.game.state.poison.add(self)
|
67
|
-
await self.send(
|
79
|
+
self.game.state.poison.add(self.p)
|
80
|
+
await self.p.send(
|
81
|
+
f"✅当前回合选择对玩家 {selected.name} 使用毒药\n回合结束"
|
82
|
+
)
|
83
|
+
|
84
|
+
|
85
|
+
class Witch(Player):
|
86
|
+
role = Role.WITCH
|
87
|
+
role_group = RoleGroup.GOODGUY
|
88
|
+
interact_provider = WitchInteractProvider
|
89
|
+
|
90
|
+
antidote: bool = True
|
91
|
+
poison: bool = True
|
@@ -1,14 +1,20 @@
|
|
1
1
|
from typing_extensions import override
|
2
2
|
|
3
|
+
from nonebot_plugin_alconna import UniMessage
|
4
|
+
|
3
5
|
from ..models import Role, RoleGroup
|
4
|
-
from .
|
5
|
-
from .
|
6
|
-
from .werewolf import Werewolf
|
6
|
+
from .shooter import ShooterKillProvider
|
7
|
+
from .werewolf import Werewolf, WerewolfNotifyProvider
|
7
8
|
|
8
9
|
|
9
|
-
|
10
|
-
class WolfKing(CanShoot, Werewolf):
|
10
|
+
class WolfKingNotifyProvider(WerewolfNotifyProvider):
|
11
11
|
@override
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
def message(self, message: UniMessage) -> UniMessage:
|
13
|
+
return super().message(message).text("⚙️作为狼王,你可以在死后射杀一名玩家")
|
14
|
+
|
15
|
+
|
16
|
+
class WolfKing(Werewolf):
|
17
|
+
role = Role.WOLFKING
|
18
|
+
role_group = RoleGroup.WEREWOLF
|
19
|
+
kill_provider = ShooterKillProvider
|
20
|
+
notify_provider = WolfKingNotifyProvider
|
nonebot_plugin_werewolf/utils.py
CHANGED
@@ -1,20 +1,33 @@
|
|
1
|
+
import abc
|
1
2
|
import functools
|
2
3
|
import itertools
|
3
4
|
from collections import defaultdict
|
4
|
-
from
|
5
|
+
from collections.abc import Iterable
|
6
|
+
from typing import TYPE_CHECKING, Any, ClassVar, Generic, ParamSpec, TypeVar
|
5
7
|
|
6
8
|
import anyio
|
7
9
|
import anyio.streams.memory
|
8
|
-
from
|
10
|
+
from nonebot.adapters import Bot, Event
|
11
|
+
from nonebot.internal.matcher import current_bot
|
12
|
+
from nonebot_plugin_alconna.uniseg import (
|
13
|
+
Button,
|
14
|
+
FallbackStrategy,
|
15
|
+
Keyboard,
|
16
|
+
Receipt,
|
17
|
+
Target,
|
18
|
+
UniMessage,
|
19
|
+
)
|
9
20
|
from nonebot_plugin_uninfo import Session
|
10
21
|
|
11
|
-
from .
|
22
|
+
from .config import config
|
23
|
+
from .constant import STOP_COMMAND, stop_command_prompt
|
12
24
|
|
13
25
|
if TYPE_CHECKING:
|
26
|
+
from .player import Player
|
14
27
|
from .player_set import PlayerSet
|
15
|
-
from .players import Player
|
16
28
|
|
17
29
|
T = TypeVar("T")
|
30
|
+
P = ParamSpec("P")
|
18
31
|
|
19
32
|
|
20
33
|
def check_index(text: str, arrlen: int) -> int | None:
|
@@ -85,7 +98,7 @@ class InputStore:
|
|
85
98
|
task.set(msg)
|
86
99
|
|
87
100
|
@classmethod
|
88
|
-
def cleanup(cls, players:
|
101
|
+
def cleanup(cls, players: Iterable[str], group_id: str) -> None:
|
89
102
|
for p, g in itertools.product(players, (group_id, None)):
|
90
103
|
key = cls._key(p, g)
|
91
104
|
if key in cls.locks:
|
@@ -106,7 +119,10 @@ def as_player_set(*player: "Player") -> "PlayerSet":
|
|
106
119
|
|
107
120
|
|
108
121
|
class ObjectStream(Generic[T]):
|
109
|
-
|
122
|
+
class Unset: ...
|
123
|
+
|
124
|
+
__UNSET: ClassVar[Unset] = Unset()
|
125
|
+
|
110
126
|
_send: anyio.streams.memory.MemoryObjectSendStream[T]
|
111
127
|
_recv: anyio.streams.memory.MemoryObjectReceiveStream[T]
|
112
128
|
_closed: anyio.Event
|
@@ -119,7 +135,7 @@ class ObjectStream(Generic[T]):
|
|
119
135
|
await self._send.send(obj)
|
120
136
|
|
121
137
|
async def recv(self) -> T:
|
122
|
-
result = self.
|
138
|
+
result: Any = self.__UNSET
|
123
139
|
|
124
140
|
async def _recv() -> None:
|
125
141
|
nonlocal result
|
@@ -134,7 +150,7 @@ class ObjectStream(Generic[T]):
|
|
134
150
|
tg.start_soon(_recv)
|
135
151
|
tg.start_soon(_cancel)
|
136
152
|
|
137
|
-
if result is self.
|
153
|
+
if result is self.__UNSET:
|
138
154
|
raise anyio.EndOfStream
|
139
155
|
|
140
156
|
return result
|
@@ -148,3 +164,86 @@ class ObjectStream(Generic[T]):
|
|
148
164
|
|
149
165
|
async def wait_closed(self) -> None:
|
150
166
|
await self._closed.wait()
|
167
|
+
|
168
|
+
|
169
|
+
def _btn(label: str, text: str, /) -> Button:
|
170
|
+
return Button(flag="input", label=label, text=text)
|
171
|
+
|
172
|
+
|
173
|
+
def add_stop_button(msg: str | UniMessage, label: str | None = None) -> UniMessage:
|
174
|
+
if isinstance(msg, str):
|
175
|
+
msg = UniMessage.text(msg)
|
176
|
+
|
177
|
+
stop = stop_command_prompt()
|
178
|
+
return msg.keyboard(_btn(label or stop, stop))
|
179
|
+
|
180
|
+
|
181
|
+
def add_players_button(msg: str | UniMessage, players: "PlayerSet") -> UniMessage:
|
182
|
+
if isinstance(msg, str):
|
183
|
+
msg = UniMessage.text(msg)
|
184
|
+
|
185
|
+
it = enumerate(players, 1)
|
186
|
+
while line := tuple(itertools.islice(it, 3)):
|
187
|
+
msg.keyboard(*(_btn(p.name, str(i)) for i, p in line))
|
188
|
+
return msg
|
189
|
+
|
190
|
+
|
191
|
+
class SendHandler(abc.ABC, Generic[P]):
|
192
|
+
bot: Bot
|
193
|
+
target: Event | Target
|
194
|
+
reply_to: bool | None = None
|
195
|
+
last_msg: UniMessage | None = None
|
196
|
+
last_receipt: Receipt | None = None
|
197
|
+
|
198
|
+
def update(self, target: Event | Target, bot: Bot | None = None) -> None:
|
199
|
+
self.bot = bot or current_bot.get()
|
200
|
+
self.target = target
|
201
|
+
|
202
|
+
async def _edit(self) -> None:
|
203
|
+
last = self.last_receipt
|
204
|
+
if (
|
205
|
+
config.enable_button
|
206
|
+
and self.last_msg is not None
|
207
|
+
and last is not None
|
208
|
+
and last.editable
|
209
|
+
):
|
210
|
+
await last.edit(self.last_msg.exclude(Keyboard))
|
211
|
+
|
212
|
+
async def _send(self, message: UniMessage) -> None:
|
213
|
+
if not config.enable_button:
|
214
|
+
message = message.exclude(Keyboard)
|
215
|
+
receipt = await message.send(
|
216
|
+
target=self.target,
|
217
|
+
bot=self.bot,
|
218
|
+
reply_to=self.reply_to,
|
219
|
+
fallback=FallbackStrategy.ignore,
|
220
|
+
)
|
221
|
+
self.last_msg = message
|
222
|
+
self.last_receipt = receipt
|
223
|
+
|
224
|
+
@abc.abstractmethod
|
225
|
+
def solve_msg(
|
226
|
+
self,
|
227
|
+
msg: UniMessage,
|
228
|
+
*args: P.args,
|
229
|
+
**kwargs: P.kwargs,
|
230
|
+
) -> UniMessage:
|
231
|
+
raise NotImplementedError
|
232
|
+
|
233
|
+
async def send(
|
234
|
+
self,
|
235
|
+
msg: str | UniMessage,
|
236
|
+
*args: P.args,
|
237
|
+
**kwargs: P.kwargs,
|
238
|
+
) -> Receipt:
|
239
|
+
msg = UniMessage.text(msg) if isinstance(msg, str) else msg
|
240
|
+
msg = self.solve_msg(msg, *args, **kwargs)
|
241
|
+
|
242
|
+
async with anyio.create_task_group() as tg:
|
243
|
+
tg.start_soon(self._edit)
|
244
|
+
tg.start_soon(self._send, msg)
|
245
|
+
|
246
|
+
if TYPE_CHECKING:
|
247
|
+
assert self.last_receipt is not None
|
248
|
+
|
249
|
+
return self.last_receipt
|