nonebot-plugin-werewolf 1.1.11__py3-none-any.whl → 1.1.12__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.
Files changed (26) hide show
  1. nonebot_plugin_werewolf/__init__.py +1 -1
  2. nonebot_plugin_werewolf/config.py +36 -35
  3. nonebot_plugin_werewolf/constant.py +0 -17
  4. nonebot_plugin_werewolf/dead_channel.py +79 -0
  5. nonebot_plugin_werewolf/game.py +46 -115
  6. nonebot_plugin_werewolf/matchers/_prepare_game.py +223 -0
  7. nonebot_plugin_werewolf/matchers/depends.py +2 -2
  8. nonebot_plugin_werewolf/matchers/edit_preset.py +13 -12
  9. nonebot_plugin_werewolf/matchers/poke/chronocat_poke.py +3 -3
  10. nonebot_plugin_werewolf/matchers/poke/ob11_poke.py +3 -3
  11. nonebot_plugin_werewolf/matchers/start_game.py +19 -232
  12. nonebot_plugin_werewolf/matchers/superuser_ops.py +4 -8
  13. nonebot_plugin_werewolf/models.py +19 -0
  14. nonebot_plugin_werewolf/player.py +35 -31
  15. nonebot_plugin_werewolf/players/guard.py +2 -2
  16. nonebot_plugin_werewolf/players/prophet.py +2 -2
  17. nonebot_plugin_werewolf/players/shooter.py +2 -2
  18. nonebot_plugin_werewolf/players/werewolf.py +18 -19
  19. nonebot_plugin_werewolf/players/witch.py +4 -4
  20. nonebot_plugin_werewolf/utils.py +18 -56
  21. {nonebot_plugin_werewolf-1.1.11.dist-info → nonebot_plugin_werewolf-1.1.12.dist-info}/METADATA +55 -34
  22. nonebot_plugin_werewolf-1.1.12.dist-info/RECORD +37 -0
  23. {nonebot_plugin_werewolf-1.1.11.dist-info → nonebot_plugin_werewolf-1.1.12.dist-info}/WHEEL +1 -1
  24. nonebot_plugin_werewolf-1.1.11.dist-info/RECORD +0 -35
  25. {nonebot_plugin_werewolf-1.1.11.dist-info → nonebot_plugin_werewolf-1.1.12.dist-info}/licenses/LICENSE +0 -0
  26. {nonebot_plugin_werewolf-1.1.11.dist-info → nonebot_plugin_werewolf-1.1.12.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,5 @@
1
1
  import dataclasses
2
+ import functools
2
3
  from enum import Enum, auto
3
4
  from typing import TYPE_CHECKING
4
5
 
@@ -26,12 +27,30 @@ class Role(int, Enum):
26
27
  # 平民
27
28
  CIVILIAN = 0
28
29
 
30
+ @functools.cached_property
31
+ def emoji(self) -> str:
32
+ from .constant import ROLE_EMOJI
33
+
34
+ return ROLE_EMOJI[self]
35
+
36
+ @functools.cached_property
37
+ def display(self) -> str:
38
+ from .constant import ROLE_NAME_CONV
39
+
40
+ return ROLE_NAME_CONV[self]
41
+
29
42
 
30
43
  class RoleGroup(Enum):
31
44
  WEREWOLF = auto()
32
45
  GOODGUY = auto()
33
46
  OTHERS = auto()
34
47
 
48
+ @functools.cached_property
49
+ def display(self) -> str:
50
+ from .constant import ROLE_NAME_CONV
51
+
52
+ return ROLE_NAME_CONV[self]
53
+
35
54
 
36
55
  class KillReason(Enum):
37
56
  WEREWOLF = auto()
@@ -9,9 +9,10 @@ import nonebot
9
9
  from nonebot.adapters import Bot
10
10
  from nonebot.utils import escape_tag
11
11
  from nonebot_plugin_alconna.uniseg import Receipt, Target, UniMessage
12
- from nonebot_plugin_uninfo import Interface, SceneType
12
+ from nonebot_plugin_uninfo import Interface, SceneType, get_interface
13
13
 
14
- from .constant import ROLE_EMOJI, ROLE_NAME_CONV, STOP_COMMAND, stop_command_prompt
14
+ from .config import stop_command_prompt
15
+ from .constant import STOP_COMMAND
15
16
  from .models import KillInfo, KillReason, Role, RoleGroup
16
17
  from .utils import (
17
18
  InputStore,
@@ -92,7 +93,7 @@ class NotifyProvider(ActionProvider[_P], Generic[_P]):
92
93
  return message
93
94
 
94
95
  async def notify(self) -> None:
95
- msg = UniMessage.text(f"⚙️你的身份: {ROLE_EMOJI[self.role]}{self.role_name}\n")
96
+ msg = UniMessage.text(f"⚙️你的身份: {self.role.emoji}{self.role_name}\n")
96
97
  await self.p.send(self.message(msg))
97
98
 
98
99
 
@@ -135,14 +136,18 @@ class Player:
135
136
  @override
136
137
  def __init_subclass__(cls) -> None:
137
138
  super().__init_subclass__()
138
- if hasattr(cls, "role") and hasattr(cls, "role_group"):
139
- cls._player_class[cls.role] = cls
140
- if not hasattr(cls, "interact_provider"):
141
- cls.interact_provider = None
142
- if not hasattr(cls, "kill_provider"):
143
- cls.kill_provider = KillProvider
144
- if not hasattr(cls, "notify_provider"):
145
- cls.notify_provider = NotifyProvider
139
+ if not (hasattr(cls, "role") and hasattr(cls, "role_group")):
140
+ return
141
+
142
+ assert cls.role not in cls._player_class # noqa: S101
143
+ cls._player_class[cls.role] = cls
144
+ for k, v in {
145
+ "interact_provider": None,
146
+ "kill_provider": KillProvider,
147
+ "notify_provider": NotifyProvider,
148
+ }.items():
149
+ if not hasattr(cls, k):
150
+ setattr(cls, k, v)
146
151
 
147
152
  @final
148
153
  @classmethod
@@ -152,7 +157,6 @@ class Player:
152
157
  bot: Bot,
153
158
  game: "Game",
154
159
  user_id: str,
155
- interface: Interface,
156
160
  ) -> "Player":
157
161
  if role not in cls._player_class:
158
162
  raise ValueError(f"Unexpected role: {role!r}")
@@ -166,7 +170,9 @@ class Player:
166
170
  extra=game.group.extra,
167
171
  )
168
172
  self = cls._player_class[role](bot, game, user)
169
- await self._fetch_member(interface)
173
+
174
+ if interface := get_interface(bot):
175
+ await self._fetch_member(interface)
170
176
  return self
171
177
 
172
178
  def __repr__(self) -> str:
@@ -187,7 +193,7 @@ class Player:
187
193
  @final
188
194
  @functools.cached_property
189
195
  def role_name(self) -> str:
190
- return ROLE_NAME_CONV[self.role]
196
+ return self.role.display
191
197
 
192
198
  @final
193
199
  async def _fetch_member(self, interface: Interface) -> None:
@@ -278,14 +284,12 @@ class Player:
278
284
  provider = self.interact_provider(self)
279
285
 
280
286
  await provider.before()
281
-
282
287
  timeout = self.interact_timeout
283
288
  await self.send(f"✏️{self.role_name}交互开始,限时 {timeout / 60:.2f} 分钟")
284
289
 
285
- try:
286
- with anyio.fail_after(timeout):
287
- await provider.interact()
288
- except TimeoutError:
290
+ with anyio.move_on_after(timeout) as scope:
291
+ await provider.interact()
292
+ if scope.cancelled_caught:
289
293
  logger.debug(f"{self.role_name}交互超时 (<y>{timeout}</y>s)")
290
294
  await self.send(f"⚠️{self.role_name}交互超时")
291
295
 
@@ -304,25 +308,25 @@ class Player:
304
308
  self.killed.set()
305
309
 
306
310
  async def vote(self, players: "PlayerSet") -> "Player | None":
311
+ vote_timeout = self.game.behavior.timeout.vote
307
312
  await self.send(
308
313
  f"💫请选择需要投票的玩家:\n"
309
314
  f"{players.show()}\n\n"
310
315
  "🗳️发送编号选择玩家\n"
311
- f"❌发送 “{stop_command_prompt()}” 弃票\n\n"
312
- "限时1分钟,超时将视为弃票",
316
+ f"❌发送 “{stop_command_prompt}” 弃票\n\n"
317
+ f"限时{vote_timeout / 60:.1f}分钟,超时将视为弃票",
313
318
  stop_btn_label="弃票",
314
319
  select_players=players,
315
320
  )
316
321
 
317
- try:
318
- with anyio.fail_after(self.game.behavior.timeout.vote):
319
- selected = await self.select_player(
320
- players,
321
- on_stop="⚠️你选择了弃票",
322
- on_index_error="⚠️输入错误: 请发送编号选择玩家",
323
- )
324
- except TimeoutError:
325
- selected = None
322
+ selected = None
323
+ with anyio.move_on_after(vote_timeout) as scope:
324
+ selected = await self.select_player(
325
+ players,
326
+ on_stop="⚠️你选择了弃票",
327
+ on_index_error="⚠️输入错误: 请发送编号选择玩家",
328
+ )
329
+ if scope.cancelled_caught:
326
330
  await self.send("⚠️投票超时,将视为弃票")
327
331
 
328
332
  if selected is not None:
@@ -343,7 +347,7 @@ class Player:
343
347
  ) -> "Player | None":
344
348
  on_stop = on_stop if on_stop is not None else "ℹ️你选择了取消,回合结束"
345
349
  on_index_error = (
346
- on_index_error or f"⚠️输入错误: 请发送玩家编号或 “{stop_command_prompt()}”"
350
+ on_index_error or f"⚠️输入错误: 请发送玩家编号或 “{stop_command_prompt}”"
347
351
  )
348
352
  selected = None
349
353
 
@@ -1,6 +1,6 @@
1
1
  from typing_extensions import override
2
2
 
3
- from ..constant import stop_command_prompt
3
+ from ..config import stop_command_prompt
4
4
  from ..models import GameState, Role, RoleGroup
5
5
  from ..player import InteractProvider, Player
6
6
 
@@ -13,7 +13,7 @@ class GuardInteractProvider(InteractProvider["Guard"]):
13
13
  "💫请选择需要保护的玩家:\n"
14
14
  f"{players.show()}\n\n"
15
15
  "🛡️发送编号选择玩家\n"
16
- f"❌发送 “{stop_command_prompt()}” 结束回合",
16
+ f"❌发送 “{stop_command_prompt}” 结束回合",
17
17
  stop_btn_label="结束回合",
18
18
  select_players=players,
19
19
  )
@@ -1,6 +1,6 @@
1
1
  from typing_extensions import override
2
2
 
3
- from ..constant import stop_command_prompt
3
+ from ..config import stop_command_prompt
4
4
  from ..models import Role, RoleGroup
5
5
  from ..player import InteractProvider, Player
6
6
 
@@ -13,7 +13,7 @@ class ProphetInteractProvider(InteractProvider["Prophet"]):
13
13
  "💫请选择需要查验身份的玩家:\n"
14
14
  f"{players.show()}\n\n"
15
15
  "🔮发送编号选择玩家\n"
16
- f"❌发送 “{stop_command_prompt()}” 结束回合(不查验身份)",
16
+ f"❌发送 “{stop_command_prompt}” 结束回合(不查验身份)",
17
17
  stop_btn_label="结束回合",
18
18
  select_players=players,
19
19
  )
@@ -2,7 +2,7 @@ from typing_extensions import override
2
2
 
3
3
  from nonebot_plugin_alconna.uniseg import UniMessage
4
4
 
5
- from ..constant import stop_command_prompt
5
+ from ..config import stop_command_prompt
6
6
  from ..models import KillReason
7
7
  from ..player import KillProvider, Player
8
8
 
@@ -39,7 +39,7 @@ class ShooterKillProvider(KillProvider["Player"]):
39
39
  "💫请选择需要射杀的玩家:\n"
40
40
  f"{players.show()}\n\n"
41
41
  "🔫发送编号选择玩家\n"
42
- f"❌发送 “{stop_command_prompt()}” 取消技能",
42
+ f"❌发送 “{stop_command_prompt}” 取消技能",
43
43
  stop_btn_label="取消技能",
44
44
  select_players=players,
45
45
  )
@@ -5,23 +5,23 @@ from typing_extensions import override
5
5
  import anyio
6
6
  from nonebot_plugin_alconna.uniseg import UniMessage
7
7
 
8
- from ..constant import STOP_COMMAND, stop_command_prompt
8
+ from ..config import stop_command_prompt
9
+ from ..constant import STOP_COMMAND
9
10
  from ..models import Role, RoleGroup
10
11
  from ..player import InteractProvider, NotifyProvider, Player
11
- from ..utils import ObjectStream, as_player_set, check_index
12
+ from ..utils import as_player_set, check_index
12
13
 
13
14
  if TYPE_CHECKING:
14
15
  from ..player_set import PlayerSet
15
16
 
16
17
 
17
18
  class WerewolfInteractProvider(InteractProvider["Werewolf"]):
18
- stream: ObjectStream[str | UniMessage]
19
-
20
19
  @override
21
20
  async def before(self) -> None:
22
21
  self.game.state.werewolf_start()
23
22
 
24
23
  async def handle_interact(self, players: "PlayerSet") -> None:
24
+ stream = self.stream[0]
25
25
  self.selected = None
26
26
 
27
27
  while True:
@@ -32,30 +32,31 @@ class WerewolfInteractProvider(InteractProvider["Werewolf"]):
32
32
  self.selected = players[index - 1]
33
33
  msg = f"当前选择玩家: {self.selected.name}"
34
34
  await self.p.send(
35
- f"🎯{msg}\n发送 “{stop_command_prompt()}” 结束回合",
35
+ f"🎯{msg}\n发送 “{stop_command_prompt}” 结束回合",
36
36
  stop_btn_label="结束回合",
37
37
  select_players=players,
38
38
  )
39
- await self.stream.send(f"📝队友 {self.p.name} {msg}")
39
+ await stream.send(f"📝队友 {self.p.name} {msg}")
40
40
  if text == STOP_COMMAND:
41
41
  if self.selected is not None:
42
42
  await self.p.send("✅你已结束当前回合")
43
- await self.stream.send(f"📝队友 {self.p.name} 结束当前回合")
44
- self.stream.close()
43
+ await stream.send(f"📝队友 {self.p.name} 结束当前回合")
44
+ stream.close()
45
45
  return
46
46
  await self.p.send(
47
47
  "⚠️当前未选择玩家,无法结束回合",
48
48
  select_players=players,
49
49
  )
50
50
  else:
51
- await self.stream.send(
51
+ await stream.send(
52
52
  UniMessage.text(f"💬队友 {self.p.name}:\n") + input_msg
53
53
  )
54
54
 
55
55
  async def handle_broadcast(self, partners: "PlayerSet") -> None:
56
- while not self.stream.closed:
56
+ stream = self.stream[1]
57
+ while True:
57
58
  try:
58
- message = await self.stream.recv()
59
+ message = await stream.receive()
59
60
  except anyio.EndOfStream:
60
61
  return
61
62
 
@@ -77,19 +78,17 @@ class WerewolfInteractProvider(InteractProvider["Werewolf"]):
77
78
  msg.text("💫请选择今晚的目标:\n")
78
79
  .text(players.show())
79
80
  .text("\n\n🔪发送编号选择玩家")
80
- .text(f"\n❌发送 “{stop_command_prompt()}” 结束回合")
81
+ .text(f"\n❌发送 “{stop_command_prompt}” 结束回合")
81
82
  .text("\n\n⚠️意见未统一将空刀"),
82
83
  select_players=players,
83
84
  )
84
85
 
85
- self.stream = ObjectStream[str | UniMessage](8)
86
+ self.stream = anyio.create_memory_object_stream[str | UniMessage](8)
87
+ send, recv = self.stream
86
88
 
87
- try:
88
- async with anyio.create_task_group() as tg:
89
- tg.start_soon(self.handle_interact, players)
90
- tg.start_soon(self.handle_broadcast, partners)
91
- finally:
92
- del self.stream
89
+ async with send, recv, anyio.create_task_group() as tg:
90
+ tg.start_soon(self.handle_interact, players)
91
+ tg.start_soon(self.handle_broadcast, partners)
93
92
 
94
93
  async def finalize(self) -> None:
95
94
  w = self.game.players.alive().select(RoleGroup.WEREWOLF)
@@ -2,7 +2,7 @@ from typing_extensions import override
2
2
 
3
3
  from nonebot_plugin_alconna.uniseg import UniMessage
4
4
 
5
- from ..constant import stop_command_prompt
5
+ from ..config import stop_command_prompt
6
6
  from ..models import Role, RoleGroup
7
7
  from ..player import InteractProvider, Player
8
8
  from ..utils import as_player_set
@@ -28,7 +28,7 @@ class WitchInteractProvider(InteractProvider["Witch"]):
28
28
  await self.p.send(msg.text("⚙️你已经用过解药了"))
29
29
  return False
30
30
 
31
- msg.text(f"✏️使用解药请发送 “1”\n❌不使用解药请发送 “{stop_command_prompt()}”")
31
+ msg.text(f"✏️使用解药请发送 “1”\n❌不使用解药请发送 “{stop_command_prompt}”")
32
32
  await self.p.send(
33
33
  msg,
34
34
  stop_btn_label="不使用解药",
@@ -38,7 +38,7 @@ class WitchInteractProvider(InteractProvider["Witch"]):
38
38
  if not await self.p.select_player(
39
39
  as_player_set(killed),
40
40
  on_stop=f"ℹ️你选择不对 {killed.name} 使用解药",
41
- on_index_error=f"⚠️输入错误: 请输入 “1” 或 “{stop_command_prompt()}”",
41
+ on_index_error=f"⚠️输入错误: 请输入 “1” 或 “{stop_command_prompt}”",
42
42
  stop_btn_label="不使用解药",
43
43
  ):
44
44
  return False
@@ -64,7 +64,7 @@ class WitchInteractProvider(InteractProvider["Witch"]):
64
64
  "玩家列表:\n"
65
65
  f"{players.show()}\n\n"
66
66
  "🧪发送玩家编号使用毒药\n"
67
- f"❌发送 “{stop_command_prompt()}” 结束回合(不使用药水)",
67
+ f"❌发送 “{stop_command_prompt}” 结束回合(不使用药水)",
68
68
  stop_btn_label="结束回合",
69
69
  select_players=players,
70
70
  )
@@ -6,7 +6,6 @@ from collections.abc import Iterable
6
6
  from typing import TYPE_CHECKING, Any, ClassVar, Generic, ParamSpec, TypeVar
7
7
 
8
8
  import anyio
9
- import anyio.streams.memory
10
9
  from nonebot.adapters import Bot, Event
11
10
  from nonebot.internal.matcher import current_bot
12
11
  from nonebot_plugin_alconna.uniseg import (
@@ -19,8 +18,8 @@ from nonebot_plugin_alconna.uniseg import (
19
18
  )
20
19
  from nonebot_plugin_uninfo import Session
21
20
 
22
- from .config import config
23
- from .constant import STOP_COMMAND, stop_command_prompt
21
+ from .config import config, stop_command_prompt
22
+ from .constant import STOP_COMMAND
24
23
 
25
24
  if TYPE_CHECKING:
26
25
  from .player import Player
@@ -118,55 +117,7 @@ def as_player_set(*player: "Player") -> "PlayerSet":
118
117
  return cached_player_set()(player)
119
118
 
120
119
 
121
- class ObjectStream(Generic[T]):
122
- class Unset: ...
123
-
124
- __UNSET: ClassVar[Unset] = Unset()
125
-
126
- _send: anyio.streams.memory.MemoryObjectSendStream[T]
127
- _recv: anyio.streams.memory.MemoryObjectReceiveStream[T]
128
- _closed: anyio.Event
129
-
130
- def __init__(self, max_buffer_size: float = 0) -> None:
131
- self._send, self._recv = anyio.create_memory_object_stream(max_buffer_size)
132
- self._closed = anyio.Event()
133
-
134
- async def send(self, obj: T) -> None:
135
- await self._send.send(obj)
136
-
137
- async def recv(self) -> T:
138
- result: Any = self.__UNSET
139
-
140
- async def _recv() -> None:
141
- nonlocal result
142
- result = await self._recv.receive()
143
- tg.cancel_scope.cancel()
144
-
145
- async def _cancel() -> None:
146
- await self._closed.wait()
147
- tg.cancel_scope.cancel()
148
-
149
- async with anyio.create_task_group() as tg:
150
- tg.start_soon(_recv)
151
- tg.start_soon(_cancel)
152
-
153
- if result is self.__UNSET:
154
- raise anyio.EndOfStream
155
-
156
- return result
157
-
158
- def close(self) -> None:
159
- self._closed.set()
160
-
161
- @property
162
- def closed(self) -> bool:
163
- return self._closed.is_set()
164
-
165
- async def wait_closed(self) -> None:
166
- await self._closed.wait()
167
-
168
-
169
- def _btn(label: str, text: str, /) -> Button:
120
+ def btn(label: str, text: str, /) -> Button:
170
121
  return Button(flag="input", label=label, text=text)
171
122
 
172
123
 
@@ -174,8 +125,8 @@ def add_stop_button(msg: str | UniMessage, label: str | None = None) -> UniMessa
174
125
  if isinstance(msg, str):
175
126
  msg = UniMessage.text(msg)
176
127
 
177
- stop = stop_command_prompt()
178
- return msg.keyboard(_btn(label or stop, stop))
128
+ stop = stop_command_prompt
129
+ return msg.keyboard(btn(label or stop, stop))
179
130
 
180
131
 
181
132
  def add_players_button(msg: str | UniMessage, players: "PlayerSet") -> UniMessage:
@@ -184,7 +135,7 @@ def add_players_button(msg: str | UniMessage, players: "PlayerSet") -> UniMessag
184
135
 
185
136
  it = enumerate(players, 1)
186
137
  while line := tuple(itertools.islice(it, 3)):
187
- msg.keyboard(*(_btn(p.name, str(i)) for i, p in line))
138
+ msg.keyboard(*(btn(p.name, str(i)) for i, p in line))
188
139
  return msg
189
140
 
190
141
 
@@ -207,6 +158,15 @@ class SendHandler(abc.ABC, Generic[P]):
207
158
  self.bot = bot or current_bot.get()
208
159
  self.target = target
209
160
 
161
+ @functools.cached_property
162
+ def _is_dc(self) -> bool:
163
+ try:
164
+ from nonebot.adapters.discord import Bot
165
+ except ImportError:
166
+ return False
167
+
168
+ return isinstance(self.bot, Bot)
169
+
210
170
  async def _edit(self) -> None:
211
171
  last = self.last_receipt
212
172
  if (
@@ -214,6 +174,7 @@ class SendHandler(abc.ABC, Generic[P]):
214
174
  and self.last_msg is not None
215
175
  and last is not None
216
176
  and last.editable
177
+ and not self._is_dc
217
178
  ):
218
179
  await last.edit(self.last_msg.exclude(Keyboard))
219
180
 
@@ -221,7 +182,8 @@ class SendHandler(abc.ABC, Generic[P]):
221
182
  if self.target is None:
222
183
  raise RuntimeError("Target cannot be None when sending a message.")
223
184
 
224
- if not config.enable_button:
185
+ if not config.enable_button or self._is_dc:
186
+ # TODO: support discord button
225
187
  message = message.exclude(Keyboard)
226
188
  receipt = await message.send(
227
189
  target=self.target,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nonebot-plugin-werewolf
3
- Version: 1.1.11
3
+ Version: 1.1.12
4
4
  Summary: 适用于 Nonebot2 的狼人杀插件
5
5
  Author-email: wyf7685 <wyf7685@163.com>
6
6
  License: MIT
@@ -11,9 +11,9 @@ Requires-Python: >=3.10
11
11
  Description-Content-Type: text/markdown
12
12
  License-File: LICENSE
13
13
  Requires-Dist: nonebot2>=2.4.0
14
- Requires-Dist: nonebot-plugin-alconna>=0.57.0
14
+ Requires-Dist: nonebot-plugin-alconna>=0.58.0
15
15
  Requires-Dist: nonebot-plugin-localstore>=0.7.1
16
- Requires-Dist: nonebot-plugin-uninfo>=0.7.3
16
+ Requires-Dist: nonebot-plugin-uninfo>=0.8.0
17
17
  Requires-Dist: nonebot-plugin-waiter>=0.8.0
18
18
  Requires-Dist: anyio>=4.6.0
19
19
  Dynamic: license-file
@@ -50,6 +50,21 @@ _✨ 简单的狼人杀插件 ✨_
50
50
 
51
51
  和朋友们来一场紧张刺激的狼人杀游戏
52
52
 
53
+ <!-- ref: https://github.com/KomoriDev/Starify -->
54
+
55
+ > [!IMPORTANT]
56
+ > **收藏项目**,你将从 GitHub 上无延迟地接收所有发布通知~⭐️
57
+
58
+ <img width="100%" src="https://starify.komoridevs.icu/api/starify?owner=wyf7685&repo=nonebot-plugin-werewolf" alt="starify" />
59
+
60
+ <details>
61
+ <summary><kbd>Star History</kbd></summary>
62
+ <picture>
63
+ <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=wyf7685/nonebot-plugin-werewolf&theme=dark&type=Date" />
64
+ <img width="100%" src="https://star-history.com/#wyf7685/nonebot-plugin-werewolf&Date" />
65
+ </picture>
66
+ </details>
67
+
53
68
  ## 💿 安装
54
69
 
55
70
  > [!note]
@@ -121,7 +136,7 @@ _✨ 简单的狼人杀插件 ✨_
121
136
 
122
137
  `werewolf__enable_poke` 仅在 `OneBot V11` 适配器 / `Satori/chronocat` 下生效
123
138
 
124
- `werewolf__enable_button` 仅在 `Telegram` 适配器下通过测试,不保证在其他适配器的可用性。如有疑问欢迎提出。
139
+ `werewolf__enable_button` 仅在 `Telegram` 适配器下通过测试,不保证在其他适配器的可用性,如有疑问欢迎提出。
125
140
 
126
141
  <details>
127
142
  <summary> werewolf__require_at 示例 </summary>
@@ -136,15 +151,16 @@ werewolf__require_at=false
136
151
  # 狼人杀命令需要 at, 中止游戏命令不需要 at
137
152
  werewolf__require_at='{"start": true, "terminate": false}'
138
153
  ```
154
+
139
155
  </details>
140
- <br/>
141
156
 
142
157
  `werewolf__matcher_priority` 的 matcher 优先级参考 [官方文档](https://nonebot.dev/docs/advanced/matcher#%E5%93%8D%E5%BA%94%E4%BC%98%E5%85%88%E7%BA%A7)
143
- - 一般情况下不需要修改此配置, 插件的默认优先级可以参考 [这里](./nonebot_plugin_werewolf/config.py) 的 `MatcherPriorityConfig`
144
- - 如果遇到与其他插件的命令冲突, 可考虑修改此处的优先级配置
145
- - 配置应填入 JSON 对象, 可用键: `start` `terminate` `preset` `behavior` `in_game` `stop`
146
158
 
147
- ## 🎉 使用
159
+ - 一般情况下不需要修改此配置, 插件的默认优先级可以参考 [这里](./nonebot_plugin_werewolf/config.py) 的 `MatcherPriorityConfig`
160
+ - 如果遇到与其他插件的命令冲突, 可考虑修改此处的优先级配置
161
+ - 配置应填入 JSON 对象, 可用键: `start` `terminate` `preset` `behavior` `in_game` `stop`
162
+
163
+ ## 🚀 使用
148
164
 
149
165
  > [!note]
150
166
  >
@@ -165,19 +181,19 @@ werewolf__require_at='{"start": true, "terminate": false}'
165
181
 
166
182
  </details>
167
183
 
168
- ### 指令表
184
+ ### 📋 指令表
169
185
 
170
- | 指令 | 权限 | 需要@ | 范围 | 说明 |
171
- | :-----------------: | :-----------------: | :---: | :--: | :---------------------------------------: |
172
- | `werewolf`/`狼人杀` | 群员 | 是 | 群聊 | 发起游戏 (进入准备阶段) |
173
- | `开始游戏` | 游戏发起者 | 否 | 群聊 | _[准备阶段]_ 游戏发起者开始游戏 |
174
- | `结束游戏` | 游戏发起者/超级用户 | 否 | 群聊 | _[准备阶段]_ 游戏发起者/超级用户 结束游戏 |
175
- | `当前玩家` | 群员 | 否 | 群聊 | _[准备阶段]_ 列出参与游戏的玩家列表 |
176
- | `加入游戏` | 群员 | 否 | 群聊 | _[准备阶段]_ 玩家加入游戏 |
177
- | `退出游戏` | 群员 | 否 | 群聊 | _[准备阶段]_ 玩家退出游戏 |
178
- | `中止游戏` | 超级用户 | 是 | 群聊 | _[游戏内]_ 超级用户强制中止游戏 |
179
- | `狼人杀预设` | 超级用户 | 否 | 任意 | _[游戏外]_ 超级用户编辑游戏预设 |
180
- | `狼人杀配置` | 超级用户 | 否 | 任意 | _[游戏外]_ 超级用户编辑游戏配置 |
186
+ | 指令 | 权限 | 需要@ | 范围 | 说明 |
187
+ | :-----------------: | :-----------------: | :---: | :---------------: | :--------------------------: |
188
+ | `werewolf`/`狼人杀` | 群员 | 是 | 群聊 _[游戏外]_ | 发起游戏 (进入准备阶段) |
189
+ | `开始游戏` | 游戏发起者 | 否 | 群聊 _[准备阶段]_ | 游戏发起者开始游戏 |
190
+ | `结束游戏` | 游戏发起者/超级用户 | 否 | 群聊 _[准备阶段]_ | 游戏发起者/超级用户 结束游戏 |
191
+ | `当前玩家` | 群员 | 否 | 群聊 _[准备阶段]_ | 列出参与游戏的玩家列表 |
192
+ | `加入游戏` | 群员 | 否 | 群聊 _[准备阶段]_ | 玩家加入游戏 |
193
+ | `退出游戏` | 群员 | 否 | 群聊 _[准备阶段]_ | 玩家退出游戏 |
194
+ | `中止游戏` | 超级用户 | 是 | 群聊 _[游戏内]_ | 超级用户强制中止游戏 |
195
+ | `狼人杀预设` | 超级用户 | 否 | 任意 _[游戏外]_ | 超级用户编辑游戏预设 |
196
+ | `狼人杀配置` | 超级用户 | 否 | 任意 _[游戏外]_ | 超级用户编辑游戏配置 |
181
197
 
182
198
  - `超级用户` 为 nonebot2 配置项中的 `SUPERUSERS`, 配置说明参考 [官方文档](https://nonebot.dev/docs/appendices/config#superusers)
183
199
 
@@ -191,7 +207,7 @@ werewolf__require_at='{"start": true, "terminate": false}'
191
207
 
192
208
  - _其他交互参考游戏内提示_
193
209
 
194
- ### 游戏内容
210
+ ### 🎭 游戏内容
195
211
 
196
212
  > [!note]
197
213
  >
@@ -202,14 +218,14 @@ werewolf__require_at='{"start": true, "terminate": false}'
202
218
  插件中保存了一份 [职业预设](./nonebot_plugin_werewolf/constant.py), 内容如下
203
219
 
204
220
  | 总人数 | 狼人 | 神职 | 平民 |
205
- | ------ | ---- | ---- | ---- |
206
- | 6 | 1 | 2 | 3 |
207
- | 7 | 2 | 2 | 3 |
208
- | 8 | 2 | 3 | 3 |
209
- | 9 | 2 | 4 | 3 |
210
- | 10 | 3 | 4 | 3 |
211
- | 11 | 3 | 5 | 3 |
212
- | 12 | 4 | 5 | 3 |
221
+ | :----: | :--: | :--: | :--: |
222
+ | 6 | 1 | 2 | 3 |
223
+ | 7 | 2 | 2 | 3 |
224
+ | 8 | 2 | 3 | 3 |
225
+ | 9 | 2 | 4 | 3 |
226
+ | 10 | 3 | 4 | 3 |
227
+ | 11 | 3 | 5 | 3 |
228
+ | 12 | 4 | 5 | 3 |
213
229
 
214
230
  职业预设可以通过命令 `狼人杀预设 职业 ...` 修改
215
231
 
@@ -254,7 +270,6 @@ werewolf__require_at='{"start": true, "terminate": false}'
254
270
  > 其效果等同于以上描述中的单条命令 `狼人杀预设 狼人 狼人 狼王 狼人 狼人`
255
271
 
256
272
  </details>
257
- <br/>
258
273
 
259
274
  对于 `小丑` 职业,当预设中的平民数量大于或等于 2 时,将有 _一定概率_ 将其中一个平民替换为小丑。
260
275
 
@@ -262,12 +277,14 @@ werewolf__require_at='{"start": true, "terminate": false}'
262
277
 
263
278
  小丑生成概率可以通过命令 `狼人杀预设 小丑 <概率>` 设置,默认值为 0 (不生成小丑)。
264
279
 
265
- ### 已知问题
280
+ ### 🔧 已知问题
266
281
 
267
282
  <details>
268
283
  <summary>已知问题</summary>
269
284
 
270
- - 截止 chronocat v0.2.19, 调用 [`guild.member.get`](https://github.com/chrononeko/chronocat/blob/8558ad9ff4319395d86abbfda22136939bf66780/packages/engine-chronocat-api/src/api/guild/member/get.ts) / [`user.get`](https://github.com/chrononeko/chronocat/blob/8558ad9ff4319395d86abbfda22136939bf66780/packages/engine-chronocat-api/src/api/user/get.ts) 均无法获取用户名,这将导致在交互过程中的玩家名显示为用户 ID
285
+ - 截止 chronocat [v0.2.19](https://github.com/chrononeko/chronocat/tree/v0.2.19), 调用 [`guild.member.get`](https://github.com/chrononeko/chronocat/blob/8558ad9ff4319395d86abbfda22136939bf66780/packages/engine-chronocat-api/src/api/guild/member/get.ts) / [`user.get`](https://github.com/chrononeko/chronocat/blob/8558ad9ff4319395d86abbfda22136939bf66780/packages/engine-chronocat-api/src/api/user/get.ts) 均无法获取用户名,这将导致在交互过程中的玩家名显示为用户 ID
286
+
287
+ - v1.1.6 添加的按钮操作在 `discord` 适配器中不可用, 已在 v1.1.12 禁用 (2e31d43)
271
288
 
272
289
  </details>
273
290
 
@@ -278,6 +295,10 @@ werewolf__require_at='{"start": true, "terminate": false}'
278
295
 
279
296
  <!-- CHANGELOG -->
280
297
 
298
+ - 2025.06.01 v1.1.12
299
+
300
+ - 禁用 `discord` 适配器中的按钮操作 ~~以后会写适配的...吗?~~
301
+
281
302
  - 2025.04.20 v1.1.11
282
303
 
283
304
  - 添加配置项 `werewolf__require_at`, 用于配置命令是否需要 at 机器人触发
@@ -362,7 +383,7 @@ werewolf__require_at='{"start": true, "terminate": false}'
362
383
 
363
384
  </details>
364
385
 
365
- ## 鸣谢
386
+ ## 🎉 鸣谢
366
387
 
367
388
  - [`nonebot/nonebot2`](https://github.com/nonebot/nonebot2): 跨平台 Python 异步机器人框架
368
389
  - [`nonebot/plugin-alconna`](https://github.com/nonebot/plugin-alconna): 跨平台的消息处理接口