nonebot-plugin-shitbot 0.2__tar.gz → 0.3.1__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 (53) hide show
  1. {nonebot_plugin_shitbot-0.2 → nonebot_plugin_shitbot-0.3.1}/PKG-INFO +3 -2
  2. {nonebot_plugin_shitbot-0.2 → nonebot_plugin_shitbot-0.3.1}/README.md +11 -9
  3. {nonebot_plugin_shitbot-0.2 → nonebot_plugin_shitbot-0.3.1}/nonebot_plugin_shitbot/__init__.py +4 -0
  4. {nonebot_plugin_shitbot-0.2 → nonebot_plugin_shitbot-0.3.1}/nonebot_plugin_shitbot/commands/autoreply_cmd.py +55 -38
  5. {nonebot_plugin_shitbot-0.2 → nonebot_plugin_shitbot-0.3.1}/nonebot_plugin_shitbot/commands/autoreply_main_cmd.py +2 -1
  6. {nonebot_plugin_shitbot-0.2 → nonebot_plugin_shitbot-0.3.1}/nonebot_plugin_shitbot/commands/convert_cmd.py +70 -28
  7. {nonebot_plugin_shitbot-0.2 → nonebot_plugin_shitbot-0.3.1}/nonebot_plugin_shitbot/commands/pixiv_cmd.py +1 -1
  8. {nonebot_plugin_shitbot-0.2 → nonebot_plugin_shitbot-0.3.1}/nonebot_plugin_shitbot/commands/shitpost_cmd.py +2 -2
  9. nonebot_plugin_shitbot-0.3.1/nonebot_plugin_shitbot/config.py +246 -0
  10. {nonebot_plugin_shitbot-0.2 → nonebot_plugin_shitbot-0.3.1}/nonebot_plugin_shitbot/handlers.py +6 -2
  11. nonebot_plugin_shitbot-0.3.1/nonebot_plugin_shitbot/help/advrandpic.md +61 -0
  12. nonebot_plugin_shitbot-0.3.1/nonebot_plugin_shitbot/help/autoreply.md +165 -0
  13. nonebot_plugin_shitbot-0.3.1/nonebot_plugin_shitbot/help/convert.md +37 -0
  14. nonebot_plugin_shitbot-0.3.1/nonebot_plugin_shitbot/help/help.md +17 -0
  15. nonebot_plugin_shitbot-0.3.1/nonebot_plugin_shitbot/help/imgs/md2picex1.png +0 -0
  16. nonebot_plugin_shitbot-0.3.1/nonebot_plugin_shitbot/help/imgs/md2picex2.png +0 -0
  17. nonebot_plugin_shitbot-0.3.1/nonebot_plugin_shitbot/help/index.md +23 -0
  18. nonebot_plugin_shitbot-0.3.1/nonebot_plugin_shitbot/help/md2pic.md +57 -0
  19. nonebot_plugin_shitbot-0.3.1/nonebot_plugin_shitbot/help/perm.md +110 -0
  20. nonebot_plugin_shitbot-0.3.1/nonebot_plugin_shitbot/help/pixiv.md +42 -0
  21. nonebot_plugin_shitbot-0.3.1/nonebot_plugin_shitbot/help/randpic.md +34 -0
  22. nonebot_plugin_shitbot-0.3.1/nonebot_plugin_shitbot/help/session.md +38 -0
  23. nonebot_plugin_shitbot-0.3.1/nonebot_plugin_shitbot/help/shitpost.md +39 -0
  24. {nonebot_plugin_shitbot-0.2 → nonebot_plugin_shitbot-0.3.1}/nonebot_plugin_shitbot/msgdatabase.py +3 -3
  25. {nonebot_plugin_shitbot-0.2 → nonebot_plugin_shitbot-0.3.1}/nonebot_plugin_shitbot/msgutils.py +2 -2
  26. {nonebot_plugin_shitbot-0.2 → nonebot_plugin_shitbot-0.3.1}/nonebot_plugin_shitbot/permissions.py +31 -20
  27. {nonebot_plugin_shitbot-0.2 → nonebot_plugin_shitbot-0.3.1}/nonebot_plugin_shitbot.egg-info/PKG-INFO +3 -2
  28. {nonebot_plugin_shitbot-0.2 → nonebot_plugin_shitbot-0.3.1}/nonebot_plugin_shitbot.egg-info/SOURCES.txt +13 -5
  29. {nonebot_plugin_shitbot-0.2 → nonebot_plugin_shitbot-0.3.1}/nonebot_plugin_shitbot.egg-info/requires.txt +2 -1
  30. {nonebot_plugin_shitbot-0.2 → nonebot_plugin_shitbot-0.3.1}/pyproject.toml +4 -4
  31. nonebot_plugin_shitbot-0.2/nonebot_plugin_shitbot/config.py +0 -191
  32. nonebot_plugin_shitbot-0.2/nonebot_plugin_shitbot/config.yaml +0 -26
  33. nonebot_plugin_shitbot-0.2/nonebot_plugin_shitbot/default_config.yaml +0 -24
  34. nonebot_plugin_shitbot-0.2/nonebot_plugin_shitbot/scripts/p2png.sh +0 -38
  35. nonebot_plugin_shitbot-0.2/nonebot_plugin_shitbot/scripts/png2fr.sh +0 -19
  36. nonebot_plugin_shitbot-0.2/nonebot_plugin_shitbot/scripts/png2v.sh +0 -24
  37. {nonebot_plugin_shitbot-0.2 → nonebot_plugin_shitbot-0.3.1}/LICENSE +0 -0
  38. {nonebot_plugin_shitbot-0.2 → nonebot_plugin_shitbot-0.3.1}/nonebot_plugin_shitbot/aux.py +0 -0
  39. {nonebot_plugin_shitbot-0.2 → nonebot_plugin_shitbot-0.3.1}/nonebot_plugin_shitbot/command.py +0 -0
  40. {nonebot_plugin_shitbot-0.2 → nonebot_plugin_shitbot-0.3.1}/nonebot_plugin_shitbot/commands/__init__.py +0 -0
  41. {nonebot_plugin_shitbot-0.2 → nonebot_plugin_shitbot-0.3.1}/nonebot_plugin_shitbot/commands/advrandpic_cmd.py +0 -0
  42. {nonebot_plugin_shitbot-0.2 → nonebot_plugin_shitbot-0.3.1}/nonebot_plugin_shitbot/commands/help_cmd.py +0 -0
  43. {nonebot_plugin_shitbot-0.2 → nonebot_plugin_shitbot-0.3.1}/nonebot_plugin_shitbot/commands/md2pic_cmd.py +0 -0
  44. {nonebot_plugin_shitbot-0.2 → nonebot_plugin_shitbot-0.3.1}/nonebot_plugin_shitbot/commands/perm_cmd.py +0 -0
  45. {nonebot_plugin_shitbot-0.2 → nonebot_plugin_shitbot-0.3.1}/nonebot_plugin_shitbot/commands/randpic_cmd.py +0 -0
  46. {nonebot_plugin_shitbot-0.2 → nonebot_plugin_shitbot-0.3.1}/nonebot_plugin_shitbot/commands/session_cmd.py +0 -0
  47. {nonebot_plugin_shitbot-0.2 → nonebot_plugin_shitbot-0.3.1}/nonebot_plugin_shitbot/css/github-markdown-dark-dimmed.css +0 -0
  48. {nonebot_plugin_shitbot-0.2 → nonebot_plugin_shitbot-0.3.1}/nonebot_plugin_shitbot/parser.py +0 -0
  49. {nonebot_plugin_shitbot-0.2 → nonebot_plugin_shitbot-0.3.1}/nonebot_plugin_shitbot/session.py +0 -0
  50. {nonebot_plugin_shitbot-0.2 → nonebot_plugin_shitbot-0.3.1}/nonebot_plugin_shitbot/tasks.py +0 -0
  51. {nonebot_plugin_shitbot-0.2 → nonebot_plugin_shitbot-0.3.1}/nonebot_plugin_shitbot.egg-info/dependency_links.txt +0 -0
  52. {nonebot_plugin_shitbot-0.2 → nonebot_plugin_shitbot-0.3.1}/nonebot_plugin_shitbot.egg-info/top_level.txt +0 -0
  53. {nonebot_plugin_shitbot-0.2 → nonebot_plugin_shitbot-0.3.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nonebot-plugin-shitbot
3
- Version: 0.2
3
+ Version: 0.3.1
4
4
  Summary: NoneBot shitpost plugin
5
5
  Requires-Python: >=3.10
6
6
  License-File: LICENSE
@@ -11,7 +11,8 @@ Requires-Dist: nonebot-plugin-localstore>=0.3.0
11
11
  Requires-Dist: PixivPy-Async>=1.2.14
12
12
  Requires-Dist: Pillow>=12.2.0
13
13
  Requires-Dist: httpx>=0.28.1
14
- Requires-Dist: pydantic>=2.13.4
14
+ Requires-Dist: python-magic>=0.4
15
+ Requires-Dist: pydantic>=2
15
16
  Requires-Dist: PyYAML>=6.0.3
16
17
  Requires-Dist: ruamel.yaml>=0.18.0
17
18
  Dynamic: license-file
@@ -61,8 +61,7 @@
61
61
 
62
62
  ## 配置
63
63
 
64
- 插件启动时会自动从插件目录下的 `default_config.yaml` 复制默认配置到 `config.yaml`,
65
- 之后编辑 `config.yaml` 即可。
64
+ 插件配置通过 NoneBot 标准的 dotenv / 环境变量读取。
66
65
 
67
66
  ### 安装
68
67
 
@@ -70,14 +69,17 @@
70
69
  nb plugin install nonebot-plugin-shitbot
71
70
  ```
72
71
 
73
- ### 首次部署必填项
72
+ ### 环境变量
74
73
 
75
- | 配置项 | 类型 | 说明 |
76
- |---|---|---|
77
- | `bot_base` | 路径 | 插件的持久存储根目录(cache / data / config / 均存放于此) |
78
- | `client_base` | 路径 | 客户端侧的持久存储根目录(用于兼容容器部署) |
79
- | `owners` | 字符串列表 | 机器人所有者 QQ 号列表 |
80
- | `pixiv_access_token` | 字符串 | Pixiv API 访问令牌(可选填,使用 `/pixiv` 时必填) |
74
+ 在 NoneBot 项目的 `.env` 文件中配置,变量如下:
75
+
76
+ | 配置项 | 类型 | 默认值 | 是否必填 | 说明 |
77
+ |---|---|---|---|---|
78
+ | `SHITBOT__CLIENT_CACHE` | 路径 | 本地缓存路径 | 否 | 容器端缓存路径。本地缓存路径为 `$LOCALSTORE_CACHE_DIR/nonebot_plugin_shitbot`,容器端需对应挂载 |
79
+ | `SHITBOT__CLIENT_DATA` | 路径 | 本地数据路径 | 否 | 容器端数据路径。本地数据路径为 `$LOCALSTORE_DATA_DIR/nonebot_plugin_shitbot`,容器端需对应挂载 |
80
+ | `SHITBOT__OWNERS` | JSON 字符串列表 | None | 是 | 机器人所有者 QQ 号列表,如 `'["114514"]'` |
81
+ | `SHITBOT__MAX_MESSAGE_DEPTH` | 整数 | `16` | 否 | 消息解析最大递归深度 |
82
+ | `SHITBOT__PIXIV_ACCESS_TOKEN` | 字符串 | "" | 部分必填 | Pixiv API 访问令牌(使用 `/pixiv` 时必填) |
81
83
 
82
84
  ### 触发规则
83
85
 
@@ -5,11 +5,15 @@ from os import environ
5
5
  # (e.g. nonebot_plugin_htmlrender → get_driver()). Pure-logic modules
6
6
  # like parser.py should remain importable without a running NoneBot.
7
7
  if not environ.get("PYTEST_RUNNING"):
8
+ from nonebot import require
8
9
  from nonebot.plugin import PluginMetadata
9
10
 
10
11
  from . import handlers # noqa: F401
11
12
  from .config import ShitBotConfig
12
13
 
14
+ require("nonebot_plugin_htmlrender")
15
+ require("nonebot_plugin_localstore")
16
+
13
17
  __plugin_meta__ = PluginMetadata(
14
18
  name="shitbot",
15
19
  description="个人自用自己手搓框架的多功能bot, 还在开发中",
@@ -1,15 +1,20 @@
1
1
  import asyncio
2
- import yaml
3
-
4
- from pathlib import Path
5
2
 
3
+ import yaml
6
4
  from nonebot.adapters.onebot.v11 import Bot, Message, MessageEvent
7
5
 
8
6
  from ..command import BotCommand
9
- from ..session import BotSession
10
- from ..parser import BotArgParser
11
7
  from ..msgdatabase import BotMsgDataBase
12
- from ..msgutils import DumpedMsg, DumpedSeg, DataVariables, dump_message, msg_filter, modify_msg_data
8
+ from ..msgutils import (
9
+ DataVariables,
10
+ DumpedMsg,
11
+ DumpedSeg,
12
+ dump_message,
13
+ modify_msg_data,
14
+ msg_filter,
15
+ )
16
+ from ..parser import BotArgParser
17
+ from ..session import BotSession
13
18
  from ..tasks import autoreply_lock
14
19
  from .autoreply_main_cmd import BotCommandAutoReplyMain
15
20
 
@@ -17,6 +22,7 @@ from .autoreply_main_cmd import BotCommandAutoReplyMain
17
22
  class BotCommandAutoreply(BotCommand):
18
23
  _name = "autoreply"
19
24
  _autoreply_dir = BotCommandAutoReplyMain._autoreply_dir
25
+ _state_path = BotCommandAutoReplyMain._state_path
20
26
  _rule_path = BotCommandAutoReplyMain._rule_path
21
27
  _msg_table_path = BotCommandAutoReplyMain._msg_table_path
22
28
 
@@ -33,6 +39,8 @@ class BotCommandAutoreply(BotCommand):
33
39
  self._rp: list[str] = []
34
40
  self._T: list[str] = []
35
41
  self._t: list[str] = []
42
+ self._D: list[str] = []
43
+ self._d: list[str] = []
36
44
  self._K: list[str] = []
37
45
  self._k: list[str] = []
38
46
  self._R: list[str] = []
@@ -162,7 +170,7 @@ class BotCommandAutoreply(BotCommand):
162
170
  stop.set_rule(max=0)
163
171
  stop.add_opt("--auto", default=[0])
164
172
 
165
- lst.set_rule(min=1,max=1)
173
+ lst.set_rule(min=1, max=1)
166
174
 
167
175
  info.set_rule(max=None)
168
176
 
@@ -176,14 +184,24 @@ class BotCommandAutoreply(BotCommand):
176
184
  delete.add_opt("--key", required=True, max_appeared=None)
177
185
  delete.add_opt("--rp", required=True, max_appeared=None)
178
186
 
179
- reply.set_rule(min=1,max=1, need_subcmd=True)
187
+ reply.set_rule(min=1, max=1, need_subcmd=True)
180
188
  reply_start = reply.add_subparser("start")
181
189
  reply_stop = reply.add_subparser("stop")
182
190
  reply_modify = reply.add_subparser("modify")
183
191
 
184
- key.set_rule(min=1,max=1)
185
- key.add_opt("-T", required=True, choice=["delmark", "delspace", "upper", "lower"], max_appeared=None)
186
- key.add_opt("-t", required=True, choice=["delmark", "delspace", "upper", "lower"], max_appeared=None)
192
+ key.set_rule(min=1, max=1)
193
+ key.add_opt(
194
+ "-T",
195
+ required=True,
196
+ choice=["delmark", "delspace", "upper", "lower"],
197
+ max_appeared=None,
198
+ )
199
+ key.add_opt(
200
+ "-t",
201
+ required=True,
202
+ choice=["delmark", "delspace", "upper", "lower"],
203
+ max_appeared=None,
204
+ )
187
205
  key.add_opt("-D", required=True, max_appeared=None)
188
206
  key.add_opt("-d", required=True, max_appeared=None)
189
207
  key.add_opt("-K", required=True, max_appeared=None)
@@ -217,7 +235,7 @@ class BotCommandAutoreply(BotCommand):
217
235
  if subsubcmd == "stop" and self._rstate != "start":
218
236
  return await super()._guard_state()
219
237
  return True
220
-
238
+
221
239
  async def _start(self):
222
240
  autoreply_session = BotSession.make("public", "autoreply")
223
241
  main_command = BotCommandAutoReplyMain.make(
@@ -228,12 +246,9 @@ class BotCommandAutoreply(BotCommand):
228
246
  else:
229
247
  await main_command.run(Message())
230
248
  if self._auto:
231
- from ruamel.yaml import YAML
232
- config_path = Path(__file__).parent.parent / "config.yaml"
233
- config_yaml = YAML()
234
- data = config_yaml.load(config_path)
235
- data["if_auto_start_autoreply"] = True
236
- config_yaml.dump(data, config_path)
249
+ self._state_path.write_text(
250
+ yaml.safe_dump(True, allow_unicode=True), encoding="utf-8"
251
+ )
237
252
  await self.send_msg("自动回复已开启")
238
253
  self.unlock()
239
254
 
@@ -251,12 +266,9 @@ class BotCommandAutoreply(BotCommand):
251
266
 
252
267
  await _exe()
253
268
  if self._auto:
254
- from ruamel.yaml import YAML
255
- config_path = Path(__file__).parent.parent / "config.yaml"
256
- config_yaml = YAML()
257
- data = config_yaml.load(config_path)
258
- data["if_auto_start_autoreply"] = False
259
- config_yaml.dump(data, config_path)
269
+ self._state_path.write_text(
270
+ yaml.safe_dump(False, allow_unicode=True), encoding="utf-8"
271
+ )
260
272
  await self.send_msg("自动回复已关闭")
261
273
  self.unlock()
262
274
 
@@ -334,7 +346,9 @@ class BotCommandAutoreply(BotCommand):
334
346
  return
335
347
  self._rstate = "start"
336
348
  self._reply_name = reply_name
337
- await self.send_msg(f"请发送仅包含图片或文字的信息, 或者发送 /autoreply reply {reply_name} stop 停止设置")
349
+ await self.send_msg(
350
+ f"请发送仅包含图片或文字的信息, 或者发送 /autoreply reply {reply_name} stop 停止设置"
351
+ )
338
352
  self._is_accept_msg = True
339
353
 
340
354
  async def _reply_stop(self, reply_name: str):
@@ -371,7 +385,7 @@ class BotCommandAutoreply(BotCommand):
371
385
  "summary": DataVariables(self._sum),
372
386
  "sub_type": DataVariables(self._st),
373
387
  },
374
- ["image"]
388
+ ["image"],
375
389
  )
376
390
  await self._set_reply(reply_name)
377
391
  self._temp_msg = []
@@ -383,6 +397,7 @@ class BotCommandAutoreply(BotCommand):
383
397
  await self.send_msg(f"错误: 键 {key_name} 不存在")
384
398
  self.unlock()
385
399
  return
400
+
386
401
  def _rm_dup(lst_A: list, lst_R: list):
387
402
  dup = []
388
403
  for elem in lst_A:
@@ -403,7 +418,12 @@ class BotCommandAutoreply(BotCommand):
403
418
  lst.remove(elem)
404
419
  return lst
405
420
 
406
- for (lst_A, lst_R) in [(self._T, self._t), (self._D, self._d), (self._K, self._k), (self._R, self._r)]:
421
+ for lst_A, lst_R in [
422
+ (self._T, self._t),
423
+ (self._D, self._d),
424
+ (self._K, self._k),
425
+ (self._R, self._r),
426
+ ]:
407
427
  _rm_dup(lst_A, lst_R)
408
428
  for lst in [self._R, self._r]:
409
429
  rem = []
@@ -414,9 +434,13 @@ class BotCommandAutoreply(BotCommand):
414
434
  for elem in rem:
415
435
  lst.remove(elem)
416
436
  data = []
417
- for (lst_S, lst_A, lst_R) in [(self._rule[key_name]["trans"], self._T, self._t), (self._rule[key_name]["dels"], self._D, self._d), (self._rule[key_name]["keywords"], self._K, self._k), (self._rule[key_name]["replys"], self._R, self._r)]:
437
+ for lst_S, lst_A, lst_R in [
438
+ (self._rule[key_name]["trans"], self._T, self._t),
439
+ (self._rule[key_name]["dels"], self._D, self._d),
440
+ (self._rule[key_name]["keywords"], self._K, self._k),
441
+ (self._rule[key_name]["replys"], self._R, self._r),
442
+ ]:
418
443
  data.append(_get_lst(lst_S, lst_A, lst_R))
419
-
420
444
 
421
445
  _m = None
422
446
  if self._m == 0:
@@ -431,13 +455,11 @@ class BotCommandAutoreply(BotCommand):
431
455
  dels=data[1],
432
456
  is_contain=_m,
433
457
  keywords=data[2],
434
- replys=data[3]
458
+ replys=data[3],
435
459
  )
436
460
  await self.send_msg("修改成功")
437
461
  self.unlock()
438
462
 
439
-
440
-
441
463
  async def roger(self, event: MessageEvent):
442
464
  async with self._roger_lock:
443
465
  if not self._is_accept_msg:
@@ -453,11 +475,10 @@ class BotCommandAutoreply(BotCommand):
453
475
  raise ValueError
454
476
  asyncio.create_task(self.run(Message(f"reply {reply_name} stop")))
455
477
 
456
-
457
-
458
478
  async def run(self, args: Message):
459
479
  async with autoreply_lock:
460
480
  self._database = BotMsgDataBase(self._autoreply_dir)
481
+ self._state_path.touch(exist_ok=True)
461
482
  self._rule_path.touch(exist_ok=True)
462
483
  self._msg_table_path.touch(exist_ok=True)
463
484
  self._load_meta()
@@ -517,7 +538,6 @@ class BotCommandAutoreply(BotCommand):
517
538
  self._st = subsubparser.opts_value.get("--st", [])
518
539
  self._rstate = subsubcmd if subsubcmd != "modify" else ""
519
540
 
520
-
521
541
  if subcmd == "start":
522
542
  await self._start()
523
543
  if subcmd == "stop":
@@ -545,6 +565,3 @@ class BotCommandAutoreply(BotCommand):
545
565
  if subcmd == "key":
546
566
  key_name = subparser.value[0]
547
567
  await self._key_modify(key_name)
548
-
549
-
550
-
@@ -1,5 +1,4 @@
1
1
  import random
2
- import re
3
2
  import unicodedata
4
3
  from typing import Any
5
4
 
@@ -25,6 +24,7 @@ _TERM_SCHEMA = {
25
24
  class BotCommandAutoReplyMain(BotCommand):
26
25
  _name = "autoreply_main_rec"
27
26
  _autoreply_dir = config.data / "autoreply"
27
+ _state_path = _autoreply_dir / "state.yaml"
28
28
  _rule_path = _autoreply_dir / "rule.yaml"
29
29
  _msg_table_path = _autoreply_dir / "msg_table.yaml"
30
30
  _rule: dict[str, dict[str, Any]]
@@ -94,6 +94,7 @@ class BotCommandAutoReplyMain(BotCommand):
94
94
  random.seed()
95
95
  async with autoreply_lock:
96
96
  self._database = BotMsgDataBase(self._autoreply_dir)
97
+ self._state_path.touch(exist_ok=True)
97
98
  self._rule_path.touch(exist_ok=True)
98
99
  self._msg_table_path.touch(exist_ok=True)
99
100
  self.load_meta()
@@ -1,12 +1,14 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import asyncio
4
+ import random
4
5
  import shutil
5
6
  import uuid
6
7
  from pathlib import Path
7
8
  from typing import TYPE_CHECKING
8
9
 
9
10
  import httpx
11
+ import magic
10
12
  from nonebot.adapters.onebot.v11 import Bot, Message, MessageEvent, MessageSegment
11
13
  from nonebot.log import logger
12
14
 
@@ -20,7 +22,7 @@ from ..tasks import EndOfQueue, prod_cons
20
22
  if TYPE_CHECKING:
21
23
  from ..session import BotSession
22
24
 
23
- bash = shutil.which("bash") or "/bin/bash"
25
+ ffmpeg = shutil.which("ffmpeg") or "ffmpeg"
24
26
 
25
27
 
26
28
  class BotCommandConvert(BotCommand):
@@ -94,26 +96,40 @@ class BotCommandConvert(BotCommand):
94
96
  if download_path == "":
95
97
  return ""
96
98
  png_path = png_dir / f"{uuid.uuid4().hex}.png"
99
+ mime = magic.from_file(download_path, mime=True)
97
100
  logger.info(
98
- f"执行图片转png脚本: {config.script_p2png_path} {download_path} {png_path}"
101
+ f"检测文件类型: {mime}, 执行图片转 PNG: {download_path} -> {png_path}"
99
102
  )
103
+ if mime == "image/png":
104
+ shutil.copy(download_path, png_path)
105
+ logger.info(f"转换 PNG 成功: {png_path}")
106
+ return str(png_path)
100
107
  proc = await asyncio.create_subprocess_exec(
101
- bash,
102
- str(config.script_p2png_path),
108
+ ffmpeg,
109
+ "-y",
110
+ "-i",
103
111
  str(download_path),
112
+ "-vcodec",
113
+ "png",
114
+ "-pix_fmt",
115
+ "rgba",
116
+ "-update",
117
+ "1",
104
118
  str(png_path),
105
119
  stdout=asyncio.subprocess.PIPE,
106
120
  stderr=asyncio.subprocess.PIPE,
107
121
  )
108
122
  stdout, stderr = await proc.communicate()
109
123
  if stdout:
110
- logger.info(f"脚本 stdout:\n{stdout.decode()}")
124
+ logger.info(f"ffmpeg stdout:\n{stdout.decode()}")
111
125
  if stderr:
112
- logger.warning(f"脚本 stderr:\n{stderr.decode()}")
126
+ logger.warning(f"ffmpeg stderr:\n{stderr.decode()}")
113
127
  if proc.returncode != 0:
114
- logger.error(f"转换失败: {stderr.decode()[:]}")
128
+ logger.error(
129
+ f"转换 PNG 失败 (returncode={proc.returncode}): {stderr.decode()[:] if stderr else ''}"
130
+ )
115
131
  return ""
116
- logger.info(f"转换图片成功: {png_path}")
132
+ logger.info(f"转换 PNG 成功: {png_path}")
117
133
  return str(png_path)
118
134
 
119
135
  @staticmethod
@@ -121,24 +137,47 @@ class BotCommandConvert(BotCommand):
121
137
  if png_path == "":
122
138
  return ""
123
139
  video_path = video_dir / f"{uuid.uuid4().hex}.mp4"
124
- logger.info(
125
- f"执行图片转视频脚本: {config.script_png2v_path} {png_path} {video_path}"
126
- )
140
+ duration = round(random.uniform(1, 5), 3)
141
+ logger.info(f"执行图片转视频 (ffmpeg, {duration}s): {png_path} -> {video_path}")
127
142
  proc = await asyncio.create_subprocess_exec(
128
- bash,
129
- str(config.script_png2v_path),
143
+ ffmpeg,
144
+ "-y",
145
+ "-loop",
146
+ "1",
147
+ "-i",
130
148
  str(png_path),
149
+ "-f",
150
+ "lavfi",
151
+ "-i",
152
+ "anullsrc",
153
+ "-c:v",
154
+ "libx264",
155
+ "-crf",
156
+ "1",
157
+ "-preset",
158
+ "ultrafast",
159
+ "-g",
160
+ "114514",
161
+ "-t",
162
+ str(duration),
163
+ "-pix_fmt",
164
+ "yuv420p",
165
+ "-vf",
166
+ "scale=trunc(iw/2)*2:trunc(ih/2)*2",
167
+ "-shortest",
131
168
  str(video_path),
132
169
  stdout=asyncio.subprocess.PIPE,
133
170
  stderr=asyncio.subprocess.PIPE,
134
171
  )
135
172
  stdout, stderr = await proc.communicate()
136
173
  if stdout:
137
- logger.info(f"脚本 stdout:\n{stdout.decode()}")
174
+ logger.info(f"ffmpeg stdout:\n{stdout.decode()}")
138
175
  if stderr:
139
- logger.warning(f"脚本 stderr:\n{stderr.decode()}")
176
+ logger.warning(f"ffmpeg stderr:\n{stderr.decode()}")
140
177
  if proc.returncode != 0:
141
- logger.error(f"转换失败: {stderr.decode()[:]}")
178
+ logger.error(
179
+ f"转换视频失败 (returncode={proc.returncode}): {stderr.decode()[:] if stderr else ''}"
180
+ )
142
181
  return ""
143
182
  logger.info(f"转换视频成功: {video_path}")
144
183
  return str(video_path)
@@ -148,26 +187,31 @@ class BotCommandConvert(BotCommand):
148
187
  if png_path == "":
149
188
  return ""
150
189
  frame_path = frame_dir / f"{uuid.uuid4().hex}.png"
151
- logger.info(
152
- f"执行图片加框脚本: {config.script_png2fr_path} {png_path} {frame_path}"
153
- )
190
+ border = random.randint(10, 50)
191
+ logger.info(f"执行图片加黑框 (ffmpeg, {border}px): {png_path} -> {frame_path}")
154
192
  proc = await asyncio.create_subprocess_exec(
155
- bash,
156
- str(config.script_png2fr_path),
193
+ ffmpeg,
194
+ "-i",
157
195
  str(png_path),
196
+ "-vf",
197
+ f"pad=iw+{border}:ih+{border}:(ow-iw)/2:(oh-ih)/2:black",
198
+ "-update",
199
+ "1",
158
200
  str(frame_path),
159
201
  stdout=asyncio.subprocess.PIPE,
160
202
  stderr=asyncio.subprocess.PIPE,
161
203
  )
162
204
  stdout, stderr = await proc.communicate()
163
205
  if stdout:
164
- logger.info(f"脚本 stdout:\n{stdout.decode()}")
206
+ logger.info(f"ffmpeg stdout:\n{stdout.decode()}")
165
207
  if stderr:
166
- logger.warning(f"脚本 stderr:\n{stderr.decode()}")
208
+ logger.warning(f"ffmpeg stderr:\n{stderr.decode()}")
167
209
  if proc.returncode != 0:
168
- logger.error(f"转换失败: {stderr.decode()[:]}")
210
+ logger.error(
211
+ f"加框失败 (returncode={proc.returncode}): {stderr.decode()[:] if stderr else ''}"
212
+ )
169
213
  return ""
170
- logger.info(f"转换方形图片成功: {frame_path}")
214
+ logger.info(f"加黑框成功: {frame_path}")
171
215
  return str(frame_path)
172
216
 
173
217
  async def _send_outputs(self):
@@ -195,9 +239,7 @@ class BotCommandConvert(BotCommand):
195
239
  if not output_file.exists():
196
240
  logger.error(f"输出文件不存在: {output_file}")
197
241
  continue
198
- container_path = config.client_base / output_file.relative_to(
199
- config.bot_base
200
- )
242
+ container_path = config.client_cache / output_file.relative_to(config.cache)
201
243
  try:
202
244
  if self._mode == "video":
203
245
  await self.send_msg(
@@ -30,7 +30,7 @@ class BotCommandPixiv(BotCommand):
30
30
  parser.set_rule(min=1, max=1, types=[int])
31
31
  parser.add_opt("-r", required=True, choice=["off", "on"], default=["off"])
32
32
  parser.add_opt(
33
- "-s", required=True, choice=["regular", "original"], default=["regular"]
33
+ "-s", required=True, choice=["regular", "original"], default=["original"]
34
34
  )
35
35
  return parser
36
36
 
@@ -103,8 +103,8 @@ class BotCommandShitpost(BotCommand):
103
103
  media_path = media_dir / f"{uuid.uuid4().hex}"
104
104
  async with httpx.AsyncClient() as client:
105
105
  await stuff_download(client, url, media_path)
106
- container_path = config.client_base / media_path.relative_to(
107
- config.bot_base
106
+ container_path = config.client_cache / media_path.relative_to(
107
+ config.cache
108
108
  )
109
109
  medias_path.append(media_path)
110
110
  medias.append(str(container_path))