-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path__init__.py
More file actions
600 lines (534 loc) · 21.7 KB
/
__init__.py
File metadata and controls
600 lines (534 loc) · 21.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
import jmcomic
from .jmutils import *
from nonebot import on_command, require, get_driver
from nonebot.adapters.onebot.v11 import (
Bot,
Message,
GroupMessageEvent,
PrivateMessageEvent,
MessageSegment,
Event,
)
from nonebot.params import CommandArg
from nonebot.matcher import Matcher
from nonebot.permission import SUPERUSER
import os
import asyncio
from dataclasses import dataclass
import time, json, zhconv, sqlite3
from .dbutils import DbUtils
# 创建线程池
semaphore = asyncio.Semaphore(
10
) # 任务计数,可能包括的任务有:获取本子信息、下载本子、压缩打包本子
downloading_manga = set() # 正在下载的本子ID
# 创建命令处理器
comic = on_command("manga", priority=5, block=True)
suffix = on_command("suffix", priority=5, block=True, permission=SUPERUSER)
image_set = on_command("maximages", priority=5, block=True, permission=SUPERUSER)
negtags = on_command("negtag", priority=5, block=True, permission=SUPERUSER)
cooldown = on_command("cooldown", priority=5, block=True, permission=SUPERUSER)
allow = on_command("whitelist", priority=5, block=True, permission=SUPERUSER)
baneng = on_command("baneng", priority=5, block=True, permission=SUPERUSER)
# 全局配置
script_dir = os.path.dirname(os.path.abspath(__file__))
conn = sqlite3.connect(os.path.join(script_dir, "albums.db")) # 创建数据库连接
config_path = os.path.join(script_dir, "settings.json")
with open(config_path, "r", encoding="utf-8") as f:
config = json.load(f)
CD_SECONDS = config["cd_seconds"]
file_suffix = config["file_suffix"]
max_image_count = config["max_image_count"]
negative_tags = [
zhconv.convert(tag, "zh-hans").lower() for tag in config["negative_tags"]
]
ban_english = config["ban_english"]
whitelist = config["whitelist"]
superadmin = config["superadmin"]
driver = get_driver()
# 数据库更新状态
is_updating_db = False
# 全局变量
processing = False # 是否有正在处理的请求
queue = None # 请求队列
last_request_time = {} # 记录每个群的最后请求时间
queue_started = False # 队列是否已启动
html = jmcomic.create_option_by_file(os.path.join(script_dir, "options/html.yml"))
api = jmcomic.create_option_by_file(os.path.join(script_dir, "options/api.yml"))
option = PackagedJmOption(html, api)
info_pic = JmAlbumInfoPic()
db_utils = DbUtils(option.build_jm_client())
@driver.on_shutdown
def cleanup():
print("[jmcomic] 插件正在关闭")
global info_pic, db_utils
print("[jmcomic] 关闭info_pic")
if info_pic:
del info_pic
print("[jmcomic] 关闭db_utils")
if db_utils:
db_utils.update_stop()
print("[jmcomic] 等待数据库更新完成")
while db_utils.is_update:
time.sleep(0.1)
print("[jmcomic] 插件已关闭")
async def asyncio_wrapper(
func, *args, **kwargs
): # 协程套皮,防止事件响应函数阻塞(主要用于给jmcomic库中不支持协程的功能套皮)
return func(*args, **kwargs)
@dataclass
class DownloadTask:
album: jmcomic.JmAlbumDetail
event: GroupMessageEvent | PrivateMessageEvent # 保存原始事件
async def send_to_user(bot: Bot, event: PrivateMessageEvent, message: str):
"""发送消息到指定用户"""
await bot.send_private_msg(user_id=event.user_id, message=message)
async def send_to_group(bot: Bot, event: GroupMessageEvent, message: str):
"""发送消息到指定群聊"""
await bot.send_group_msg(group_id=event.group_id, message=message)
async def run_in_pool(func, *args, **kwargs):
"""在协程池中运行函数"""
await semaphore.acquire()
result = await func(*args, **kwargs)
semaphore.release()
return result
def check_cd(group_id: int):
current_time = time.time()
if group_id in last_request_time:
time_diff = current_time - last_request_time[group_id]
if time_diff < CD_SECONDS:
return False, CD_SECONDS - time_diff
return True, 0
async def set_emoji(bot: Bot, event: GroupMessageEvent, emoji_id: str):
try:
await bot.call_api(
"set_msg_emoji_like", message_id=event.message_id, emoji_id=emoji_id
)
except Exception as e:
print(f"[jmcomic]贴{emoji_id}表情失败:{str(e)}")
@comic.handle()
async def handle_download(
matcher: Matcher,
bot: Bot,
event: GroupMessageEvent | PrivateMessageEvent,
args: Message = CommandArg(),
):
global whitelist, superadmin, max_image_count, ban_english, last_request_time, option, queue_started, negative_tags
if isinstance(event, GroupMessageEvent):
if not event.group_id in whitelist:
return
# 检查CD
cd_pass, remaining_time = check_cd(event.group_id)
if not cd_pass:
await matcher.finish(
f"看得太快了,要注意身体哦(CD还有{remaining_time:.1f}秒)"
)
return
else:
if not event.user_id in superadmin:
return
# 获取命令参数(本子ID)
args = args.extract_plain_text().strip().split(" ")
if len(args) > 0:
comic_id = args[0]
if (not comic_id.isdigit()) and (comic_id != "random"):
await matcher.finish("格式不正确")
return
else:
await matcher.finish("格式不正确")
# 贴一个OK表情提示已开始处理请求
await set_emoji(bot, event, "124")
client = option.build_jm_client()
if comic_id == "random":
try:
conn = sqlite3.connect(os.path.join(script_dir, "albums.db"))
cur = conn.cursor()
rules = []
if len(args) > 1:
try:
for i in range(1, len(args)):
arg_name, arg_exp = args[i].split("=")
rules.append(RuleLogicExpression(arg_name, arg_exp))
except Exception as e:
await matcher.finish("标签表达式错误:" + str(e))
return
rules_sql = ""
for rule in rules:
rules_sql += f" AND ({rule.to_sql()})"
command = (
f"SELECT id FROM albums WHERE (page_count <= {max_image_count})"
+ (
" AND (length(tags) <> length(cast(tags AS blob)))"
if ban_english
else ""
)
+ rules_sql
+ (f" AND ({negtag_to_sql(negative_tags)})" if negative_tags else "")
+ " ORDER BY RANDOM() LIMIT 1"
)
print("[jmcomic]SQL command: " + command)
try:
cur.execute(command)
result = cur.fetchall()
except Exception as e:
print(f"[jmcomic]数据库查询失败:{str(e)}")
return
if not result:
await matcher.finish("没有符合条件的本子")
comic_id = result[0][0]
await matcher.send(f"随机本子ID: {comic_id}")
finally:
conn.close()
try:
album = await asyncio_wrapper(client.get_album_detail, comic_id)
except Exception as e:
# 贴一个流泪表情提示处理失败
await set_emoji(bot, event, "9")
await matcher.finish(f"获取本子信息失败")
return
if album.page_count > max_image_count:
if isinstance(event, GroupMessageEvent) or not event.user_id in superadmin:
# 贴一个流泪表情提示处理失败
await set_emoji(bot, event, "9")
await matcher.finish("本子图片数量过多")
return
# 更新最后请求时间
if isinstance(event, GroupMessageEvent):
last_request_time[event.group_id] = time.time()
# 创建下载任务
task = DownloadTask(album=album, event=event)
# 提交下载任务至线程池
asyncio.create_task(run_in_pool(handle_download_task, bot, task))
await matcher.send(f"已将本子ID: {comic_id}加入下载队列")
async def handle_download_task(bot: Bot, task: DownloadTask):
global option, info_pic
file_path = os.path.join(script_dir, "compressed", f"{task.album.id}.zip")
await asyncio.sleep(0.5)
print(f"[jmcomic]开始下载 {task.album.id}")
if isinstance(task.event, GroupMessageEvent):
await send_to_group(bot, task.event, f"开始下载本子ID: {task.album.id}")
else:
await send_to_user(bot, task.event, f"开始下载本子ID: {task.album.id}")
try:
if task.album.album_id in downloading_manga:
while task.album.album_id in downloading_manga:
await asyncio.sleep(1)
if not os.path.exists(file_path):
raise "短时间内前一个同JM号下载任务出错,放弃本次尝试"
else:
downloading_manga.add(task.album.album_id)
try:
if not os.path.exists(file_path):
# 开始下载图片
await run_in_pool(packaged_download_album, option, task.album)
# 转化为加密pdf并压缩
await run_in_pool(zip_manga, task.album.id, "")
except Exception as e:
if isinstance(task.event, GroupMessageEvent):
await send_to_group(bot, task.event, f"下载失败:{str(e)}")
else:
await send_to_user(bot, task.event, f"下载失败:{str(e)}")
print(f"[jmcomic]下载失败:{str(e)}")
finally:
downloading_manga.remove(task.album.album_id)
if not os.path.exists(
os.path.join(os.path.dirname(__file__), f"info_image/{task.album.id}.jpg")
):
try:
# 生成封面图片
info_pic.generate(task.album)
except Exception as e:
if isinstance(task.event, GroupMessageEvent):
await send_to_group(bot, task.event, f"生成封面图片失败")
else:
await send_to_user(bot, task.event, f"生成封面图片失败")
print(f"[jmcomic]生成封面图片失败:{str(e)}")
delete_temp_file(task.album.album_id)
file_name = f"{task.album.id}{file_suffix}"
try:
try:
if isinstance(task.event, GroupMessageEvent):
await bot.send_group_msg(
group_id=task.event.group_id,
message=MessageSegment.image(
f"file:///{os.path.join(script_dir, f'info_image/{task.album.id}.jpg')}"
),
)
else:
await bot.send_private_msg(
user_id=task.event.user_id,
message=MessageSegment.image(
f"file:///{os.path.join(script_dir, f'info_image/{task.album.id}.jpg')}"
),
)
except Exception as e:
pass
if isinstance(task.event, GroupMessageEvent):
await bot.upload_group_file(
group_id=task.event.group_id, file=file_path, name=file_name
)
else:
await bot.upload_private_file(
user_id=task.event.user_id, file=file_path, name=file_name
)
except Exception as e:
if isinstance(task.event, GroupMessageEvent):
await send_to_group(bot, task.event, f"文件上传失败:{str(e)}")
else:
await send_to_user(bot, task.event, f"文件上传失败:{str(e)}")
print(f"[jmcomic]上传失败,文件路径: {file_path}")
except Exception as e:
if isinstance(task.event, GroupMessageEvent):
await send_to_group(bot, task.event, f"处理过程中出错:{str(e)}")
else:
await send_to_user(bot, task.event, f"处理过程中出错:{str(e)}")
print(f"[jmcomic]处理错误:{str(e)}")
@suffix.handle()
async def handle_suffix(
matcher: Matcher,
event: GroupMessageEvent | PrivateMessageEvent,
args: Message = CommandArg(),
):
global file_suffix, config, config_path
if isinstance(event, GroupMessageEvent):
if not event.group_id in whitelist:
return
args = args.extract_plain_text().strip().split(" ")
if len(args) > 0:
new_suffix = args[0]
if new_suffix == "" and len(args) == 1:
await matcher.finish(f"当前发送文件后缀为:{file_suffix}")
return
if new_suffix[0] != ".":
await matcher.finish("后缀必须以.开头")
return
# 更新全局变量
file_suffix = new_suffix
config["file_suffix"] = new_suffix
# 更新.json文件
with open(config_path, "w", encoding="utf-8") as f:
json.dump(config, f, ensure_ascii=False, indent=4)
await matcher.finish(f"修改发送文件后缀为:{file_suffix}")
else:
await matcher.finish(f"当前发送文件后缀为:{file_suffix}")
@image_set.handle()
async def handle_max_images(
matcher: Matcher,
event: GroupMessageEvent | PrivateMessageEvent,
args: Message = CommandArg(),
):
global max_image_count, config, config_path
if isinstance(event, GroupMessageEvent):
if not event.group_id in whitelist:
return
args = args.extract_plain_text().strip().split(" ")
if len(args) > 0:
new_count = args[0]
if new_count == "" and len(args) == 1:
await matcher.finish(f"当前单本最大图片数量为:{max_image_count}")
return
if not new_count.isdigit():
await matcher.finish("参数必须是数字")
return
# 更新全局变量
max_image_count = int(new_count)
config["max_image_count"] = int(new_count)
# 更新.json文件
with open(config_path, "w", encoding="utf-8") as f:
json.dump(config, f, ensure_ascii=False, indent=4)
await matcher.finish(f"修改单本最大图片数量为:{max_image_count}")
else:
await matcher.finish(f"当前单本最大图片数量为:{max_image_count}")
@negtags.handle()
async def handle_negtags(
matcher: Matcher,
event: GroupMessageEvent | PrivateMessageEvent,
args: Message = CommandArg(),
):
global negative_tags, config, config_path
if isinstance(event, GroupMessageEvent):
if not event.group_id in whitelist:
return
args = args.extract_plain_text().strip().split(" ")
if len(args) > 0:
command = args[0]
if command == "" and len(args) == 1:
await matcher.finish(f"当前负面标签为:{negative_tags}")
return
if command == "clear":
negative_tags = []
config["negative_tags"] = []
with open(config_path, "w", encoding="utf-8") as f:
json.dump(config, f, ensure_ascii=False, indent=4)
await matcher.finish("已清空负面标签")
return
elif command == "add":
new_tags = [zhconv.convert(tag, "zh-hans").lower() for tag in args[1:]]
if not new_tags:
await matcher.finish("请提供要添加的负面标签")
return
negative_tags.extend(new_tags)
negative_tags = list(set(negative_tags))
config["negative_tags"] = negative_tags
with open(config_path, "w", encoding="utf-8") as f:
json.dump(config, f, ensure_ascii=False, indent=4)
await matcher.finish(f"添加负面标签:{new_tags}")
return
elif command == "remove":
remove_tags = [zhconv.convert(tag, "zh-hans").lower() for tag in args[1:]]
if not remove_tags:
await matcher.finish("请提供要移除的负面标签")
return
for tag in remove_tags:
if tag in negative_tags:
negative_tags.remove(tag)
config["negative_tags"] = negative_tags
with open(config_path, "w", encoding="utf-8") as f:
json.dump(config, f, ensure_ascii=False, indent=4)
await matcher.finish(f"移除负面标签:{remove_tags}")
return
else:
await matcher.finish("未知命令")
return
else:
await matcher.finish(f"当前负面标签为:{negative_tags}")
@cooldown.handle()
async def handle_cooldown(
matcher: Matcher,
event: GroupMessageEvent | PrivateMessageEvent,
args: Message = CommandArg(),
):
global CD_SECONDS
if isinstance(event, GroupMessageEvent):
if not event.group_id in whitelist:
return
args = args.extract_plain_text().strip().split(" ")
if len(args) > 0:
new_cd = args[0]
if new_cd == "" and len(args) == 1:
await matcher.finish(f"当前CD时间为:{CD_SECONDS}秒")
return
if not new_cd.isdigit():
await matcher.finish("参数必须是数字")
return
# 更新全局变量
CD_SECONDS = int(new_cd)
config["cd_seconds"] = int(new_cd)
# 更新.json文件
with open(config_path, "w", encoding="utf-8") as f:
json.dump(config, f, ensure_ascii=False, indent=4)
await matcher.finish(f"修改CD时间为:{CD_SECONDS}秒")
else:
await matcher.finish(f"当前CD时间为:{CD_SECONDS}秒")
@allow.handle()
async def handle_whitelist(
matcher: Matcher,
event: GroupMessageEvent | PrivateMessageEvent,
args: Message = CommandArg(),
):
global whitelist
if isinstance(event, GroupMessageEvent):
if not event.group_id in whitelist:
return
args = args.extract_plain_text().strip().split(" ")
if len(args) > 0:
command = args[0]
if command == "" and len(args) == 1:
await matcher.finish(f"当前白名单群号为:{whitelist}")
return
if command == "clear":
whitelist = []
config["whitelist"] = []
with open(config_path, "w", encoding="utf-8") as f:
json.dump(config, f, ensure_ascii=False, indent=4)
await matcher.finish("已清空白名单")
return
elif command == "add":
new_list = args[1:]
if not new_list:
await matcher.finish("请提供要添加的群号")
return
for i, item in enumerate(new_list):
if not item.isdigit():
await matcher.finish("存在不合法群号")
return
new_list[i] = int(item)
whitelist.extend(new_list)
whitelist = list(set(whitelist))
config["whitelist"] = whitelist
with open(config_path, "w", encoding="utf-8") as f:
json.dump(config, f, ensure_ascii=False, indent=4)
await matcher.finish(f"添加白名单:{new_list}")
return
elif command == "remove":
remove_list = args[1:]
if not remove_list:
await matcher.finish("请提供要移除的群号")
return
for i, item in enumerate(remove_list):
if not item.isdigit():
await matcher.finish("存在不合法群号")
return
remove_list[i] = int(item)
for item in remove_list:
if item in whitelist:
whitelist.remove(item)
config["whitelist"] = whitelist
with open(config_path, "w", encoding="utf-8") as f:
json.dump(config, f, ensure_ascii=False, indent=4)
await matcher.finish(f"移除白名单:{remove_list}")
return
else:
await matcher.finish("未知命令")
return
else:
await matcher.finish(f"当前白名单为:{whitelist}")
@baneng.handle()
async def handle_baneng(
matcher: Matcher,
event: GroupMessageEvent | PrivateMessageEvent,
args: Message = CommandArg(),
):
global ban_english
if isinstance(event, GroupMessageEvent):
if not event.group_id in whitelist:
return
args = args.extract_plain_text().strip().split(" ")
if len(args) > 0:
commmand = args[0]
if commmand == "" and len(args) == 1:
await matcher.finish(f"当前是否排除欧美本:{ban_english}")
return
if commmand == "set":
ban_english = True
config["ban_english"] = True
with open(config_path, "w", encoding="utf-8") as f:
json.dump(config, f, ensure_ascii=False, indent=4)
await matcher.finish(f"已设置为排除欧美本")
elif commmand == "unset":
ban_english = False
config["ban_english"] = False
with open(config_path, "w", encoding="utf-8") as f:
json.dump(config, f, ensure_ascii=False, indent=4)
await matcher.finish(f"已设置为不排除欧美本")
else:
await matcher.finish(f"未知命令")
else:
await matcher.finish(f"当前是否排除欧美本:{ban_english}")
# 获取定时任务调度器
scheduler = require("nonebot_plugin_apscheduler").scheduler
async def update_database():
"""定时更新数据库的任务"""
global is_updating_db, db_utils
if is_updating_db:
print("[jmcomic] 数据库正在更新中,跳过本次更新")
return
try:
is_updating_db = True
print("[jmcomic] 开始更新数据库...")
await asyncio.to_thread(db_utils.update_db)
print("[jmcomic] 数据库更新完成")
except Exception as e:
print(f"[jmcomic] 数据库更新出错: {str(e)}")
finally:
is_updating_db = False