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 6c2cd9b..0000000 Binary files a/src/resources/image/jm/temp.jpg and /dev/null differ