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