nonebot-plugin-mail 0.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nonebot_plugin_mail/__init__.py +30 -0
- nonebot_plugin_mail/config.py +29 -0
- nonebot_plugin_mail/constants.py +18 -0
- nonebot_plugin_mail/data/__init__.py +4 -0
- nonebot_plugin_mail/data/fallback_contacts.py +48 -0
- nonebot_plugin_mail/data/fallback_contacts_privacy.py +25 -0
- nonebot_plugin_mail/handlers/__init__.py +4 -0
- nonebot_plugin_mail/handlers/mail.py +69 -0
- nonebot_plugin_mail/handlers/query.py +48 -0
- nonebot_plugin_mail/handlers/receive.py +69 -0
- nonebot_plugin_mail/handlers/recognize.py +134 -0
- nonebot_plugin_mail/handlers/send.py +243 -0
- nonebot_plugin_mail/handlers/utils.py +44 -0
- nonebot_plugin_mail/models.py +53 -0
- nonebot_plugin_mail/services/__init__.py +4 -0
- nonebot_plugin_mail/services/contacts.py +157 -0
- nonebot_plugin_mail/services/images.py +79 -0
- nonebot_plugin_mail/services/matcher.py +57 -0
- nonebot_plugin_mail/services/notion.py +229 -0
- nonebot_plugin_mail/services/recognizer.py +191 -0
- nonebot_plugin_mail/services/render.py +115 -0
- nonebot_plugin_mail/services/rules.py +165 -0
- nonebot_plugin_mail-0.7.0.dist-info/METADATA +126 -0
- nonebot_plugin_mail-0.7.0.dist-info/RECORD +27 -0
- nonebot_plugin_mail-0.7.0.dist-info/WHEEL +5 -0
- nonebot_plugin_mail-0.7.0.dist-info/licenses/LICENSE +674 -0
- nonebot_plugin_mail-0.7.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# @Version : 0.7.0
|
|
4
|
+
# 规划备注:插件入口:PluginMetadata、注册所有 handlers
|
|
5
|
+
|
|
6
|
+
from nonebot.plugin import PluginMetadata
|
|
7
|
+
from .config import MailConfig
|
|
8
|
+
|
|
9
|
+
__plugin_meta__ = PluginMetadata(
|
|
10
|
+
name="nonebot-plugin-mail",
|
|
11
|
+
description="QQ 邮件登记机器人插件,支持 Notion 邮件登记、查询、签收和群聊信封图片智能识别。",
|
|
12
|
+
usage=(
|
|
13
|
+
"/mail contacts 查看联系人表\n"
|
|
14
|
+
"/mail records 查看邮件记录\n"
|
|
15
|
+
"寄信 进入人工寄件登记流程\n"
|
|
16
|
+
"查询 查询最近 7 天邮件\n"
|
|
17
|
+
"签收 查询并签收未签收邮件\n"
|
|
18
|
+
"识别信件 使用群聊图片智能识别并登记"
|
|
19
|
+
),
|
|
20
|
+
type="application",
|
|
21
|
+
homepage="https://github.com/WhyPilotXia/nonebot_plugin_mail",
|
|
22
|
+
config=MailConfig,
|
|
23
|
+
supported_adapters={"~onebot.v11"},
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
from .handlers import mail as mail
|
|
27
|
+
from .handlers import query as query
|
|
28
|
+
from .handlers import receive as receive
|
|
29
|
+
from .handlers import recognize as recognize
|
|
30
|
+
from .handlers import send as send
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# @Version : 0.7.0
|
|
4
|
+
# 规划备注:配置:Notion、AI_BASE_URL、AI_API_KEY、AI_MODEL、数据库 ID
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
from nonebot import get_driver
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MailConfig(BaseModel):
|
|
11
|
+
notion_token: str = ""
|
|
12
|
+
notion_version: str = "2025-09-03"
|
|
13
|
+
ras_data_source_id: str = "31e70d82-c716-80ba-b4d2-000b1892f62c"
|
|
14
|
+
ras_database_id: str = "31e70d82-c716-80d3-9f2d-e73dcc4033b3"
|
|
15
|
+
contact_data_source_id: str = "31e70d82-c716-8034-b23d-000ba20878af"
|
|
16
|
+
ai_base_url: str = "https://api.openai.com/v1"
|
|
17
|
+
ai_api_key: str = ""
|
|
18
|
+
ai_model: str = "gpt-4o"
|
|
19
|
+
mail_image_max_count: int = 12
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_config() -> MailConfig:
|
|
23
|
+
raw_config = get_driver().config
|
|
24
|
+
if hasattr(MailConfig, "model_validate"): # 兼容pydantic v1/2
|
|
25
|
+
return MailConfig.model_validate(raw_config)
|
|
26
|
+
return MailConfig.parse_obj(raw_config)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
config = get_config()
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# @Version : 0.7.0
|
|
4
|
+
# 规划备注:邮件类型、字段名、默认值、正则、澳门规则常量
|
|
5
|
+
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
PLUGIN_DIR = Path(__file__).resolve().parent
|
|
9
|
+
DATA_DIR = PLUGIN_DIR / "data" / "mail_imgs"
|
|
10
|
+
DATA_DIR.mkdir(parents=True, exist_ok=True)
|
|
11
|
+
|
|
12
|
+
SPECIAL_CAKE_ID = "31e70d82-c716-81ef-9ecb-ec45fbaabaf2"
|
|
13
|
+
SPECIAL_YUN_ID = "31e70d82-c716-8180-9fa9-e6328d4db9c0"
|
|
14
|
+
SPECIAL_HK_ID = "31e70d82-c716-8172-8088-c4cc856f8422"
|
|
15
|
+
SPECIAL_BIRDGREEN_ID = "31e70d82-c716-81a8-b2c2-ca848376185e"
|
|
16
|
+
SPECIAL_YING_ID = "31f70d82-c716-81ea-9fe9-cff8aee2d0c2"
|
|
17
|
+
SPECIAL_DANDAN_ID = "31e70d82-c716-815e-9cce-c216a363a9df"
|
|
18
|
+
SPECIAL_SCHOOL_ID = "31e70d82-c716-8148-95fb-f8e38f1d9292"
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# @Version : 0.7.0
|
|
4
|
+
# 规划备注:叶恩杰项目 FALLBACK_CONTACTS 转 Python
|
|
5
|
+
|
|
6
|
+
import hashlib
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def contact(
|
|
11
|
+
name: str,
|
|
12
|
+
phone: str,
|
|
13
|
+
postcode1: str,
|
|
14
|
+
address1: str,
|
|
15
|
+
postcode2: str,
|
|
16
|
+
address2: str,
|
|
17
|
+
qq: str,
|
|
18
|
+
extra: dict[str, Any] | None = None,
|
|
19
|
+
) -> dict[str, Any]:
|
|
20
|
+
extra = extra or {}
|
|
21
|
+
local_id = hashlib.sha1(f"{name}|{qq}".encode("utf-8")).hexdigest()[:12]
|
|
22
|
+
return {
|
|
23
|
+
"id": extra.get("id") or f"local-{local_id}",
|
|
24
|
+
"name": name,
|
|
25
|
+
"phone": phone,
|
|
26
|
+
"email": "",
|
|
27
|
+
"postcode1": postcode1,
|
|
28
|
+
"address1": address1,
|
|
29
|
+
"postcode2": postcode2,
|
|
30
|
+
"address2": address2,
|
|
31
|
+
"qq": qq,
|
|
32
|
+
"url": "",
|
|
33
|
+
"source": "fallback",
|
|
34
|
+
"aliases": extra.get("aliases", []),
|
|
35
|
+
"macauRecipient": bool(extra.get("macauRecipient", False)),
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
from .fallback_contacts_privacy import FALLBACK_CONTACTS as FALLBACK_CONTACTS
|
|
40
|
+
except ImportError: # 此为示例,按此格式增加你的通讯录
|
|
41
|
+
FALLBACK_CONTACTS = [
|
|
42
|
+
contact("王xx(工亚xxx)", "153xxx", "730xxx", "甘肃省兰州市xxxx校区", "", "", "192xxxx"),
|
|
43
|
+
contact("陳xx(诺x)", "172xxx", "43xxx", "湖北省武xxx", "",
|
|
44
|
+
"澳门地址:AI 识别到澳门或 Chan Kuok Cheng 时匹配此联系人", "90xxx", {
|
|
45
|
+
"aliases": ["陈国政", "陳國政", "诺斯", "Chan Kuok Cheng", "Chan Kwok Cheng"],
|
|
46
|
+
"macauRecipient": True, # 硬编码示例
|
|
47
|
+
}),
|
|
48
|
+
]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from .fallback_contacts import contact
|
|
2
|
+
|
|
3
|
+
FALLBACK_CONTACTS = [
|
|
4
|
+
contact("四月八", "13370163498", "102249", "北京市昌平区府学路27号", "415700", "湖南省常德市桃源县浔阳街道办事处", "2300790043"),
|
|
5
|
+
contact("陆昱岐(心非黍离)", "13307320807", "117000", "辽宁省本溪市明山区春明小区33-15号楼一单元6层18号", "", "", "3429068514"),
|
|
6
|
+
contact("李昊(Hao Lee)", "18251860728", "211300", "江苏省南京市高淳区漆桥街道双富嘉园62幢201", "210000", "江苏省南京市浦口区浦珠南路30号南京工业大学南苑11栋", "2830063477"),
|
|
7
|
+
contact("戴高乐(蛋糕)", "17355107252", "230061", "安徽省合肥市庐阳区永红路15号12栋301室", "", "", "2092494182"),
|
|
8
|
+
contact("钟浩轩(help660)", "18470483739", "341299", "江西省赣州市上犹县东山镇上西村路66号江西省上犹中学", "", "", "2817162250,2107399661"),
|
|
9
|
+
contact("刘天榆(Bluerain)", "18943761450;18520143197", "342400", "江西省赣州市兴国县和睦路5号云顶公馆2号楼1单元2902", "", "", "2662751570"),
|
|
10
|
+
contact("陈垚烨(英)", "17759087767", "355101", "福建省宁德市霞浦县松港街道兴建弄43-1号", "", "", "1292465559"),
|
|
11
|
+
contact("叶恩杰(不爱吃苹果的小苹果)", "13372636761", "400025", "重庆市江北区观音桥街道鸿恩二路138号保利山庄22-1-301", "", "", "3658503541,1874826835"),
|
|
12
|
+
contact("孙思源(最笨的蛋蛋)", "15342267168", "430048", "湖北省武汉市东西湖区环湖中路36号武汉轻工大学金银湖校区", "", "", "8630023"),
|
|
13
|
+
contact("陳國政(诺斯)", "17205931062", "430079", "湖北省武汉市洪山区珞喻路129号武汉大学信息学部国软C4", "", "澳门地址:AI 识别到澳门或 Chan Kuok Cheng 时匹配此联系人", "907347520", {
|
|
14
|
+
"aliases": ["陈国政", "陳國政", "诺斯", "Chan Kuok Cheng", "Chan Kwok Cheng"],
|
|
15
|
+
"macauRecipient": True,
|
|
16
|
+
}),
|
|
17
|
+
contact("苏业森(杉 Caam)", "18218680555", "510540", "广东省广州市白云区太和镇兴太三路638号广州理工学院", "", "", "1226824536"),
|
|
18
|
+
contact("罗曦(flashier)", "15207556674", "518060", "广东省深圳市南山区南海大道3688号深圳大学粤海校区红豆斋", "", "", "2915773430"),
|
|
19
|
+
contact("李铭修(Ad)", "18307760360", "533000", "广西壮族自治区百色市右江区百高路9号百色高级中学2407", "533000", "广西壮族自治区百色市右江区东洲大道碧桂园翡翠郡12-1-2302", "3423118775,3839761587"),
|
|
20
|
+
contact("柳源昊(鸟绿)", "18363800296", "610054", "四川省成都市成华区建设北路二段4号电子科技大学", "264025", "山东省烟台市芝罘区红旗中路186号 45-361", "2176700635,2431394341"),
|
|
21
|
+
contact("张淞云(想要上岸)", "15982263486", "611731", "(暂时勿寄此地址)四川省成都市高新西区西源大道2006号电子科技大学", "610017", "四川省成都市青羊区东通顺街2号", "1920143820"),
|
|
22
|
+
contact("李浩诚( )", "15850787478", "611731", "四川省成都市高新西区西源大道2006号电子科技大学清水河校区", "210000", "江苏省南京市秦淮区朝天宫街道石鼓路小区小桂子(李浩诚先生收)", "2743218818"),
|
|
23
|
+
contact("孙彦恒(秋)", "18533718711", "710038", "陕西省西安市灞桥区狄寨街道水安路28号西安思源学院", "065000", "河北省廊坊市广阳区北凤道 阿尔卡迪亚小区华景园8号楼二单元201室", "2703065363"),
|
|
24
|
+
contact("王子涵(工亚一生黑)", "15349271968", "730050", "甘肃省兰州市七里河区彭家坪36号兰州理工大学彭家坪校区", "", "", "1925879836"),
|
|
25
|
+
]
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# @Version : 0.7.0
|
|
4
|
+
# 规划备注:/mail contacts、/mail records、/mail @某人
|
|
5
|
+
|
|
6
|
+
from nonebot import on_command
|
|
7
|
+
from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, Message, MessageSegment
|
|
8
|
+
from nonebot.params import CommandArg
|
|
9
|
+
|
|
10
|
+
from .utils import At
|
|
11
|
+
from ..services.contacts import get_contacts, get_key_by_qq, qqmap
|
|
12
|
+
from ..services.render import contacts_to_image, latest_mail_records_to_image
|
|
13
|
+
|
|
14
|
+
matcher = on_command("mail", priority=5, block=True)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@matcher.handle()
|
|
18
|
+
async def _(bot: Bot, event: GroupMessageEvent, arg: Message = CommandArg()):
|
|
19
|
+
contacts = await get_contacts()
|
|
20
|
+
qqmap(contacts)
|
|
21
|
+
cmd = arg.extract_plain_text().strip().lower()
|
|
22
|
+
at = At(event.json())
|
|
23
|
+
|
|
24
|
+
if cmd in ("contacts", "联系人", "contact"):
|
|
25
|
+
img_path = await contacts_to_image()
|
|
26
|
+
await matcher.finish(MessageSegment.image(f"file:///{img_path}"))
|
|
27
|
+
|
|
28
|
+
elif cmd in ("records", "record", "邮件", "mail"):
|
|
29
|
+
img_path = await latest_mail_records_to_image(15)
|
|
30
|
+
await matcher.finish(MessageSegment.image(f"file:///{img_path}"))
|
|
31
|
+
|
|
32
|
+
elif at:
|
|
33
|
+
result_blocks = []
|
|
34
|
+
for idx, qq in enumerate(at, 1):
|
|
35
|
+
target_qq = str(qq)
|
|
36
|
+
target_uuid = get_key_by_qq(target_qq)
|
|
37
|
+
if not target_uuid:
|
|
38
|
+
result_blocks.append(f"--- 第 {idx} 位 ---\n" f"QQ:{target_qq}\n" f"没有找到这个联系人的信息哦")
|
|
39
|
+
continue
|
|
40
|
+
target_contact = None
|
|
41
|
+
for c in contacts:
|
|
42
|
+
if c["id"] == target_uuid:
|
|
43
|
+
target_contact = c
|
|
44
|
+
break
|
|
45
|
+
if not target_contact:
|
|
46
|
+
result_blocks.append(f"--- 第 {idx} 位 ---\n" f"QQ:{target_qq}\n" f"没有找到这个联系人的详细资料哦")
|
|
47
|
+
continue
|
|
48
|
+
lines = [
|
|
49
|
+
f"--- 第 {idx} 位 ---",
|
|
50
|
+
f"姓名:{target_contact.get('姓名', '') or '无'}",
|
|
51
|
+
f"电话:{target_contact.get('电话', '') or '无'}",
|
|
52
|
+
f"地址1:{target_contact.get('地址1', '') or '无'}",
|
|
53
|
+
f"邮编1:{target_contact.get('邮编1', '') or '无'}",
|
|
54
|
+
]
|
|
55
|
+
if target_contact.get("地址2"):
|
|
56
|
+
lines.append(f"地址2:{target_contact.get('地址2', '')}")
|
|
57
|
+
if target_contact.get("邮编2"):
|
|
58
|
+
lines.append(f"邮编2:{target_contact.get('邮编2', '')}")
|
|
59
|
+
result_blocks.append("\n".join(lines))
|
|
60
|
+
await matcher.finish("\n\n".join(result_blocks))
|
|
61
|
+
|
|
62
|
+
else:
|
|
63
|
+
await matcher.finish(
|
|
64
|
+
"用法:\n"
|
|
65
|
+
"/mail contacts 查看联系人表(全部)\n"
|
|
66
|
+
"/mail records 查看邮件记录(最新15条)\n"
|
|
67
|
+
"/mail @某人 查看该联系人的信息\n"
|
|
68
|
+
"/mail @甲 @乙 按顺序查看多位联系人的信息"
|
|
69
|
+
)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# @Version : 0.7.0
|
|
4
|
+
# 规划备注:“查询/查件”
|
|
5
|
+
|
|
6
|
+
import random
|
|
7
|
+
|
|
8
|
+
from nonebot import on_command
|
|
9
|
+
from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent
|
|
10
|
+
from nonebot.typing import T_State
|
|
11
|
+
|
|
12
|
+
from ..constants import SPECIAL_CAKE_ID, SPECIAL_YUN_ID
|
|
13
|
+
from ..services.contacts import get_contacts, get_key_by_qq, get_name_by_uuid, qq_map, qqmap
|
|
14
|
+
from ..services.notion import query_recent_mails_by_addressee, simplify_mail_results
|
|
15
|
+
|
|
16
|
+
query = on_command("查询", priority=5, block=True, aliases={"查件"})
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@query.handle()
|
|
20
|
+
async def _(state: T_State, bot: Bot, event: GroupMessageEvent):
|
|
21
|
+
qq_str = event.get_user_id()
|
|
22
|
+
nickname = event.sender.nickname
|
|
23
|
+
await query.send(f"你好呀{nickname},让我帮你查询一下最近有没有人给你寄件呢")
|
|
24
|
+
contacts = await get_contacts()
|
|
25
|
+
qqmap(contacts)
|
|
26
|
+
query_addressee = get_key_by_qq(event.get_user_id())
|
|
27
|
+
query_result = query_recent_mails_by_addressee(addressee_id=query_addressee, days=7, limit=10, rec=False)
|
|
28
|
+
mails = simplify_mail_results(query_result)
|
|
29
|
+
if not mails:
|
|
30
|
+
if qq_str in qq_map.get(SPECIAL_CAKE_ID, []):
|
|
31
|
+
query_message = random.choice(["可恶的蛋糕,遭报应了吧,最近7天内没人给你寄信","这倒霉的蛋糕,是不是你平时诅咒别人太多了?这7天可没人给你写信啊", "真是个讨厌的蛋糕,看来你平常没少咒人,最近一周都没人联系你","这破蛋糕,怕不是你老爱诅咒别人吧,这七天一个给你寄信的都没有","这个可恨的蛋糕,大概是你咒人太多的报应吧,最近七天没人给你寄信"])
|
|
32
|
+
elif qq_str in qq_map.get(SPECIAL_YUN_ID, []):
|
|
33
|
+
query_message = random.choice(["云云,最近7天内没人给你寄信,可惜"])
|
|
34
|
+
else:
|
|
35
|
+
query_message = f"太遗憾了{nickname},7天内没有人给你寄信啊"
|
|
36
|
+
else:
|
|
37
|
+
query_message = f"""最近 {7} 天内,{f"查询到{len(mails)}条" if len(mails) <= 10 else "寄给你的信真是太多了,你真是个人气王,我只帮你查最近10条哦"}:"""
|
|
38
|
+
if qq_str in qq_map.get(SPECIAL_CAKE_ID, []):
|
|
39
|
+
query_message = "坏蛋糕," + query_message
|
|
40
|
+
elif qq_str in qq_map.get(SPECIAL_YUN_ID, []):
|
|
41
|
+
query_message = "本✌," + query_message
|
|
42
|
+
for i, mail in enumerate(mails, 1):
|
|
43
|
+
lines = [f"--- 第 {i} 条 ---", f"寄出日期: {mail['寄出日期']}", f"类别: {mail['备注']}"]
|
|
44
|
+
if mail["邮件编号"]:
|
|
45
|
+
lines.append(f"邮件编号: {mail['邮件编号']}")
|
|
46
|
+
lines.append(f"寄件人: {await get_name_by_uuid(mail['寄件人_uuid'], contacts)}")
|
|
47
|
+
query_message += "\n" + "\n".join(lines) + "\n"
|
|
48
|
+
await query.finish(query_message)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# @Version : 0.7.0
|
|
4
|
+
# 规划备注:“签收/收件”
|
|
5
|
+
|
|
6
|
+
import random
|
|
7
|
+
import re
|
|
8
|
+
|
|
9
|
+
from nonebot import on_command
|
|
10
|
+
from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, MessageEvent
|
|
11
|
+
from nonebot.log import logger
|
|
12
|
+
from nonebot.params import ArgStr
|
|
13
|
+
from nonebot.typing import T_State
|
|
14
|
+
|
|
15
|
+
from .utils import MsgText
|
|
16
|
+
from ..constants import SPECIAL_CAKE_ID, SPECIAL_YUN_ID
|
|
17
|
+
from ..services.contacts import get_contacts, get_key_by_qq, get_name_by_uuid, qq_map, qqmap
|
|
18
|
+
from ..services.notion import mark_signed_from_input, query_recent_mails_by_addressee, simplify_mail_results
|
|
19
|
+
|
|
20
|
+
receive = on_command("签收", priority=5, block=True, aliases={"收件"})
|
|
21
|
+
label_to_page_id = {}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@receive.handle()
|
|
25
|
+
async def _(state: T_State, bot: Bot, event: GroupMessageEvent):
|
|
26
|
+
contacts = await get_contacts()
|
|
27
|
+
qq_str = event.get_user_id()
|
|
28
|
+
nickname = event.sender.nickname
|
|
29
|
+
await receive.send(f"你好呀{nickname},让我帮你查询一下你有没有在途的邮件呢")
|
|
30
|
+
qqmap(contacts)
|
|
31
|
+
query_addressee = get_key_by_qq(event.get_user_id())
|
|
32
|
+
query_result = query_recent_mails_by_addressee(addressee_id=query_addressee, days=7, limit=10, rec=True)
|
|
33
|
+
mails = simplify_mail_results(query_result)
|
|
34
|
+
if not mails:
|
|
35
|
+
if qq_str in qq_map.get(SPECIAL_CAKE_ID, []):
|
|
36
|
+
query_message = random.choice(["可恶的蛋糕,你没有等待签收的邮件,是不是因为诅咒的人太多了没人写给你?"])
|
|
37
|
+
elif qq_str in qq_map.get(SPECIAL_YUN_ID, []):
|
|
38
|
+
query_message = random.choice(["云云,你没有等待签收的邮件,要不去gayhub找找同好?"])
|
|
39
|
+
else:
|
|
40
|
+
query_message = f"{nickname},你目前没有未签收的邮件!快让别人多寄寄给你吧"
|
|
41
|
+
await receive.finish(query_message)
|
|
42
|
+
else:
|
|
43
|
+
query_message = f"""{f"查询到{len(mails)}条,请回复编号以签收" if len(mails) <= 10 else "寄给你的信真是太多了,我只能显示最近10条哦,先签收这一轮的吧!"}:"""
|
|
44
|
+
for i, mail in enumerate(mails):
|
|
45
|
+
label = chr(65 + i)
|
|
46
|
+
label_to_page_id[label] = mail["page_id"]
|
|
47
|
+
lines = [f"--- 第 {label} 条 ---", f"寄出日期: {mail['寄出日期']}", f"类别: {mail['备注']}"]
|
|
48
|
+
if mail["邮件编号"]:
|
|
49
|
+
lines.append(f"邮件编号: {mail['邮件编号']}")
|
|
50
|
+
lines.append(f"寄件人: {await get_name_by_uuid(mail['寄件人_uuid'], contacts)}")
|
|
51
|
+
query_message += "\n" + "\n".join(lines) + "\n"
|
|
52
|
+
await receive.send(query_message)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@receive.got("a1")
|
|
56
|
+
async def _(state: T_State, bot: Bot, event: MessageEvent, lst: str = ArgStr("a1")):
|
|
57
|
+
msgtext = MsgText(event.json())
|
|
58
|
+
s = msgtext.upper()
|
|
59
|
+
result = []
|
|
60
|
+
for ch in re.findall(r"[A-J]", s):
|
|
61
|
+
if ch not in result:
|
|
62
|
+
result.append(ch)
|
|
63
|
+
if not result:
|
|
64
|
+
await receive.finish("输入无效!请稍后重试!")
|
|
65
|
+
else:
|
|
66
|
+
parse_letters = result
|
|
67
|
+
updated = mark_signed_from_input(parse_letters, label_to_page_id)
|
|
68
|
+
logger.info("已更新如下页面:" + str(updated))
|
|
69
|
+
await receive.finish(f"已签收第 {','.join(parse_letters)} 条")
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# @Version : 0.7.0
|
|
4
|
+
# 规划备注:新增:群内发送信件图片触发智能识别、人工确认、提交 Notion
|
|
5
|
+
|
|
6
|
+
import re
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
from nonebot import on_command
|
|
10
|
+
from nonebot.adapters.onebot.v11 import Bot, Event, GroupMessageEvent
|
|
11
|
+
from nonebot.params import ArgStr
|
|
12
|
+
from nonebot.typing import T_State
|
|
13
|
+
from nonebot.log import logger
|
|
14
|
+
|
|
15
|
+
from .utils import At, MsgText
|
|
16
|
+
from ..config import config
|
|
17
|
+
from ..services.contacts import get_contacts, get_key_by_qq, get_name_by_uuid, qqmap
|
|
18
|
+
from ..services.images import collect_message_images
|
|
19
|
+
from ..services.notion import mail_record
|
|
20
|
+
from ..services.recognizer import AiRecognizeError, recognize_images
|
|
21
|
+
from ..services.render import render_recognition_text
|
|
22
|
+
from ..services.rules import normalize_date, normalize_mail_type, normalize_tracking
|
|
23
|
+
|
|
24
|
+
recognize = on_command("识别信件", priority=5, block=True, aliases={"智能寄信", "信件识别", "识别邮件"})
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@recognize.handle()
|
|
28
|
+
async def _(state: T_State, bot: Bot, event: GroupMessageEvent):
|
|
29
|
+
try:
|
|
30
|
+
await bot.call_api('set_msg_emoji_like', group_id=event.group_id, message_id=event.message_id, emoji_id='294',
|
|
31
|
+
set=True)
|
|
32
|
+
except Exception as e:
|
|
33
|
+
logger.opt(exception=e).warning("贴表情失败")
|
|
34
|
+
contacts = await get_contacts()
|
|
35
|
+
qqmap(contacts)
|
|
36
|
+
state["contacts"] = contacts
|
|
37
|
+
await recognize.send("请发送至少一张信封图片,我会识别收件人、寄件人、邮戳日期和邮件类型。")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@recognize.got("images")
|
|
41
|
+
async def _(state: T_State, bot: Bot, event: Event):
|
|
42
|
+
contacts = state["contacts"]
|
|
43
|
+
images = await collect_message_images(bot, event)
|
|
44
|
+
if not images:
|
|
45
|
+
await recognize.reject("请发送至少一张信封图片")
|
|
46
|
+
await recognize.send("收到图片,正在识别信封信息,请稍等。")
|
|
47
|
+
try:
|
|
48
|
+
result = await recognize_images(images, contacts)
|
|
49
|
+
except AiRecognizeError as e:
|
|
50
|
+
await recognize.finish(str(e))
|
|
51
|
+
except Exception as e:
|
|
52
|
+
await recognize.finish(f"识别失败:{e}")
|
|
53
|
+
records = result["records"]
|
|
54
|
+
state["records"] = records
|
|
55
|
+
preview = await render_recognition_text(records, contacts)
|
|
56
|
+
await recognize.send(preview)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@recognize.got("confirm")
|
|
60
|
+
async def _(state: T_State, bot: Bot, event: Event, text: str = ArgStr("confirm")):
|
|
61
|
+
contacts = state["contacts"]
|
|
62
|
+
records = state["records"]
|
|
63
|
+
msgtext = MsgText(event.json()) or text
|
|
64
|
+
if msgtext.strip() not in {"确认", "提交", "ok", "OK", "好的"}:
|
|
65
|
+
apply_corrections(records, contacts, event, msgtext)
|
|
66
|
+
preview = await render_recognition_text(records, contacts)
|
|
67
|
+
await recognize.reject(preview)
|
|
68
|
+
|
|
69
|
+
contact_ids = {c["id"] for c in contacts}
|
|
70
|
+
for index, record in enumerate(records, 1):
|
|
71
|
+
if record.get("senderId") not in contact_ids:
|
|
72
|
+
await recognize.reject(f"第 {index} 条寄件人未匹配,请手动选择")
|
|
73
|
+
if record.get("recipientId") not in contact_ids:
|
|
74
|
+
await recognize.reject(f"第 {index} 条收件人未匹配,请手动选择")
|
|
75
|
+
if str(record.get("senderId", "")).startswith("local-") or str(record.get("recipientId", "")).startswith("local-"):
|
|
76
|
+
await recognize.reject(f"第 {index} 条联系人只来自本地参考表,缺少 Notion 页面 id。请确认 NOTION_TOKEN 可读取联系人表后重试")
|
|
77
|
+
|
|
78
|
+
results = []
|
|
79
|
+
try:
|
|
80
|
+
for record in records:
|
|
81
|
+
created = mail_record(
|
|
82
|
+
DATABASE_ID=config.ras_database_id,
|
|
83
|
+
SENDER_ID=record["senderId"],
|
|
84
|
+
ADDRESSEE_ID=record["recipientId"],
|
|
85
|
+
SEND_DATE=normalize_date(record.get("sendDate") or "") or record.get("sendDate"),
|
|
86
|
+
TRACKING_NO=normalize_tracking(record.get("trackingNo") or ""),
|
|
87
|
+
TYPE=normalize_mail_type(record.get("mailType") or "平信"),
|
|
88
|
+
title="由AI识图提交",
|
|
89
|
+
)
|
|
90
|
+
results.append(created)
|
|
91
|
+
except httpx.ConnectError as e:
|
|
92
|
+
await recognize.finish(f"Notion 请求异常,请重试: {e}")
|
|
93
|
+
return
|
|
94
|
+
except Exception as e:
|
|
95
|
+
await recognize.finish(f"提交失败:{e}")
|
|
96
|
+
return
|
|
97
|
+
|
|
98
|
+
lines = ["登记成功!"]
|
|
99
|
+
for index, res in enumerate(results, 1):
|
|
100
|
+
lines.append(f"第 {index} 条:{res['url']}")
|
|
101
|
+
await recognize.finish("\n".join(lines))
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def apply_corrections(records: list[dict], contacts: list[dict], event: Event, text: str):
|
|
105
|
+
if not records:
|
|
106
|
+
return
|
|
107
|
+
index = 0
|
|
108
|
+
match_index = re.search(r"第\s*(\d+)", text)
|
|
109
|
+
if match_index:
|
|
110
|
+
index = max(0, min(len(records) - 1, int(match_index.group(1)) - 1))
|
|
111
|
+
record = records[index]
|
|
112
|
+
at = At(event.json())
|
|
113
|
+
if "寄件人" in text and at:
|
|
114
|
+
uuid = get_key_by_qq(str(at[0]))
|
|
115
|
+
if uuid:
|
|
116
|
+
record["senderId"] = uuid
|
|
117
|
+
if "收件人" in text and at:
|
|
118
|
+
uuid = get_key_by_qq(str(at[-1]))
|
|
119
|
+
if uuid:
|
|
120
|
+
record["recipientId"] = uuid
|
|
121
|
+
date_match = re.search(r"(\d{4}-\d{1,2}-\d{1,2})", text)
|
|
122
|
+
if date_match:
|
|
123
|
+
date = normalize_date(date_match.group(1))
|
|
124
|
+
if date:
|
|
125
|
+
record["sendDate"] = date
|
|
126
|
+
type_match = re.search(r"类型\s*[:: ]\s*([^\s]+)", text)
|
|
127
|
+
if not type_match:
|
|
128
|
+
type_match = re.search(r"类别\s*[:: ]\s*([^\s]+)", text)
|
|
129
|
+
if type_match:
|
|
130
|
+
record["mailType"] = normalize_mail_type(type_match.group(1))
|
|
131
|
+
tracking_match = re.search(r"编号\s*[:: ]\s*([A-Za-z0-9\-]+)", text)
|
|
132
|
+
if tracking_match:
|
|
133
|
+
record["trackingNo"] = normalize_tracking(tracking_match.group(1))
|
|
134
|
+
record["errors"] = []
|