nonebot-plugin-shitbot 0.4.2__tar.gz → 0.5.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.
Files changed (47) hide show
  1. {nonebot_plugin_shitbot-0.4.2 → nonebot_plugin_shitbot-0.5.0}/PKG-INFO +1 -1
  2. {nonebot_plugin_shitbot-0.4.2 → nonebot_plugin_shitbot-0.5.0}/nonebot_plugin_shitbot/aux.py +1 -9
  3. {nonebot_plugin_shitbot-0.4.2 → nonebot_plugin_shitbot-0.5.0}/nonebot_plugin_shitbot/command.py +81 -0
  4. {nonebot_plugin_shitbot-0.4.2 → nonebot_plugin_shitbot-0.5.0}/nonebot_plugin_shitbot/commands/convert_cmd.py +11 -5
  5. {nonebot_plugin_shitbot-0.4.2 → nonebot_plugin_shitbot-0.5.0}/nonebot_plugin_shitbot/commands/help_cmd.py +14 -1
  6. {nonebot_plugin_shitbot-0.4.2 → nonebot_plugin_shitbot-0.5.0}/nonebot_plugin_shitbot/commands/session_cmd.py +17 -0
  7. {nonebot_plugin_shitbot-0.4.2 → nonebot_plugin_shitbot-0.5.0}/nonebot_plugin_shitbot/handlers.py +17 -8
  8. {nonebot_plugin_shitbot-0.4.2 → nonebot_plugin_shitbot-0.5.0}/nonebot_plugin_shitbot/help/session.md +2 -0
  9. {nonebot_plugin_shitbot-0.4.2 → nonebot_plugin_shitbot-0.5.0}/nonebot_plugin_shitbot.egg-info/PKG-INFO +1 -1
  10. {nonebot_plugin_shitbot-0.4.2 → nonebot_plugin_shitbot-0.5.0}/pyproject.toml +1 -1
  11. {nonebot_plugin_shitbot-0.4.2 → nonebot_plugin_shitbot-0.5.0}/LICENSE +0 -0
  12. {nonebot_plugin_shitbot-0.4.2 → nonebot_plugin_shitbot-0.5.0}/README.md +0 -0
  13. {nonebot_plugin_shitbot-0.4.2 → nonebot_plugin_shitbot-0.5.0}/nonebot_plugin_shitbot/__init__.py +0 -0
  14. {nonebot_plugin_shitbot-0.4.2 → nonebot_plugin_shitbot-0.5.0}/nonebot_plugin_shitbot/commands/__init__.py +0 -0
  15. {nonebot_plugin_shitbot-0.4.2 → nonebot_plugin_shitbot-0.5.0}/nonebot_plugin_shitbot/commands/advrandpic_cmd.py +0 -0
  16. {nonebot_plugin_shitbot-0.4.2 → nonebot_plugin_shitbot-0.5.0}/nonebot_plugin_shitbot/commands/autoreply_cmd.py +0 -0
  17. {nonebot_plugin_shitbot-0.4.2 → nonebot_plugin_shitbot-0.5.0}/nonebot_plugin_shitbot/commands/autoreply_main_cmd.py +0 -0
  18. {nonebot_plugin_shitbot-0.4.2 → nonebot_plugin_shitbot-0.5.0}/nonebot_plugin_shitbot/commands/md2pic_cmd.py +0 -0
  19. {nonebot_plugin_shitbot-0.4.2 → nonebot_plugin_shitbot-0.5.0}/nonebot_plugin_shitbot/commands/perm_cmd.py +0 -0
  20. {nonebot_plugin_shitbot-0.4.2 → nonebot_plugin_shitbot-0.5.0}/nonebot_plugin_shitbot/commands/pixiv_cmd.py +0 -0
  21. {nonebot_plugin_shitbot-0.4.2 → nonebot_plugin_shitbot-0.5.0}/nonebot_plugin_shitbot/commands/randpic_cmd.py +0 -0
  22. {nonebot_plugin_shitbot-0.4.2 → nonebot_plugin_shitbot-0.5.0}/nonebot_plugin_shitbot/commands/shitpost_cmd.py +0 -0
  23. {nonebot_plugin_shitbot-0.4.2 → nonebot_plugin_shitbot-0.5.0}/nonebot_plugin_shitbot/config.py +0 -0
  24. {nonebot_plugin_shitbot-0.4.2 → nonebot_plugin_shitbot-0.5.0}/nonebot_plugin_shitbot/css/github-markdown-dark-dimmed.css +0 -0
  25. {nonebot_plugin_shitbot-0.4.2 → nonebot_plugin_shitbot-0.5.0}/nonebot_plugin_shitbot/help/advrandpic.md +0 -0
  26. {nonebot_plugin_shitbot-0.4.2 → nonebot_plugin_shitbot-0.5.0}/nonebot_plugin_shitbot/help/autoreply.md +0 -0
  27. {nonebot_plugin_shitbot-0.4.2 → nonebot_plugin_shitbot-0.5.0}/nonebot_plugin_shitbot/help/convert.md +0 -0
  28. {nonebot_plugin_shitbot-0.4.2 → nonebot_plugin_shitbot-0.5.0}/nonebot_plugin_shitbot/help/help.md +0 -0
  29. {nonebot_plugin_shitbot-0.4.2 → nonebot_plugin_shitbot-0.5.0}/nonebot_plugin_shitbot/help/imgs/md2picex1.png +0 -0
  30. {nonebot_plugin_shitbot-0.4.2 → nonebot_plugin_shitbot-0.5.0}/nonebot_plugin_shitbot/help/imgs/md2picex2.png +0 -0
  31. {nonebot_plugin_shitbot-0.4.2 → nonebot_plugin_shitbot-0.5.0}/nonebot_plugin_shitbot/help/index.md +0 -0
  32. {nonebot_plugin_shitbot-0.4.2 → nonebot_plugin_shitbot-0.5.0}/nonebot_plugin_shitbot/help/md2pic.md +0 -0
  33. {nonebot_plugin_shitbot-0.4.2 → nonebot_plugin_shitbot-0.5.0}/nonebot_plugin_shitbot/help/perm.md +0 -0
  34. {nonebot_plugin_shitbot-0.4.2 → nonebot_plugin_shitbot-0.5.0}/nonebot_plugin_shitbot/help/pixiv.md +0 -0
  35. {nonebot_plugin_shitbot-0.4.2 → nonebot_plugin_shitbot-0.5.0}/nonebot_plugin_shitbot/help/randpic.md +0 -0
  36. {nonebot_plugin_shitbot-0.4.2 → nonebot_plugin_shitbot-0.5.0}/nonebot_plugin_shitbot/help/shitpost.md +0 -0
  37. {nonebot_plugin_shitbot-0.4.2 → nonebot_plugin_shitbot-0.5.0}/nonebot_plugin_shitbot/msgdatabase.py +0 -0
  38. {nonebot_plugin_shitbot-0.4.2 → nonebot_plugin_shitbot-0.5.0}/nonebot_plugin_shitbot/msgutils.py +0 -0
  39. {nonebot_plugin_shitbot-0.4.2 → nonebot_plugin_shitbot-0.5.0}/nonebot_plugin_shitbot/parser.py +0 -0
  40. {nonebot_plugin_shitbot-0.4.2 → nonebot_plugin_shitbot-0.5.0}/nonebot_plugin_shitbot/permissions.py +0 -0
  41. {nonebot_plugin_shitbot-0.4.2 → nonebot_plugin_shitbot-0.5.0}/nonebot_plugin_shitbot/session.py +0 -0
  42. {nonebot_plugin_shitbot-0.4.2 → nonebot_plugin_shitbot-0.5.0}/nonebot_plugin_shitbot/tasks.py +0 -0
  43. {nonebot_plugin_shitbot-0.4.2 → nonebot_plugin_shitbot-0.5.0}/nonebot_plugin_shitbot.egg-info/SOURCES.txt +0 -0
  44. {nonebot_plugin_shitbot-0.4.2 → nonebot_plugin_shitbot-0.5.0}/nonebot_plugin_shitbot.egg-info/dependency_links.txt +0 -0
  45. {nonebot_plugin_shitbot-0.4.2 → nonebot_plugin_shitbot-0.5.0}/nonebot_plugin_shitbot.egg-info/requires.txt +0 -0
  46. {nonebot_plugin_shitbot-0.4.2 → nonebot_plugin_shitbot-0.5.0}/nonebot_plugin_shitbot.egg-info/top_level.txt +0 -0
  47. {nonebot_plugin_shitbot-0.4.2 → nonebot_plugin_shitbot-0.5.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nonebot-plugin-shitbot
3
- Version: 0.4.2
3
+ Version: 0.5.0
4
4
  Summary: NoneBot shitpost plugin
5
5
  Requires-Python: >=3.10
6
6
  License-File: LICENSE
@@ -6,8 +6,6 @@ from typing import Any
6
6
  import httpx
7
7
  from nonebot.log import logger
8
8
 
9
- from .config import config
10
-
11
9
 
12
10
  def validate_schema(data: Any, schema: Any) -> bool:
13
11
  if schema is Any:
@@ -36,7 +34,7 @@ def validate_schema(data: Any, schema: Any) -> bool:
36
34
  return False
37
35
 
38
36
 
39
- async def rm_path(path: Path):
37
+ def rm_path(path: Path):
40
38
  if not path.exists():
41
39
  return
42
40
  if path.is_dir():
@@ -45,12 +43,6 @@ async def rm_path(path: Path):
45
43
  path.unlink()
46
44
 
47
45
 
48
- async def rm_cache(group_id: str, user_id: str, pid: str):
49
- cache_path = config.cache / group_id / user_id / pid
50
- await rm_path(cache_path)
51
- logger.info(f"已删除缓存: {cache_path}")
52
-
53
-
54
46
  def get_file_sha256(file_path: Path | str):
55
47
  file = Path(file_path) if isinstance(file_path, str) else file_path
56
48
  if not file.is_file():
@@ -1,9 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import asyncio
4
+ from functools import wraps
3
5
  from typing import TYPE_CHECKING
4
6
 
5
7
  from nonebot.adapters.onebot.v11 import Bot, Message, MessageEvent
6
8
 
9
+ from .aux import rm_path
10
+ from .config import config
7
11
  from .msgutils import send_msg
8
12
  from .parser import BotArgParser
9
13
  from .permissions import permissions
@@ -12,6 +16,40 @@ if TYPE_CHECKING:
12
16
  from .session import BotSession
13
17
 
14
18
 
19
+ def _cancellable_run(func):
20
+ @wraps(func)
21
+ async def wrapper(self, *args, **kwargs):
22
+ task = asyncio.current_task()
23
+ if task is not None:
24
+ self._run_tasks.add(task)
25
+ try:
26
+ return await func(self, *args, **kwargs)
27
+ except asyncio.CancelledError:
28
+ pass
29
+ finally:
30
+ if task is not None:
31
+ self._run_tasks.discard(task)
32
+
33
+ return wrapper
34
+
35
+
36
+ def _cancellable_roger(func):
37
+ @wraps(func)
38
+ async def wrapper(self, *args, **kwargs):
39
+ task = asyncio.current_task()
40
+ if task is not None:
41
+ self._roger_tasks.add(task)
42
+ try:
43
+ return await func(self, *args, **kwargs)
44
+ except asyncio.CancelledError:
45
+ pass
46
+ finally:
47
+ if task is not None:
48
+ self._roger_tasks.discard(task)
49
+
50
+ return wrapper
51
+
52
+
15
53
  # Base class of all the commands
16
54
  class BotCommand:
17
55
  # class fields:
@@ -27,6 +65,13 @@ class BotCommand:
27
65
  _sentinel = object()
28
66
  _name = "otherwise"
29
67
 
68
+ def __init_subclass__(cls, **kwargs):
69
+ super().__init_subclass__(**kwargs)
70
+ if "run" in cls.__dict__:
71
+ cls.run = _cancellable_run(cls.run)
72
+ if "roger" in cls.__dict__:
73
+ cls.roger = _cancellable_roger(cls.roger)
74
+
30
75
  # Use make() instead of this
31
76
  def __init__(self, bot: Bot, session: BotSession, *, _pid: int, _internal=None):
32
77
  if _internal is not self._sentinel:
@@ -36,6 +81,10 @@ class BotCommand:
36
81
  self._argv = None
37
82
  self._parser = self._init_parser()
38
83
  self._pid = _pid
84
+ self._run_tasks: set[asyncio.Task] = set()
85
+ self._roger_tasks: set[asyncio.Task] = set()
86
+ self._inner_tasks: set[asyncio.Task] = set()
87
+ self._inner_cmds: list[BotCommand] = []
39
88
  session.commands[_pid] = self
40
89
 
41
90
  @classmethod
@@ -72,6 +121,14 @@ class BotCommand:
72
121
  def pid(self):
73
122
  return self._pid
74
123
 
124
+ @property
125
+ def run_tasks(self):
126
+ return self._run_tasks
127
+
128
+ @property
129
+ def roger_tasks(self):
130
+ return self._roger_tasks
131
+
75
132
  def _init_parser(self):
76
133
  return BotArgParser()
77
134
 
@@ -131,6 +188,20 @@ class BotCommand:
131
188
  entry_name, self.session.group_id, self.session.user_id
132
189
  )
133
190
 
191
+ def _create_task(self, coro) -> asyncio.Task:
192
+ task = asyncio.create_task(coro)
193
+ self._inner_tasks.add(task)
194
+ task.add_done_callback(self._inner_tasks.discard)
195
+ return task
196
+
197
+ def _rm_cache(self):
198
+ if not self.session:
199
+ return
200
+ cache_path = (
201
+ config.cache / self.session.group_id / self.session.user_id / str(self._pid)
202
+ )
203
+ rm_path(cache_path)
204
+
134
205
  async def roger(self, event: MessageEvent):
135
206
  pass
136
207
 
@@ -149,6 +220,16 @@ class BotCommand:
149
220
  def unlock(self):
150
221
  if self._session is None:
151
222
  return
223
+ for task in self._inner_tasks:
224
+ if not task.done():
225
+ task.cancel()
226
+ for cmd in self._inner_cmds:
227
+ cmd.unlock()
228
+ self._rm_cache()
152
229
  self._session.commands.pop(self._pid, None)
153
230
  self._session.release()
154
231
  self._session = None
232
+
233
+
234
+ BotCommand.run = _cancellable_run(BotCommand.run)
235
+ BotCommand.roger = _cancellable_roger(BotCommand.roger)
@@ -11,7 +11,7 @@ import httpx
11
11
  from nonebot.adapters.onebot.v11 import Bot, Message, MessageEvent, MessageSegment
12
12
  from nonebot.log import logger
13
13
 
14
- from ..aux import rm_cache, stuff_download
14
+ from ..aux import stuff_download
15
15
  from ..command import BotCommand
16
16
  from ..config import config
17
17
  from ..msgutils import dump_message, get_message_with_reply, get_multimedias_url
@@ -96,6 +96,7 @@ class BotCommandConvert(BotCommand):
96
96
  return ""
97
97
  png_path = png_dir / f"{uuid.uuid4().hex}.png"
98
98
  import magic
99
+
99
100
  mime = magic.from_file(download_path, mime=True)
100
101
  logger.info(
101
102
  f"检测文件类型: {mime}, 执行图片转 PNG: {download_path} -> {png_path}"
@@ -312,6 +313,8 @@ class BotCommandConvert(BotCommand):
312
313
  client,
313
314
  downloads_dir,
314
315
  )
316
+ except asyncio.CancelledError:
317
+ pass
315
318
  except Exception as e:
316
319
  logger.exception(f"urls_to_downloads 管道异常退出: {e}")
317
320
 
@@ -320,6 +323,8 @@ class BotCommandConvert(BotCommand):
320
323
  await prod_cons(
321
324
  self._downloads, self._pngs, self._download_to_png, pngs_dir
322
325
  )
326
+ except asyncio.CancelledError:
327
+ pass
323
328
  except Exception as e:
324
329
  logger.exception(f"downloads_to_pngs 管道异常退出: {e}")
325
330
 
@@ -334,12 +339,14 @@ class BotCommandConvert(BotCommand):
334
339
  await prod_cons(
335
340
  self._pngs, self._outputs, self._png_to_frame, outputs_dir
336
341
  )
342
+ except asyncio.CancelledError:
343
+ pass
337
344
  except Exception as e:
338
345
  logger.exception(f"pngs_to_outputs 管道异常退出: {e}")
339
346
 
340
- asyncio.create_task(_urls_to_downloads())
341
- asyncio.create_task(_downloads_to_pngs())
342
- asyncio.create_task(_pngs_to_outputs())
347
+ self._create_task(_urls_to_downloads())
348
+ self._create_task(_downloads_to_pngs())
349
+ self._create_task(_pngs_to_outputs())
343
350
 
344
351
  logger.info(f"用户 {self.session.user_id} 开始了图片收集")
345
352
  await self.send_msg(
@@ -363,7 +370,6 @@ class BotCommandConvert(BotCommand):
363
370
  await self.send_msg("转换完毕。")
364
371
 
365
372
  await self._send_outputs()
366
- await rm_cache(self.session.group_id, self.session.user_id, str(self._pid))
367
373
  self.unlock()
368
374
  return
369
375
 
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import asyncio
3
4
  import re
4
5
  from pathlib import Path
5
6
  from typing import TYPE_CHECKING
@@ -34,6 +35,14 @@ class BotCommandHelp(BotCommand):
34
35
  parser.add_subparser("autoreply")
35
36
  return parser
36
37
 
38
+ def unlock(self):
39
+ for task in self._inner_tasks:
40
+ if not task.done():
41
+ task.cancel()
42
+ for cmd in self._inner_cmds:
43
+ cmd.unlock()
44
+ super().unlock()
45
+
37
46
  async def run(self, args: Message):
38
47
  if not self.session:
39
48
  return
@@ -88,7 +97,11 @@ class BotCommandHelp(BotCommand):
88
97
  while _pid in pids:
89
98
  _pid -= 1
90
99
  md2pic = BotCommandMd2pic.make(self.bot, self.session, _pid=_pid)
100
+ if md2pic is None:
101
+ return
91
102
  tip = "-c\n" + tip
92
103
  msg = Message(tip)
93
- await md2pic.run(msg) # type: ignore
104
+ inner_task = self._create_task(md2pic.run(msg))
105
+ self._inner_cmds.append(md2pic)
106
+ await asyncio.gather(inner_task, return_exceptions=True)
94
107
  self.unlock()
@@ -23,8 +23,10 @@ class BotCommandSession(BotCommand):
23
23
  parser.set_rule(max=0, need_subcmd=True)
24
24
  switch = parser.add_subparser("switch")
25
25
  info = parser.add_subparser("info")
26
+ stop = parser.add_subparser("stop")
26
27
  switch.set_rule(min=1, max=1, types=[int])
27
28
  info.set_rule(max=0)
29
+ stop.set_rule(max=0)
28
30
  return parser
29
31
 
30
32
  async def run(self, args: Message):
@@ -76,4 +78,19 @@ class BotCommandSession(BotCommand):
76
78
  tip += f"正在运行的命令: \n{json.dumps(cmd_info, indent=2, ensure_ascii=False)}"
77
79
  await self.send_msg(tip)
78
80
 
81
+ if subcmd == "stop":
82
+ curpid = self.session.curpid
83
+ target = self.session.commands.get(curpid)
84
+ if target is None:
85
+ await self.send_msg(f"pid {curpid} 没有正在运行的命令")
86
+ else:
87
+ for task in target.run_tasks:
88
+ if not task.done():
89
+ task.cancel()
90
+ for task in target.roger_tasks:
91
+ if not task.done():
92
+ task.cancel()
93
+ target.unlock()
94
+ await self.send_msg(f"已停止命令 {target.name} (pid={curpid})")
95
+
79
96
  self.unlock()
@@ -1,5 +1,3 @@
1
- import asyncio
2
-
3
1
  import yaml
4
2
  from nonebot import get_driver, on_command, on_message
5
3
  from nonebot.adapters.onebot.v11 import Bot, Message, MessageEvent
@@ -89,7 +87,7 @@ driver = get_driver()
89
87
 
90
88
  @driver.on_startup
91
89
  async def startup():
92
- await rm_path(config.cache)
90
+ rm_path(config.cache)
93
91
  config.cache.mkdir(parents=True, exist_ok=True)
94
92
  logger.info("nonebot-plugin-shitbot 已加载")
95
93
 
@@ -110,11 +108,22 @@ async def bot_connect(bot: Bot):
110
108
 
111
109
  @driver.on_shutdown
112
110
  async def shutdown():
113
- tasks = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()]
114
- for task in tasks:
115
- task.cancel()
116
-
117
- await asyncio.gather(*tasks, return_exceptions=True)
111
+ commands = [
112
+ cmd
113
+ for group_objs in BotSession._objs.values()
114
+ for session in group_objs.values()
115
+ for pid, cmd in session.commands.items()
116
+ if pid >= 0
117
+ ]
118
+ for command in commands:
119
+ for task in command.run_tasks:
120
+ if not task.done():
121
+ task.cancel()
122
+ for task in command.roger_tasks:
123
+ if not task.done():
124
+ task.cancel()
125
+ command.unlock()
126
+ rm_path(config.cache)
118
127
 
119
128
 
120
129
  # ===Commands handlers=== #
@@ -14,6 +14,7 @@
14
14
  |--------|------|
15
15
  | `switch <pid>` | 将前台切换到指定 pid |
16
16
  | `info` | 查看当前会话信息 |
17
+ | `stop` | 停止当前会话的前台 pid 中运行的命令 |
17
18
 
18
19
  ## 说明
19
20
 
@@ -35,4 +36,5 @@
35
36
  ```bash
36
37
  /session switch 3 将前台切换到 pid=3
37
38
  /session info 查看所有运行中的命令以及前台对应的 pid
39
+ /session stop 停止当前会话的前台 pid 中运行的命令
38
40
  ```
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nonebot-plugin-shitbot
3
- Version: 0.4.2
3
+ Version: 0.5.0
4
4
  Summary: NoneBot shitpost plugin
5
5
  Requires-Python: >=3.10
6
6
  License-File: LICENSE
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "nonebot-plugin-shitbot"
3
- version = "0.4.2"
3
+ version = "0.5.0"
4
4
  description = "NoneBot shitpost plugin"
5
5
  requires-python = ">=3.10"
6
6
  dependencies = [