From 7968114e125806af828dbe5f53ac4c8fb312b725 Mon Sep 17 00:00:00 2001 From: SlyAimer <2289782085@qq.com> Date: Wed, 2 Apr 2025 16:36:17 +0800 Subject: [PATCH] =?UTF-8?q?fix(ai=5Fchat):=20=E7=A7=BB=E9=99=A4=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E4=BB=A3=E7=A0=81=20feat(jm):=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E9=82=AE=E4=BB=B6=E5=8F=91=E9=80=81=E5=8A=9F=E8=83=BD=E5=8F=8A?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E7=AE=A1=E7=90=86=20feat(config):=20?= =?UTF-8?q?=E6=96=B0=E5=A2=9Ejm=E9=85=8D=E7=BD=AE=E6=96=87=E4=BB=B6=20feat?= =?UTF-8?q?(email):=20=E6=96=B0=E5=A2=9E=E9=82=AE=E4=BB=B6=E5=8F=91?= =?UTF-8?q?=E9=80=81=E6=A8=A1=E5=9D=97=20chore(.gitignore):=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0jm=E5=9B=BE=E7=89=87=E7=9B=AE=E5=BD=95=E5=BF=BD?= =?UTF-8?q?=E7=95=A5=20chore(requirements):=20=E6=B7=BB=E5=8A=A0aiosmtplib?= =?UTF-8?q?=E4=BE=9D=E8=B5=96=20refactor(jm=5Fcomic):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E4=B8=8B=E8=BD=BD=E9=80=BB=E8=BE=91=E6=94=B9=E4=B8=BA=E9=82=AE?= =?UTF-8?q?=E4=BB=B6=E5=8F=91=E9=80=81=20refactor(path=5Fconfig):=20?= =?UTF-8?q?=E6=9B=B4=E6=96=B0jm=E9=85=8D=E7=BD=AE=E8=B7=AF=E5=BE=84=20refa?= =?UTF-8?q?ctor(jm=5Fdownload):=20=E9=80=82=E9=85=8D=E6=96=B0=E4=B8=8B?= =?UTF-8?q?=E8=BD=BD=E9=80=BB=E8=BE=91=E5=B9=B6=E6=B7=BB=E5=8A=A0=E9=82=AE?= =?UTF-8?q?=E7=AE=B1=E9=AA=8C=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 +- requirements.txt | 1 + src/clover_email/send_email.py | 191 ++++++++++++++++++++++++++++++++ src/clover_jm/disguise_pdf.py | 79 ++++++++++++- src/clover_jm/jm_comic.py | 76 +++++++++---- src/clover_openai/ai_chat.py | 3 - src/configs/jm_config.yml | 3 + src/configs/path_config.py | 5 +- src/plugins/jm_download.py | 27 +++-- src/resources/image/jm/temp.jpg | Bin 2790 -> 0 bytes 10 files changed, 347 insertions(+), 41 deletions(-) create mode 100644 src/clover_email/send_email.py create mode 100644 src/configs/jm_config.yml delete mode 100644 src/resources/image/jm/temp.jpg diff --git a/.gitignore b/.gitignore index 476e7f3..d5938c6 100644 --- a/.gitignore +++ b/.gitignore @@ -26,4 +26,5 @@ bili.cookie /src/resources/image/yuc_wiki/* src/clover_lightnovel/wenku8.cookie src/clover_lightnovel/output1.html -*.pyc \ No newline at end of file +*.pyc +/src/resources/image/jm/* \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index ba0c4ee..513bd9d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -29,6 +29,7 @@ paramiko commonX jmcomic natsort +aiosmtplib requests pillow diff --git a/src/clover_email/send_email.py b/src/clover_email/send_email.py new file mode 100644 index 0000000..badf441 --- /dev/null +++ b/src/clover_email/send_email.py @@ -0,0 +1,191 @@ +import os +from email.mime.multipart import MIMEMultipart +from email.mime.application import MIMEApplication +from email.mime.text import MIMEText +import aiosmtplib +from src.configs.api_config import google_smtp_server,google_email,google_password +from src.configs.api_config import qq_smtp_server,qq_email,qq_password + +# 发送内容 +html = """ + + + + + + 健康小贴士 + + + +
+

可别🦌死了

+

作案前记得洗手,适度使用手动挡,别让发动机过热抛锚。

+ +
+ 健康小贴士: 适度有益健康,过度可能影响日常生活。保持良好卫生习惯,合理安排时间哦~ +
+
+ + + """ +async def send_email_by_google(receiver_email: str, file_path: str): + """发送单个文件附件邮件(Google版)""" + msg = MIMEMultipart() + msg["From"] = google_email + msg["To"] = receiver_email + msg["Subject"] = "您的快递已送达" + msg.attach(MIMEText(html, "html", "utf-8")) + + try: + # 验证文件存在性 + if not os.path.isfile(file_path): + print(f"文件不存在:{file_path}") + return False + + # 添加单个文件附件 + file_name = os.path.basename(file_path) + with open(file_path, "rb") as f: + attachment = MIMEApplication(f.read()) + attachment.add_header( + "Content-Disposition", + "attachment", + filename=file_name + ) + msg.attach(attachment) + + async with aiosmtplib.SMTP( + hostname=google_smtp_server, + port=465, + timeout=60, + use_tls=True + ) as server: + await server.login(google_email, google_password) + await server.send_message(msg) + print("文件邮件发送成功!") + return True + + except Exception as e: + print(f"邮件发送失败: {str(e)}") + return False + +async def send_email_by_qq(receiver_email: str, file_path: str): + """发送单个文件附件邮件(QQ版)""" + msg = MIMEMultipart() + msg["From"] = qq_email + msg["To"] = receiver_email + msg["Subject"] = "您的快递已送达" + msg.attach(MIMEText(html, "html", "utf-8")) + + try: + if not os.path.exists(file_path): + print(f"文件不存在:{file_path}") + return False + + # 添加附件 + file_name = os.path.basename(file_path) + with open(file_path, "rb") as f: + attachment = MIMEApplication(f.read()) + attachment.add_header( + "Content-Disposition", + "attachment", + filename=file_name + ) + msg.attach(attachment) + + async with aiosmtplib.SMTP( + hostname=qq_smtp_server, + port=465, + use_tls=True, + timeout=30 + ) as server: + await server.login(qq_email, qq_password) + await server.send_message(msg) + print("QQ文件邮件发送成功!") + return True + except Exception as e: + print(f"QQ邮件发送失败: {str(e)}") + return False diff --git a/src/clover_jm/disguise_pdf.py b/src/clover_jm/disguise_pdf.py index 20d9de6..9d5bc0f 100644 --- a/src/clover_jm/disguise_pdf.py +++ b/src/clover_jm/disguise_pdf.py @@ -1,5 +1,7 @@ import os +import asyncio import zipfile +from pathlib import Path from PIL import Image from natsort import natsorted @@ -16,7 +18,6 @@ async def webp_to_pdf(input_folder, output_pdf): if not webp_files: print("未找到WebP图片") - # raise ValueError("未找到WebP图片") images = [] for webp_file in webp_files: @@ -34,20 +35,53 @@ async def webp_to_pdf(input_folder, output_pdf): if not images: print("无有效图片") - # raise ValueError("无有效图片") images[0].save( output_pdf, save_all=True, append_images=images[1:], optimize=True, - quality=85 + quality=80 ) - return output_pdf +async def batch_convert_subfolders(base_dir,output_dir): + """ + 批量转换指定目录下所有子文件夹中的WebP图片为独立PDF + :param base_dir: 要扫描的根目录,默认当前目录 + :param output_dir: PDF输出目录 + """ + subfolders = [ + f for f in os.listdir(base_dir) + if os.path.isdir(os.path.join(base_dir, f)) + and not f.startswith('.') + and f != os.path.basename(output_dir) + ] + if not subfolders: + print("未找到有效子文件夹") + return + + tasks = [] + for folder in subfolders: + input_path = os.path.join(base_dir, folder) + output_pdf = os.path.join(output_dir, f"{folder}.pdf") + tasks.append( + webp_to_pdf(input_path, output_pdf) + ) + + results = await asyncio.gather(*tasks, return_exceptions=True) + success = 0 + for folder, result in zip(subfolders, results): + if isinstance(result, Exception): + print(f"转换失败 [{folder}]: {str(result)}") + else: + print(f"成功转换: {folder} -> {result}") + success += 1 async def zip_pdf(pdf_path, zip_path): + """ + 压缩单文件 + """ try: with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf: arcname = os.path.basename(pdf_path) @@ -57,9 +91,46 @@ async def zip_pdf(pdf_path, zip_path): async def merge_files(jpg_path, zip_path, output_path): + """ + 将PDF伪装成图片 + """ try: with open(jpg_path, 'rb') as jpg_file, open(zip_path, 'rb') as zip_file, open(output_path,'wb') as output_file: output_file.write(jpg_file.read()) output_file.write(zip_file.read()) except Exception as e: print(f"合并文件时出错: {e}") + +async def folder_zip(folder_path, jm_zip_path): + """ + 异步压缩整个文件夹到指定路径 + :param folder_path: 需要压缩的文件夹路径 + :param jm_zip_path: 输出的zip文件路径 + :return: 压缩成功返回True,否则返回False + """ + try: + Path(jm_zip_path).parent.mkdir(parents=True, exist_ok=True) + + def sync_zip(): + with zipfile.ZipFile(jm_zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf: + + for root, dirs, files in os.walk(folder_path): + + relative_path = Path(root).relative_to(folder_path) + + for dir_name in dirs: + abs_dir = os.path.join(root, dir_name) + zipf.write(abs_dir, arcname=str(relative_path / dir_name)) + + for file in files: + file_path = os.path.join(root, file) + arcname = str(relative_path / file) + zipf.write(file_path, arcname=arcname) + + loop = asyncio.get_running_loop() + await loop.run_in_executor(None, sync_zip) + print(f"成功压缩文件夹到: {jm_zip_path}") + return True + except Exception as e: + print(f"压缩文件夹失败: {str(e)}") + return False diff --git a/src/clover_jm/jm_comic.py b/src/clover_jm/jm_comic.py index 3859ce7..13253a1 100644 --- a/src/clover_jm/jm_comic.py +++ b/src/clover_jm/jm_comic.py @@ -1,28 +1,62 @@ +import yaml import jmcomic from src.clover_jm.disguise_pdf import * -from src.configs.path_config import jm_path -from src.clover_image.delete_file import delete_file_batch,delete_folder +from concurrent.futures import ThreadPoolExecutor +from src.configs.path_config import jm_path,jm_config_path +from src.clover_image.delete_file import delete_folder,delete_file +from src.clover_email.send_email import send_email_by_google,send_email_by_qq -async def download_jm(album_id: str| None): +# 创建线程池 +jm_executor = ThreadPoolExecutor(max_workers=5) +jm_executor.submit(lambda: None).result() - album_detail,downloader = jmcomic.download_album(album_id) - - original_path = os.getcwd()+f"/{album_detail.title}" - # 将图片转换为PDF - await webp_to_pdf(original_path,jm_path +f"{album_id}.pdf") - pdf_file = jm_path + f"{album_id}.pdf" - jpg_file = jm_path + 'temp.jpg' - zip_file = jm_path + "resume.zip" - output_file = jm_path +"merged.jpg" - - if os.path.exists(pdf_file) and os.path.exists(jpg_file): - await zip_pdf(pdf_file, zip_file) - await merge_files(jpg_file, zip_file, output_file) - - await delete_file_batch([zip_file, pdf_file]) - await delete_folder(original_path) +async def download_jm(album_id: str| None,receiver_email: str| None): + # 修改配置文件的下载路径 + source_path = await get_jm_config(receiver_email) + option = jmcomic.JmOption.from_file(jm_config_path) + # 还原配置文件 + await recover_jm_config(source_path) + #调用JM下载api + album_detail,downloader = await asyncio.get_event_loop().run_in_executor(jm_executor,jmcomic.download_album,album_id,option) + if album_detail.title is None: + return "下载失败,请检查JM ID 是否正确" + # 创建变量 + folder_path = f"{jm_path}{receiver_email}" + zip_path = f"{jm_path}{album_detail.title}.zip" + # 压缩文件 + zip_status = await folder_zip(folder_path,zip_path) + if not zip_status: + await delete_folder(folder_path) + return "压缩文件失败" + # 发送邮件 + send_status = await send_email_by_qq(receiver_email,zip_path) + if send_status: + # 删除文件 + await delete_folder(folder_path) + await delete_file(zip_path) + return "发送成功,请注意查收\n如遇邮箱接收不到,请检查发送的邮箱是否正确,或者是否在垃圾箱" else: - print("PDF文件或JPG文件不存在,请检查文件路径。") + await delete_folder(folder_path) + await delete_file(zip_path) + return "发送邮件失败,请重试!" - return output_file +async def get_jm_config(receiver_email: str): + + with open(jm_config_path, 'r', encoding='utf-8') as f: + config = yaml.safe_load(f) + source_path = config['dir_rule']['base_dir'] + new_base_dir = str(Path(source_path) / receiver_email) + config['dir_rule']['base_dir'] = new_base_dir + with open(jm_config_path, 'w', encoding='utf-8') as f: + yaml.dump(config, f, sort_keys=False, allow_unicode=True) + return source_path + +async def recover_jm_config(source_path : str): + + with open(jm_config_path, 'r', encoding='utf-8') as f: + config = yaml.safe_load(f) + new_base_dir = str(Path(source_path)) + config['dir_rule']['base_dir'] = new_base_dir + with open(jm_config_path, 'w', encoding='utf-8') as f: + yaml.dump(config, f, sort_keys=False, allow_unicode=True) \ No newline at end of file diff --git a/src/clover_openai/ai_chat.py b/src/clover_openai/ai_chat.py index 93ac97b..846c49a 100644 --- a/src/clover_openai/ai_chat.py +++ b/src/clover_openai/ai_chat.py @@ -70,6 +70,3 @@ async def silicon_flow(group_openid, content): await GroupChatRole.save_chat_history(group_openid, {"role": "assistant", "content": reply_content}) return reply_content - -if __name__ == '__main__': - print(deepseek_chat("你拽什么啊?")) diff --git a/src/configs/jm_config.yml b/src/configs/jm_config.yml new file mode 100644 index 0000000..662e77b --- /dev/null +++ b/src/configs/jm_config.yml @@ -0,0 +1,3 @@ +dir_rule: + base_dir: src\resources\image\jm + rule: Bd_Pname diff --git a/src/configs/path_config.py b/src/configs/path_config.py index 4cd5648..06afcd7 100644 --- a/src/configs/path_config.py +++ b/src/configs/path_config.py @@ -31,7 +31,7 @@ os.makedirs(font_path, exist_ok=True) # 临时数据路径 temp_path = path + '/temp/' os.makedirs(temp_path, exist_ok=True) -# JM发送 图片模板 +# JM下载位置 jm_path = path + '/image/jm/' os.makedirs(jm_path, exist_ok=True) # 日志路径 @@ -41,7 +41,8 @@ os.makedirs(log_path, exist_ok=True) video_path = path+'/video/' os.makedirs(video_path, exist_ok=True) - +#jm配置文件路径 +jm_config_path = os.getcwd()+'/src/configs/jm_config.yml' # # 语音路径 # RECORD_PATH = Path() / "src" / "resources" / "record" diff --git a/src/plugins/jm_download.py b/src/plugins/jm_download.py index 59013f8..7c580cd 100644 --- a/src/plugins/jm_download.py +++ b/src/plugins/jm_download.py @@ -1,19 +1,26 @@ -from pathlib import Path +import re from nonebot.rule import to_me from nonebot.plugin import on_command -from nonebot.adapters.qq import MessageSegment,MessageEvent +from nonebot.adapters.qq import MessageEvent from src.clover_jm.jm_comic import download_jm -from src.clover_image.delete_file import delete_file jm = on_command("jm", rule=to_me(), priority=10,block=False) @jm.handle() async def handle_function(message: MessageEvent): - await jm.send("正在下载中,请稍等") - key = message.get_plaintext().replace("/jm", "").strip(" ") - if key == "": - await jm.finish("请输入jm的id") + values = message.get_plaintext().replace("/jm", "").split(" ") - output_file = await download_jm(key) - await jm.send(MessageSegment.file_image(Path(output_file))) - await delete_file(output_file) + if 3 > len(values) > 4: + await jm.finish("请输入正确的格式 /jm+id 或 /jm+id+邮箱号") + else: + if not validate_email(values[2]): + await jm.finish("邮箱格式不正确!") + + await jm.send("正在发送中,请稍等~") + msg = await download_jm(album_id = values[1],receiver_email = values[2]) + await jm.finish(msg) + +def validate_email(email: str) -> bool: + """验证邮箱格式是否合法""" + EMAIL_REGEX = r"^[a-zA-Z0-9._%+-]+@([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$" + return re.fullmatch(EMAIL_REGEX, email) is not None \ No newline at end of file diff --git a/src/resources/image/jm/temp.jpg b/src/resources/image/jm/temp.jpg deleted file mode 100644 index 6c2cd9bc68599f1c23cabe0933a5021b1a89ebf4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2790 zcmbW1e>~Is9>>44&Ddy-kR>9&ij~5sgV)QPCk zPuu!2?PQ{(IL*&&2W_s@BHIXe zt?C`HI>;~34}d@*zyY-ZDluT;n@v6n03jj3W&i-R)HOW-s?H#4`$H-b-~(ttAS?c- zpm6AlG+{6(9HEIotb~>}N?S_{sf9owb&*Jvj#>!qRcKwERV(?*AuHo6Q)<`ILTIgY z{BKhI0${WN6QBeNu>dqM5GV$sY6r~JyTa8(D^Pz5q5*}$HPtI2QR)WPYW4b1sD^r3 zI1HxlKBKM!FbrJJe4Cf1e%L{T#ql-U&z4`;+UR}%xxrq6%+m2tTCO(I&}gkO*2>z( zcGG4jXS|E+j-5VxeEs|b0)vR*5s{>*ebI*#jvP%)A}6P3WS+>%K6xrH|NDZ%b4A4! zl@~5nUHb8->Ys1a)!%GrymkA*!xk2s^XT!DHh%kyj?S0AcJ=o44-5{y9Ttiu7X*O*3+pek|KP%?xir*o07I;BK{T?|0mZ=J=G!#&yuuI% zkLz1(KdZIIyZrk7=h_<`_sR?or3sLRmQG?T`3l+}Wd9vl?*B#h7udhKgn$kdqJBIm z2Jirum;TXt>ze=hoGU|o57|DCQsLIN6rx!8$<$p|Sdj6_l@&{h=w(qEOJ#gxmX;nI z6KGN$RT;`X@~!wlfi~3ffS8>p_CWBG39CT6Q1Eh7v?8<5=?o%379@qcuT=_&iep1@ z(%pXWu$04hMM36O@%UW__6`WTyYeil_ue3+?_Zm?9GkprdJ!x?sRH0|kRkInLz0cm z*eu(M4;b-}jB=C)x>Fw{+{ynS>*ttovc zy{8uIw!Vp6RfCixa_3U$%KZD1<^G>As9UJCN54U@|5ohq8>`T6yDNzCquAaT&rAd$F$M&c4>a` z;>8Hq zI#Jd#n(LX40o|E~TK1C%I&7iyM=O)=cFmkM-NFS$Vlz~fPop9jym*~|C;N${&ku~3 zk3|ev4;hreQPu%UX%HvNVuNJ%p~zDOJqjd&X0c={ArF2tPeTNJdA-jAKuHsNC~egK5yQko-7p9@e|Cm}H9Gg(G)C_2D0?_B;^qa*(vl~d zp1IcNJB)>--$GCOayasE2Y!kzXBSRfWB}7)QuM?D2&wyB)>DFZ8#EjI9n6 z&*Rasy(~FC5zlDG3&V-lVe1KVbG3S`7rf#LdDD}J-Kh%; z)mJWU-~W@PwV7=rS>v|-!ARF}PmM-{PZvTF-EmkIkek|&@4Txy{Tr}bo{C-{Ti>4| z=_us)O1CwSv=c`+doP7O@cK68SY!yBKXH#EVsSev*FCS51(u;dX3hU9;T4hx5D@MgPYUxi+pO?Z z-@Z#>1?DvsMtCqJ10n*&N1B# z^*8l=3ti7gUPhG1UA$qY6O~fxXQCnphlxg-e>6-V3OKR)2Rn-KC8#= zafv(V8!e>VN@$_9zI<7@dcQa0UTP#PuR)9+&heAAHk6r8lH}IBCWSwl6gB9)tFB^p zY9b*3w;`Nio;xbsvF}i+YyXj&K(}A(tM~2RTnxOFndLRpO+bBL+46{z+CgV~PvAb%@jK1*Uz$(ZUPG&f{|Rxo1bqMi