modified: main.py
deleted: tools/mail_tool.py
This commit is contained in:
@@ -11,6 +11,13 @@ import numpy as np
|
|||||||
import sqlite3
|
import sqlite3
|
||||||
from .tools.mail_tool import emaillog
|
from .tools.mail_tool import emaillog
|
||||||
from .tools.zhangdan_flask import runwebui,stop_server
|
from .tools.zhangdan_flask import runwebui,stop_server
|
||||||
|
import imaplib
|
||||||
|
from datetime import datetime
|
||||||
|
import email.utils
|
||||||
|
import email.header
|
||||||
|
import email
|
||||||
|
from lxml import html
|
||||||
|
import re
|
||||||
|
|
||||||
@register("astrbot_bdzxb_bill", "bd_bill", "账单统计", "1.0.0")
|
@register("astrbot_bdzxb_bill", "bd_bill", "账单统计", "1.0.0")
|
||||||
class MyPlugin(Star):
|
class MyPlugin(Star):
|
||||||
@@ -284,6 +291,113 @@ class MyPlugin(Star):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"文件下载失败 原因:{e}")
|
logger.error(f"文件下载失败 原因:{e}")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# 登录邮箱 下载文件
|
||||||
|
def emaillog(self):
|
||||||
|
logger.info(f" 开始执行 登录邮箱 {self.email_usr} 命令 ")
|
||||||
|
# 邮箱登录用户名
|
||||||
|
email_user = self.email_usr
|
||||||
|
# 邮箱授权码(非登录密码),用于IMAP协议认证
|
||||||
|
email_password = self.emcil_paswd
|
||||||
|
# IMAP服务器地址,Foxmail使用QQ邮箱的IMAP服务器
|
||||||
|
imap_server = self.emcil_imap
|
||||||
|
mail = imaplib.IMAP4_SSL(imap_server)
|
||||||
|
# 使用用户名和授权码登录邮箱
|
||||||
|
# try:
|
||||||
|
mail.login(email_user, email_password)
|
||||||
|
# 查询收件箱 只读模式
|
||||||
|
mail.select("inbox",readonly=True)
|
||||||
|
# 设定查询条件
|
||||||
|
for emlname in self.emcil_white:
|
||||||
|
from_clause = " OR ".join([f'FROM "{sender}"' for sender in emlname])
|
||||||
|
# 获取今天的日期,格式化为IMAP要求的格式(如"21-Jul-2025")
|
||||||
|
today = datetime.now().strftime("%d-%b-%Y")
|
||||||
|
today = today.encode('utf-8')
|
||||||
|
search_criteria = f'({from_clause} SINCE "{today}" HAS attachment)'
|
||||||
|
logger.info(f"严格模式: 只处理今天({today})收到的邮件")
|
||||||
|
logger.info(f"发件人白名单: {self.emcil_white}")
|
||||||
|
logger.info(f"搜索条件: {search_criteria}")
|
||||||
|
|
||||||
|
# 执行IMAP搜索命令
|
||||||
|
status, messages = mail.search(None, search_criteria)
|
||||||
|
if status != "OK": # 检查搜索是否成功
|
||||||
|
raise Exception("邮件搜索失败")
|
||||||
|
|
||||||
|
# 获取邮件ID列表并只取最后20个(最新的20封)
|
||||||
|
mail_ids = messages[0].split()[-20:]
|
||||||
|
logger.info(f"获取到邮件数量: {len(messages)}")
|
||||||
|
filenames = ""
|
||||||
|
for mail_id in reversed(mail_ids):
|
||||||
|
# 获取邮件完整内容(RFC822格式)
|
||||||
|
status, msg_data = mail.fetch(mail_id, "(RFC822)")
|
||||||
|
if status != "OK": # 如果获取失败则跳过
|
||||||
|
continue
|
||||||
|
# 解析邮件内容为Message对象
|
||||||
|
email_message = email.message_from_bytes(msg_data[0][1])
|
||||||
|
# 验证发件人是否在白名单中
|
||||||
|
# ========================
|
||||||
|
from_header = email.utils.parseaddr(email_message['From'])[1] # 解析发件人邮箱
|
||||||
|
# logger.info(f"邮件发件人: {from_header}")
|
||||||
|
if from_header not in self.emcil_white: # 严格检查白名单
|
||||||
|
# logger.info(f"跳过非白名单发件人: {from_header}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 遍历邮件各部分
|
||||||
|
# =============
|
||||||
|
for part in email_message.walk(): # 递归遍历邮件所有部分
|
||||||
|
if part.get_content_maintype() == "multipart": # 跳过multipart容器部分
|
||||||
|
continue
|
||||||
|
filename = part.get_filename() # 获取附件文件名
|
||||||
|
# 获取正文
|
||||||
|
maildata = part.get_payload(decode=True)
|
||||||
|
# 微信导出文件需要点击链接下载 检测正文中是否包含链接 解析正文内容 筛选a标签获取下载链接
|
||||||
|
etree = html.etree
|
||||||
|
tree = etree.HTML(maildata)
|
||||||
|
# logger.info(f"邮件正文{maildata}")
|
||||||
|
a_tags = tree.xpath('//a')
|
||||||
|
mail_date = email.utils.parsedate_to_datetime(email_message['Date']) if email_message['Date'] else None
|
||||||
|
mail_date = re.sub(r"[^\u4e00-\u9fa5a-zA-Z0-9]", "", str(mail_date))
|
||||||
|
for a_tag in a_tags:
|
||||||
|
# 获取到url后下载url
|
||||||
|
urlt = a_tag.get("href")
|
||||||
|
if urlt:
|
||||||
|
try:
|
||||||
|
filenames += self.download_file(urlt=urlt,filenamett=f"{from_header}{mail_date}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"URl 文件下载失败:{e}")
|
||||||
|
# 处理无文件名的情况
|
||||||
|
# ================
|
||||||
|
if not filename: # 如果附件没有文件名
|
||||||
|
# 按日期发件人生成附件名
|
||||||
|
filename = f"{from_header}{mail_date}.zip"
|
||||||
|
# 文件名处理
|
||||||
|
# =========
|
||||||
|
# 解码邮件头中的文件名
|
||||||
|
try:
|
||||||
|
decoded_name = email.header.decode_header(filename)[0][0]
|
||||||
|
if isinstance(decoded_name, bytes):
|
||||||
|
decoded_name = decoded_name.decode()
|
||||||
|
except Exception as e:
|
||||||
|
# 文件名解析失败 自动生成新名字
|
||||||
|
decoded_name,filename = f"{from_header}{mail_date}.zip"
|
||||||
|
today_str = datetime.now().strftime("%Y%m%d") # 获取当前日期字符串
|
||||||
|
base_name, ext = os.path.splitext(decoded_name) # 拆分文件名和扩展名
|
||||||
|
# 生成新文件名格式:YYYYMMDD-原文件名.zip
|
||||||
|
new_filename = f"{today_str}-{base_name}{ext}"
|
||||||
|
if filename:
|
||||||
|
new_filename = filename
|
||||||
|
# 拼接完整的文件保存路径
|
||||||
|
filepath = os.path.join(StarTools.get_data_dir("astrbot_bdzxb_bill"), new_filename)
|
||||||
|
if new_filename not in self.no_file:
|
||||||
|
# 附件保存处理
|
||||||
|
# 以二进制模式保存附件
|
||||||
|
with open(filepath, "wb") as f:
|
||||||
|
f.write(part.get_payload(decode=True)) # 解码并写入附件内容
|
||||||
|
# logger.info(f"下载附件: {filename} -> {new_filename}")
|
||||||
|
filenames += new_filename+"\n"
|
||||||
|
return "获取成功:"+filenames
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async def terminate(self):
|
async def terminate(self):
|
||||||
"""可选择实现异步的插件销毁方法,当插件被卸载/停用时会调用。"""
|
"""可选择实现异步的插件销毁方法,当插件被卸载/停用时会调用。"""
|
||||||
|
|||||||
@@ -1,117 +0,0 @@
|
|||||||
import imaplib
|
|
||||||
from datetime import datetime
|
|
||||||
import email.utils
|
|
||||||
import email.header
|
|
||||||
from astrbot.api import logger
|
|
||||||
import email
|
|
||||||
from lxml import html
|
|
||||||
import re
|
|
||||||
import os
|
|
||||||
from astrbot.api.star import StarTools
|
|
||||||
|
|
||||||
|
|
||||||
# 登录邮箱 下载文件
|
|
||||||
def emaillog(self):
|
|
||||||
logger.info(f" 开始执行 登录邮箱 {self.email_usr} 命令 ")
|
|
||||||
# 邮箱登录用户名
|
|
||||||
email_user = self.email_usr
|
|
||||||
# 邮箱授权码(非登录密码),用于IMAP协议认证
|
|
||||||
email_password = self.emcil_paswd
|
|
||||||
# IMAP服务器地址,Foxmail使用QQ邮箱的IMAP服务器
|
|
||||||
imap_server = self.emcil_imap
|
|
||||||
mail = imaplib.IMAP4_SSL(imap_server)
|
|
||||||
# 使用用户名和授权码登录邮箱
|
|
||||||
# try:
|
|
||||||
mail.login(email_user, email_password)
|
|
||||||
# 查询收件箱 只读模式
|
|
||||||
mail.select("inbox",readonly=True)
|
|
||||||
# 设定查询条件
|
|
||||||
for emlname in self.emcil_white:
|
|
||||||
from_clause = " OR ".join([f'FROM "{sender}"' for sender in emlname])
|
|
||||||
# 获取今天的日期,格式化为IMAP要求的格式(如"21-Jul-2025")
|
|
||||||
today = datetime.now().strftime("%d-%b-%Y")
|
|
||||||
today = today.encode('utf-8')
|
|
||||||
search_criteria = f'({from_clause} SINCE "{today}" HAS attachment)'
|
|
||||||
logger.info(f"严格模式: 只处理今天({today})收到的邮件")
|
|
||||||
logger.info(f"发件人白名单: {self.emcil_white}")
|
|
||||||
logger.info(f"搜索条件: {search_criteria}")
|
|
||||||
|
|
||||||
# 执行IMAP搜索命令
|
|
||||||
status, messages = mail.search(None, search_criteria)
|
|
||||||
if status != "OK": # 检查搜索是否成功
|
|
||||||
raise Exception("邮件搜索失败")
|
|
||||||
|
|
||||||
# 获取邮件ID列表并只取最后20个(最新的20封)
|
|
||||||
mail_ids = messages[0].split()[-20:]
|
|
||||||
logger.info(f"获取到邮件数量: {len(messages)}")
|
|
||||||
filenames = ""
|
|
||||||
for mail_id in reversed(mail_ids):
|
|
||||||
# 获取邮件完整内容(RFC822格式)
|
|
||||||
status, msg_data = mail.fetch(mail_id, "(RFC822)")
|
|
||||||
if status != "OK": # 如果获取失败则跳过
|
|
||||||
continue
|
|
||||||
# 解析邮件内容为Message对象
|
|
||||||
email_message = email.message_from_bytes(msg_data[0][1])
|
|
||||||
# 验证发件人是否在白名单中
|
|
||||||
# ========================
|
|
||||||
from_header = email.utils.parseaddr(email_message['From'])[1] # 解析发件人邮箱
|
|
||||||
# logger.info(f"邮件发件人: {from_header}")
|
|
||||||
if from_header not in self.emcil_white: # 严格检查白名单
|
|
||||||
# logger.info(f"跳过非白名单发件人: {from_header}")
|
|
||||||
continue
|
|
||||||
|
|
||||||
# 遍历邮件各部分
|
|
||||||
# =============
|
|
||||||
for part in email_message.walk(): # 递归遍历邮件所有部分
|
|
||||||
if part.get_content_maintype() == "multipart": # 跳过multipart容器部分
|
|
||||||
continue
|
|
||||||
filename = part.get_filename() # 获取附件文件名
|
|
||||||
# 获取正文
|
|
||||||
maildata = part.get_payload(decode=True)
|
|
||||||
# 微信导出文件需要点击链接下载 检测正文中是否包含链接 解析正文内容 筛选a标签获取下载链接
|
|
||||||
etree = html.etree
|
|
||||||
tree = etree.HTML(maildata)
|
|
||||||
# logger.info(f"邮件正文{maildata}")
|
|
||||||
a_tags = tree.xpath('//a')
|
|
||||||
mail_date = email.utils.parsedate_to_datetime(email_message['Date']) if email_message['Date'] else None
|
|
||||||
mail_date = re.sub(r"[^\u4e00-\u9fa5a-zA-Z0-9]", "", str(mail_date))
|
|
||||||
for a_tag in a_tags:
|
|
||||||
# 获取到url后下载url
|
|
||||||
urlt = a_tag.get("href")
|
|
||||||
if urlt:
|
|
||||||
try:
|
|
||||||
filenames += self.download_file(urlt=urlt,filenamett=f"{from_header}{mail_date}")
|
|
||||||
except Exception as e:
|
|
||||||
logger.debug(f"URl 文件下载失败:{e}")
|
|
||||||
# 处理无文件名的情况
|
|
||||||
# ================
|
|
||||||
if not filename: # 如果附件没有文件名
|
|
||||||
# 按日期发件人生成附件名
|
|
||||||
filename = f"{from_header}{mail_date}.zip"
|
|
||||||
# 文件名处理
|
|
||||||
# =========
|
|
||||||
# 解码邮件头中的文件名
|
|
||||||
try:
|
|
||||||
decoded_name = email.header.decode_header(filename)[0][0]
|
|
||||||
if isinstance(decoded_name, bytes):
|
|
||||||
decoded_name = decoded_name.decode()
|
|
||||||
except Exception as e:
|
|
||||||
# 文件名解析失败 自动生成新名字
|
|
||||||
decoded_name,filename = f"{from_header}{mail_date}.zip"
|
|
||||||
today_str = datetime.now().strftime("%Y%m%d") # 获取当前日期字符串
|
|
||||||
base_name, ext = os.path.splitext(decoded_name) # 拆分文件名和扩展名
|
|
||||||
# 生成新文件名格式:YYYYMMDD-原文件名.zip
|
|
||||||
new_filename = f"{today_str}-{base_name}{ext}"
|
|
||||||
if filename:
|
|
||||||
new_filename = filename
|
|
||||||
# 拼接完整的文件保存路径
|
|
||||||
filepath = os.path.join(StarTools.get_data_dir("astrbot_bdzxb_bill"), new_filename)
|
|
||||||
if new_filename not in self.no_file:
|
|
||||||
# 附件保存处理
|
|
||||||
# 以二进制模式保存附件
|
|
||||||
with open(filepath, "wb") as f:
|
|
||||||
f.write(part.get_payload(decode=True)) # 解码并写入附件内容
|
|
||||||
# logger.info(f"下载附件: {filename} -> {new_filename}")
|
|
||||||
filenames += new_filename+"\n"
|
|
||||||
return "获取成功:"+filenames
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user