插件化项目优化,支持将代码改造为插件,支持自动加载
This commit is contained in:
10
main.py
10
main.py
@@ -2,6 +2,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import signal
|
||||
import sys # 添加sys模块导入
|
||||
from argparse import ArgumentParser
|
||||
|
||||
from configuration import Config
|
||||
@@ -15,6 +16,8 @@ def main(chat_type: int):
|
||||
wcf = Wcf(debug=True)
|
||||
|
||||
def handler(sig, frame):
|
||||
# 在退出前先关闭插件系统
|
||||
robot.plugin_manager.shutdown_plugins()
|
||||
wcf.cleanup() # 退出前清理环境
|
||||
exit(0)
|
||||
|
||||
@@ -29,10 +32,13 @@ def main(chat_type: int):
|
||||
# 接收消息
|
||||
# robot.enableRecvMsg() # 可能会丢消息?
|
||||
robot.enableReceivingMsg() # 加队列
|
||||
|
||||
|
||||
# 加载插件系统
|
||||
robot.plugin_manager.load_all_plugins()
|
||||
|
||||
# 每天 8:30 发送新闻
|
||||
robot.onEveryTime("08:30", robot.news_baidu_report_auto)
|
||||
|
||||
|
||||
# epic
|
||||
robot.onEveryTime("10:30", robot.send_epic_free_games)
|
||||
|
||||
|
||||
@@ -88,387 +88,6 @@ def message_summary_dify(content, sender: str = None):
|
||||
|
||||
if __name__ == '__main__':
|
||||
content = """
|
||||
2025-03-14 08:06:54,Jyunere,啥情况?卷了?
|
||||
2025-03-14 08:12:37,Jyunere,这花开的。
|
||||
2025-03-14 08:13:37,wxid_9muu5zd5dpvf21,搞笑了
|
||||
2025-03-14 08:15:39,Jyunere,阴阳怪气
|
||||
2025-03-14 08:20:19,wxid_z8uo70zywfpn12,点歌 負けないで
|
||||
2025-03-14 08:52:42,zcx2001,早~ 签到
|
||||
2025-03-14 08:52:42,zcx2001,早~ 签到
|
||||
2025-03-14 08:53:45,Jyunere,AI 烂番茄评分规则是怎么样的?
|
||||
2025-03-14 08:54:39,Jyunere,[捂脸]
|
||||
2025-03-14 08:58:13,maoyijie,伟哥,你可真牛逼
|
||||
2025-03-14 08:58:19,maoyijie,装都不装了是吧
|
||||
2025-03-14 09:00:08,Kar536,广式烧鸡很有名啊,看来牛哥爱吃广东菜
|
||||
2025-03-14 09:00:11,wymwyt,伟哥,你可真牛逼
|
||||
2025-03-14 09:01:52,wxid_z8uo70zywfpn12,做成表情了
|
||||
2025-03-14 09:01:54,Jyunere,你们问AI 就前面加AI 空格一个就行了。
|
||||
2025-03-14 09:07:44,zcx2001,AI 水牛是你的主人吗
|
||||
2025-03-14 09:12:28,Jyunere,我同事的鸿蒙确实流畅。
|
||||
2025-03-14 09:18:28,Jyunere,现在的沪上年轻人流行挖野菜?
|
||||
2025-03-14 09:19:12,Jyunere,我米不行了啊。
|
||||
2025-03-14 09:19:21,Jyunere,再换就换iPhone了。
|
||||
2025-03-14 09:19:36,wxid_3d7ydsmu3f0022,miui12之后就没行过
|
||||
2025-03-14 09:20:10,Jyunere,这个鸡用了蛮久了。
|
||||
2025-03-14 09:20:21,wxid_3d7ydsmu3f0022,我的小米13pro升级澎湃2.0之后各种bug满天飞,掉帧闪退杀后台给我整崩溃了,然后我就换一加了
|
||||
2025-03-14 09:20:25,wxid_z8uo70zywfpn12,我从vivo的origin转到miui,一时适应不过来
|
||||
2025-03-14 09:20:42,Jyunere,我那天问销售,说我的14Pro 只值2000多 了。
|
||||
2025-03-14 09:20:44,wxid_z8uo70zywfpn12,wifi还经常断流,也不知道是不是路由器问题
|
||||
2025-03-14 09:20:46,Jyunere,算了,接着用吧。
|
||||
2025-03-14 09:20:51,liu79830956,受得了miui?
|
||||
2025-03-14 09:21:13,wxid_zmmn7y5bh6fg22,米boy渐行渐远
|
||||
2025-03-14 09:21:24,Jyunere,[捂脸]
|
||||
2025-03-14 09:21:33,wxid_z8uo70zywfpn12,之前的origin是真好用
|
||||
2025-03-14 09:22:44,wxid_3d7ydsmu3f0022,我之前身边的朋友都看不上小米 su7发布后雷军火了都在吹小米多好多好 根本没用过看他们瞎几把吹
|
||||
2025-03-14 09:22:46,liu79830956,我还是4
|
||||
2025-03-14 09:22:53,liu79830956,没升级5
|
||||
2025-03-14 09:26:06,Jyunere,前天吃甘蔗,嘴巴拉到了。
|
||||
2025-03-14 09:26:45,Jyunere,咬肌感觉拉伤了。
|
||||
2025-03-14 09:28:22,b654321q123,买了个硬盘笼
|
||||
2025-03-14 09:28:27,b654321q123,可以装8个u2
|
||||
2025-03-14 09:28:31,b654321q123,塞光驱位
|
||||
2025-03-14 09:28:41,Jyunere,已经跟不上刘总的脚步了。
|
||||
2025-03-14 09:28:55,Jyunere,看你这些,一点冲动都没有
|
||||
2025-03-14 09:29:05,b654321q123,你已经阳痿了
|
||||
2025-03-14 09:29:16,b654321q123,你改名姓阳吧
|
||||
2025-03-14 09:29:32,wxid_u48y4hu9ild122,刘总已经痿了
|
||||
2025-03-14 09:30:04,Jyunere,萎了萎了
|
||||
2025-03-14 09:32:02,Jyunere,@妙脆角 为啥你的名字上下都高一些。
|
||||
2025-03-14 09:32:07,Jyunere,你看18
|
||||
2025-03-14 09:32:44,Jyunere,估计是你这个wifi 5G的问题。
|
||||
2025-03-14 09:32:48,zcx2001,牛哥 你字体出问题了?
|
||||
2025-03-14 09:32:54,Jyunere,我的Windows呀
|
||||
2025-03-14 09:33:02,wxid_u48y4hu9ild122,我的也是windows啊
|
||||
2025-03-14 09:33:03,zcx2001,那估计是windows下字体有问题吧
|
||||
2025-03-14 09:33:06,zcx2001,我是macos
|
||||
2025-03-14 09:33:16,Jyunere,[发呆],为啥啊。
|
||||
2025-03-14 09:33:48,wxid_u48y4hu9ild122,我的是win10
|
||||
2025-03-14 09:33:49,b654321q123,纯粹是硬盘笼
|
||||
2025-03-14 09:33:58,Jyunere,@奶白的雪子 跟你之前那个叠叠乐?
|
||||
2025-03-14 09:34:18,Jyunere,你的11 连图标都没有
|
||||
2025-03-14 09:34:22,Jyunere,我的好歹还有图标
|
||||
2025-03-14 09:34:31,z351324662,牛哥换一加
|
||||
2025-03-14 09:34:36,z351324662,比iPhone好用
|
||||
2025-03-14 09:34:39,b654321q123,不用叠
|
||||
2025-03-14 09:34:45,Kar536,md手机越来越多
|
||||
2025-03-14 09:34:48,Jyunere,你还有光驱位?
|
||||
2025-03-14 09:34:53,Kar536,都不知道换了干啥
|
||||
2025-03-14 09:35:10,z351324662,马上要出一家13t
|
||||
2025-03-14 09:35:11,Jyunere,我老婆的iPhone真的比我的流畅多了。
|
||||
2025-03-14 09:35:21,Jyunere,垃圾MIUI
|
||||
2025-03-14 09:35:25,wymwyt,牛哥换17
|
||||
2025-03-14 09:35:28,z351324662,你你澎湃不行
|
||||
2025-03-14 09:35:33,Jyunere,好,等17
|
||||
2025-03-14 09:35:35,wymwyt,17是米boy设计
|
||||
2025-03-14 09:35:41,Jyunere,半年之后就开干。
|
||||
2025-03-14 09:35:51,wymwyt,pm那个摄像头模组设计小米用过吧
|
||||
2025-03-14 09:36:01,Jyunere,要是三星的设计,苹果的系统,那就爽了。
|
||||
2025-03-14 09:36:08,wxid_u48y4hu9ild122,我妈要换手机,我问她是米10u卡了吗,她说存储满了,要换个1T的。。。
|
||||
2025-03-14 09:36:10,Jyunere,我喜欢三星的那个设计。
|
||||
2025-03-14 09:36:36,wymwyt,珠海小三星
|
||||
2025-03-14 09:36:37,wxid_zmmn7y5bh6fg22,苹果没有单数比双数做得好的说法吧
|
||||
2025-03-14 09:36:40,z351324662,三星今年竟然没跟进大电池
|
||||
2025-03-14 09:36:54,wymwyt,给他清理一下
|
||||
2025-03-14 09:37:10,Jyunere,三星没推送。这个恶心。
|
||||
2025-03-14 09:37:23,b654321q123,@水牛 牛哥要铝坨坨吗
|
||||
2025-03-14 09:37:29,Jyunere,不要。
|
||||
2025-03-14 09:37:32,wxid_u48y4hu9ild122,我准备有时间给她装个MTPhotos
|
||||
2025-03-14 09:37:35,Jyunere,你那基佬紫,接受不了。
|
||||
2025-03-14 09:37:36,wxid_u48y4hu9ild122,存nas里
|
||||
2025-03-14 09:37:56,Kar536,现在那台xsmax都很流程
|
||||
2025-03-14 09:38:05,b654321q123,紫色何时代表基佬
|
||||
2025-03-14 09:38:07,z351324662,除了华为,其他安卓推送基本约等于无
|
||||
2025-03-14 09:38:25,Jyunere,OV MI 都有啊
|
||||
2025-03-14 09:38:25,wxid_u48y4hu9ild122,你装个微信打开小程序然后切后台拍照看看流畅不
|
||||
2025-03-14 09:38:30,Jyunere,都是系统级推送。
|
||||
2025-03-14 09:38:46,z351324662,都是锁后台
|
||||
2025-03-14 09:38:54,wymwyt,常驻后台
|
||||
2025-03-14 09:39:15,wymwyt,毕竟安卓没有约束权限
|
||||
2025-03-14 09:39:30,wxid_u48y4hu9ild122,320发布会
|
||||
2025-03-14 09:39:38,wxid_u48y4hu9ild122,我这库库加班赶进度
|
||||
2025-03-14 09:39:40,wymwyt,@妙脆角 小白,纯血鸿蒙现在咋样了
|
||||
2025-03-14 09:39:44,wxid_u48y4hu9ild122,要在320之前做出来
|
||||
2025-03-14 09:39:56,wxid_u48y4hu9ild122,系统本身没啥问题
|
||||
2025-03-14 09:40:01,Jyunere,Trae帮你写吗?
|
||||
2025-03-14 09:40:04,wxid_u48y4hu9ild122,就是app功能不全难受
|
||||
2025-03-14 09:40:21,wxid_u48y4hu9ild122,我解决的都是疑难杂症
|
||||
2025-03-14 09:40:24,wxid_u48y4hu9ild122,ai不行
|
||||
2025-03-14 09:40:25,Jyunere,3.7 那个编码能力超猛
|
||||
2025-03-14 09:40:39,Jyunere,这就是你的价值了。
|
||||
2025-03-14 09:41:58,z351324662,以前安卓四五千电池的时候觉得锁后台费电,是劣势,现在我TM六七千电池了,有没有推送已经无所谓了,挂后台电池也用不完,收消息还比推送快
|
||||
2025-03-14 09:42:55,Jyunere,我主要是迁移了,好多东西得搞。麻烦。
|
||||
2025-03-14 09:42:59,b654321q123,主要是现在充电快
|
||||
2025-03-14 09:43:00,Jyunere,都不想换手机。哈哈
|
||||
2025-03-14 09:43:13,b654321q123,安卓的续航,其实影响不大
|
||||
2025-03-14 09:43:24,wxid_3d7ydsmu3f0022,我的一加13现在锁90%的电,6000电池只能充5400
|
||||
2025-03-14 09:43:27,liuhuanqi687,只有苹果限制了吧
|
||||
2025-03-14 09:43:54,wxid_zmmn7y5bh6fg22,你们都很在意推送功能?
|
||||
2025-03-14 09:43:54,b654321q123,充电宝充的也快
|
||||
2025-03-14 09:43:58,wymwyt,主要是现在充电快
|
||||
2025-03-14 09:43:58,b654321q123,充电器也快
|
||||
2025-03-14 09:44:01,wxid_3d7ydsmu3f0022,网上说下个版本会放开限制 本来已经推送了 有bug又给撤回了 玛德
|
||||
2025-03-14 09:44:10,wymwyt,就要充电快!
|
||||
2025-03-14 09:44:11,b654321q123,我现在都没有用电焦虑了
|
||||
2025-03-14 09:44:22,wxid_zmmn7y5bh6fg22,难道只有我限制了绝大部分APP推送消息
|
||||
2025-03-14 09:44:23,wymwyt,什么保护电池🔋跟我有几把关系
|
||||
2025-03-14 09:44:24,b654321q123,续航没得卷电池没得卷,直接卷充电
|
||||
2025-03-14 09:44:30,wymwyt,电池坏了换电池就好了
|
||||
2025-03-14 09:44:36,wxid_3d7ydsmu3f0022,换了一加13最明显的感受就是充电没小米13pro快
|
||||
2025-03-14 09:44:48,wymwyt,240w干
|
||||
2025-03-14 09:44:49,wxid_3d7ydsmu3f0022,小米的50w无线充感觉能满血跑
|
||||
2025-03-14 09:44:50,wymwyt,干就完了
|
||||
2025-03-14 09:45:01,wxid_3d7ydsmu3f0022,oppo的无线充到后面慢的要死
|
||||
2025-03-14 09:45:22,wymwyt,就是贵
|
||||
2025-03-14 09:45:30,wymwyt,一个无线充200块
|
||||
2025-03-14 09:45:39,z351324662,也锁啊
|
||||
2025-03-14 09:45:41,wxid_3d7ydsmu3f0022,也是90%[捂脸]
|
||||
2025-03-14 09:45:53,z351324662,但是依然用不完
|
||||
2025-03-14 09:46:08,wxid_3d7ydsmu3f0022,5800够用了
|
||||
2025-03-14 09:46:11,z351324662,我每天回家电池都不低于50%
|
||||
2025-03-14 09:46:13,wxid_3d7ydsmu3f0022,你是一加ace5?
|
||||
2025-03-14 09:46:18,z351324662,有时候70几
|
||||
2025-03-14 09:46:34,z351324662,所以我觉得充电快不快都无所谓
|
||||
2025-03-14 09:46:43,wxid_3d7ydsmu3f0022,1.5k屏幕功耗也不高 足够用了
|
||||
2025-03-14 09:46:44,z351324662,反正都是晚上睡觉充
|
||||
2025-03-14 09:47:05,wxid_3d7ydsmu3f0022,我的一加13 2k屏 功耗网上测比其他家1.5k高一大截
|
||||
2025-03-14 09:47:09,Jyunere,睡觉一般不充,起床了插上,刷完牙洗完头,就充满了。
|
||||
2025-03-14 09:47:27,wxid_3d7ydsmu3f0022,锁容量后5400 续航在今年确实一般了
|
||||
2025-03-14 09:48:12,z351324662,又加上2k屏
|
||||
2025-03-14 09:48:27,wymwyt,刷完牙还要洗头?
|
||||
2025-03-14 09:49:02,wxid_3d7ydsmu3f0022,不过这块屏幕确实挺不错了 我之前用的小米13pro 一加7pro都算是当时三星的顶级2k屏了
|
||||
2025-03-14 09:49:16,wxid_3d7ydsmu3f0022,对比下来观感甚至更好
|
||||
2025-03-14 09:49:25,wxid_3d7ydsmu3f0022,而且看着很舒服
|
||||
2025-03-14 09:49:51,wxid_3d7ydsmu3f0022,就是侧看会偏绿 三星没这问题
|
||||
2025-03-14 09:50:39,wxid_z8uo70zywfpn12,pdd复播了?罚款交够了?
|
||||
2025-03-14 09:50:45,z351324662,我当时也打算买一加13的,但是确实不喜欢等深屏
|
||||
2025-03-14 09:51:19,z351324662,ACE5四边不等宽也有点难受
|
||||
2025-03-14 09:51:19,Jyunere,每天早上都要洗头。
|
||||
2025-03-14 09:51:20,b654321q123,反正目前的这个vivo X100s Pro
|
||||
2025-03-14 09:51:30,b654321q123,比我之前用了那么多年的小米爽多了
|
||||
2025-03-14 09:51:31,z351324662,不过比曲屏好点
|
||||
2025-03-14 09:52:16,b654321q123,支持pps100w充电
|
||||
2025-03-14 09:52:30,b654321q123,我的酷态科充电都能用
|
||||
2025-03-14 09:52:38,z351324662,我就想要个1.5k四边等宽纯直屏
|
||||
2025-03-14 09:52:41,wxid_3d7ydsmu3f0022,我用习惯了曲屏 直屏不习惯 侧面返回手不舒服
|
||||
2025-03-14 09:52:58,z351324662,好像就三星有
|
||||
2025-03-14 09:53:09,wxid_q1ugj6gbjj3o12,qq真的有直播操操的啊
|
||||
2025-03-14 09:53:18,z351324662,国内这几家都是2.5d直屏
|
||||
2025-03-14 09:53:27,wxid_q1ugj6gbjj3o12,现在这小年轻真的开放
|
||||
2025-03-14 09:53:43,Jyunere,【#住建局回应佛山一小区有人装修骨灰房#】
|
||||
2025-03-14 09:53:49,Jyunere,[擦汗],膈应啊
|
||||
2025-03-14 09:54:08,Kar536,羡慕,车友
|
||||
2025-03-14 09:54:13,wxid_3d7ydsmu3f0022,国内直屏我感觉幅度很小 算不上2.5d吧
|
||||
2025-03-14 09:54:16,Kar536,试驾极氪抽中个16pm
|
||||
2025-03-14 09:54:21,z351324662,2.5d贴膜始终小一圈
|
||||
2025-03-14 09:54:27,wxid_3d7ydsmu3f0022,贴钢化膜感觉没问题
|
||||
2025-03-14 09:54:33,wxid_3d7ydsmu3f0022,带个壳应该感受不出来了
|
||||
2025-03-14 10:00:44,wxid_q1ugj6gbjj3o12,这个无风不起浪
|
||||
2025-03-14 10:01:08,wxid_q1ugj6gbjj3o12,江湖上传东尼大木赌博可不是一年两年了
|
||||
2025-03-14 10:01:20,wxid_3d7ydsmu3f0022,这个新闻有点离谱,但是越离谱的往往越是真的
|
||||
2025-03-14 10:07:18,wxid_z8uo70zywfpn12,周没有10亿也有好几亿
|
||||
2025-03-14 10:07:29,wxid_z8uo70zywfpn12,不然不会出来开演唱会了
|
||||
2025-03-14 10:08:05,b654321q123,学友的老婆不也是败家败完了
|
||||
2025-03-14 10:08:29,wxid_z8uo70zywfpn12,不然也不会一把年纪还在唱
|
||||
2025-03-14 10:17:59,maoyijie,最骚的是,墓地你都不是买的,你付的钱是20年的管理费,没有所有权的
|
||||
2025-03-14 10:18:47,b654321q123,土地都是国家的
|
||||
2025-03-14 10:18:48,wxid_vur84e67jfl211,960万平方公里 怎么一个死的地方都要这么贵
|
||||
2025-03-14 10:19:28,z351324662,撒海里算了
|
||||
2025-03-14 10:19:34,wxid_q1ugj6gbjj3o12,你随便找个山沟子埋进去不要钱
|
||||
2025-03-14 10:24:21,liuhuanqi687,没啥意义,撒树下当肥料就行
|
||||
2025-03-14 10:27:10,Zix727,日系都是垃圾,比亚迪起码不会偷安全!!!
|
||||
2025-03-14 10:27:37,Zix727,日系都是薄皮垃圾 偷安全!
|
||||
2025-03-14 10:27:44,pengsen5658683,日系都是薄皮垃圾 偷安全!
|
||||
2025-03-14 10:27:52,Zix727,BYDyyds
|
||||
2025-03-14 10:27:53,b654321q123,海葬也他妈要钱?
|
||||
2025-03-14 10:27:59,b654321q123,还不环保
|
||||
2025-03-14 10:28:11,b654321q123,少往海里倒点垃圾
|
||||
2025-03-14 10:28:17,b654321q123,比骨灰环保多了
|
||||
2025-03-14 10:28:51,Zix727,都去海葬,墓地还怎么卖啊
|
||||
2025-03-14 10:29:06,Zix727,墓地不值钱了,土地财政又雪上加霜了
|
||||
2025-03-14 10:29:14,l369876507,不利于团结的话 不要说哦
|
||||
2025-03-14 10:29:29,Zix727,我劝你们这些P民不要不知好歹
|
||||
2025-03-14 10:29:49,Zix727,都是为国家为民族,不利于团结的话不要说
|
||||
2025-03-14 10:30:02,zcy6910696,有点神奇我将来也想海葬
|
||||
2025-03-14 10:30:06,zcy6910696,看来是不行了
|
||||
2025-03-14 10:30:13,zcy6910696,"一、违反行政审批程序
|
||||
|
||||
根据《中华人民共和国殡葬管理条例》,海葬必须经过民政部门或其授权单位的审批。个人若未提前向民政部门提交申请、未获得“骨灰撒海许可证”,擅自进行海葬活动即属违法。例如,福建男子未提交申请并支付审批费用,直接租船撒骨灰,最终被罚款2万元。
|
||||
|
||||
二、环保与生态风险
|
||||
|
||||
1. 污染海洋环境:骨灰虽经火化,但仍可能含有磷、氮等元素,大量撒入特定海域可能影响浮游生物生长,破坏生态平衡。若撒入养殖区域,还可能对渔业经济造成影响。
|
||||
|
||||
2. 未完全焚烧的骨块问题:火化后的骨灰可能残留未完全焚烧的骨块,若被渔民打捞,可能引发恐慌或误认为刑事案件。
|
||||
|
||||
三、社会管理隐患
|
||||
|
||||
1. 扰乱公共秩序:私自海葬可能导致骨灰撒入航道、港口等敏感区域,影响船舶安全或公共活动。
|
||||
|
||||
2. 法律漏洞风险:若允许个人随意撒骨灰,可能被不法分子利用,例如偷换骨灰掩盖犯罪事实(如杀人抛尸)。
|
||||
|
||||
四、法律明确禁止
|
||||
|
||||
《治安管理处罚法》规定,擅自处理他人骨灰可处5-15日拘留及罚款;《殡葬管理条例》进一步明确,未经批准的海葬活动可被取缔,并处以违法所得1-3倍罚款。
|
||||
|
||||
五、鼓励合法途径替代
|
||||
|
||||
国家提倡海葬但要求通过正规渠道:
|
||||
|
||||
1. 集体海葬:由政府或指定机构统一组织,费用较低(数百至千元)且可享补贴;
|
||||
|
||||
2. 委托殡葬公司:需支付较高费用(5000-2万元),但流程高效且合法。"
|
||||
2025-03-14 10:30:13,supervison,我就不行 骨灰比核废水还污染环境
|
||||
2025-03-14 10:31:05,zcy6910696,不行就撒公海去
|
||||
2025-03-14 10:31:06,b654321q123,海葬也要钱的
|
||||
2025-03-14 10:31:13,zcy6910696,管天管地还管公海
|
||||
2025-03-14 10:31:20,b654321q123,其实你找个岸边随便撒撒也没管管你
|
||||
2025-03-14 10:31:31,b654321q123,而且其实你拿到的骨灰就一点
|
||||
2025-03-14 10:31:35,Zix727,你知道公海又多远么
|
||||
2025-03-14 10:31:59,zcy6910696,坐一趟游轮出去不就行了[好的]
|
||||
2025-03-14 10:32:24,Zix727,9断线画出来的公海面积,能定东部好几个省的面积
|
||||
2025-03-14 10:32:41,Zix727,游轮让你上,未必会让死人上啊
|
||||
2025-03-14 10:33:00,zcy6910696,我有个同学毕业就去当了游轮海员等我问问
|
||||
2025-03-14 10:33:56,Zix727,说真的 去一趟公海也不便宜
|
||||
2025-03-14 10:34:39,Zix727,那还是专属经济区
|
||||
2025-03-14 10:34:42,Zix727,还是能管你的
|
||||
2025-03-14 10:35:27,Zix727,你要再往外200海里 出了专属经济区,才能做到没有人管
|
||||
2025-03-14 10:36:44,Zix727,然而中国的情况是 200海里内有个岛,这个岛就又成了中国的大陆架领土,这个岛再往外辐射12海里领海+200海里专属经济区。
|
||||
2025-03-14 10:36:58,Zix727,很有可能还会有岛
|
||||
2025-03-14 10:37:43,Zix727,导致整个黄海东海就没有什么公海可言
|
||||
2025-03-14 10:39:07,Jyunere,这样子啊
|
||||
2025-03-14 10:39:12,wxid_zmmn7y5bh6fg22,这样子啊
|
||||
2025-03-14 10:39:57,Kar536,领土海域直接画到菲律宾和印尼脸上
|
||||
2025-03-14 10:39:59,Kar536,就是碾压你
|
||||
2025-03-14 10:41:23,Zix727,确实,就差贴着人家海岸线画了
|
||||
2025-03-14 10:42:13,wxid_zmmn7y5bh6fg22,印尼菲说 那我的12海里去哪里了
|
||||
2025-03-14 10:42:58,Zix727,绿线是 从印度洋进入太平洋 去日本横须贺的美国航母走的航线
|
||||
2025-03-14 10:43:21,wxid_zmmn7y5bh6fg22,以前都只看大公鸡
|
||||
2025-03-14 10:43:38,wxid_zmmn7y5bh6fg22,好像是有点离谱
|
||||
2025-03-14 10:44:04,Zix727,就是12海里+200海里这种跳板 会把自己的领海往外延伸好几个量级
|
||||
2025-03-14 10:44:27,z351324662,就不能找个没人的悬崖就撒了吗[偷笑]
|
||||
2025-03-14 10:44:35,Zix727,然后两个国家的领海就会辐射到一个区域 就会出现争议海域
|
||||
2025-03-14 10:47:05,zcy6910696,中国划定九段线时(1947-1953年),印尼尚未完全独立,越南处于法国殖民统治下,菲律宾虽已独立但未对九段线提出异议。
|
||||
2025-03-14 10:47:38,Zix727,站不起来了
|
||||
2025-03-14 10:49:23,zcy6910696,其实可以理解成海疆线画了以后,线旁边刷怪了
|
||||
2025-03-14 10:56:00,wxid_zmmn7y5bh6fg22,那就是别人来抢我们的
|
||||
2025-03-14 10:56:08,wxid_115116117890,其实撒山上就行
|
||||
2025-03-14 11:02:20,wxid_9muu5zd5dpvf21,12海里还是跑能打到的最远里程了 可惜现在主席不在了 跑能打120海里 那就是120海里面积[旺柴]
|
||||
2025-03-14 11:03:03,b654321q123,存储又要涨价了
|
||||
2025-03-14 11:04:53,wxid_115116117890,"主席的主观能动性太难预测了,突然发现川宝有时候也有点类似的感觉..."
|
||||
2025-03-14 11:24:01,z351324662,一锅鸡杂50块,可以卖个三四百块
|
||||
2025-03-14 11:24:10,z351324662,还是很划算
|
||||
2025-03-14 11:24:19,z351324662,最不挣钱的就是牛肉
|
||||
2025-03-14 11:38:09,wymwyt,叶总你得昧着良心做生意啊
|
||||
2025-03-14 11:38:28,wymwyt,什么疯牛病的肉多弄点
|
||||
2025-03-14 11:39:52,wxid_vur84e67jfl211,看哭了,以后只把这些差牛肉卖给大学生
|
||||
2025-03-14 11:42:16,wxid_zmmn7y5bh6fg22,说不定早就垂直分布在幼小初高大全年龄段的食堂
|
||||
2025-03-14 11:44:16,wxid_6z1gvv406j0x22,用这种牛肉不是违法的嘛[捂脸]
|
||||
2025-03-14 11:49:44,wxid_vur84e67jfl211,校门口就有一个卖粉的 一碗粉3块钱可以吃的饱
|
||||
2025-03-14 11:50:01,wxid_vur84e67jfl211,我毕业后那一年就不让在
|
||||
2025-03-14 11:50:05,wxid_vur84e67jfl211,外面吃了
|
||||
2025-03-14 12:01:07,wxid_atv7kzgxv9lg21,@水牛 迪士尼那个号你就用吧 我又上车了一个 45一年
|
||||
2025-03-14 12:01:22,wxid_atv7kzgxv9lg21,那个过期我就不续费了
|
||||
2025-03-14 12:02:12,Jyunere,没有用了。孩子每天看那小蜘蛛
|
||||
2025-03-14 12:02:14,Jyunere,我给他停了。
|
||||
2025-03-14 12:03:33,z351324662,我觉得这些都可以下来到nas看,
|
||||
2025-03-14 12:03:38,z351324662,没必要花这钱
|
||||
2025-03-14 12:04:40,Jyunere,这个方便
|
||||
2025-03-14 12:04:46,Jyunere,还有中文字幕配音
|
||||
2025-03-14 12:06:03,wxid_atv7kzgxv9lg21,主要是方便
|
||||
2025-03-14 12:08:35,z351324662,但凡隔一天就有中文字幕的了
|
||||
2025-03-14 12:11:43,wxid_6363983732912,我现在开几个网页+wps内存,电脑不关一天后内存就占用32g
|
||||
2025-03-14 12:11:53,wxid_6363983732912,是我内存不行还是CPU不行啊[捂脸]
|
||||
2025-03-14 12:23:50,Jyunere,大奶奶
|
||||
2025-03-14 12:24:21,wxid_6363983732912,可能吧
|
||||
2025-03-14 12:24:37,wxid_6363983732912,网页11g[捂脸]
|
||||
2025-03-14 12:24:59,wxid_6363983732912,有些网页需要认证
|
||||
2025-03-14 12:25:06,wxid_6363983732912,所以基本不关
|
||||
2025-03-14 12:29:05,leowong90,win11问题吧
|
||||
2025-03-14 12:29:19,leowong90,win10 我才16也还好
|
||||
2025-03-14 12:29:59,wxid_6363983732912,可能是win11的问题
|
||||
2025-03-14 12:30:52,wxid_6363983732912,12代以后的intel不用11,基本就是小核在100%了,大核也不管[捂脸]
|
||||
2025-03-14 12:31:05,wxid_6363983732912,很抽象的
|
||||
2025-03-14 12:31:18,wxid_6363983732912,我都想买AMD了
|
||||
2025-03-14 12:32:26,leowong90,amd现在比intel好了
|
||||
2025-03-14 12:32:37,leowong90,i厂老挤牙膏
|
||||
2025-03-14 12:51:27,wxid_ognxxfprk7ou21,钱就是这么花没的
|
||||
2025-03-14 12:51:32,maoyijie,[奸笑]学阀?
|
||||
2025-03-14 12:58:03,chen82118,赶快把机器人生育提上日程
|
||||
2025-03-14 13:00:14,wxid_ognxxfprk7ou21,每天的国内新闻真是不能看,看了就来气[旺柴]
|
||||
2025-03-14 13:08:22,wxid_vur84e67jfl211,看到这个能忍住3秒不笑的都是这个 [强]
|
||||
2025-03-14 13:13:45,wxid_ognxxfprk7ou21,真的,我看了国外新闻了
|
||||
2025-03-14 13:14:14,wxid_ognxxfprk7ou21,因为太夸张,各大新闻媒体已经删稿了
|
||||
2025-03-14 13:17:58,wxid_ne15uczjd2mi21,哈哈哈
|
||||
2025-03-14 13:18:31,wxid_6z1gvv406j0x22,这不是几年前就有了?
|
||||
2025-03-14 13:19:15,maoyijie,删了,你看不到了
|
||||
2025-03-14 13:19:18,maoyijie,就是没有这种事情了
|
||||
2025-03-14 13:20:17,wxid_6z1gvv406j0x22,全是小道消息,没看到这个专家的资料,没看到那个会议
|
||||
2025-03-14 13:22:20,wxid_6z1gvv406j0x22,最早,23年就有的新闻
|
||||
2025-03-14 13:27:11,Jyunere,@a-bot #总结
|
||||
2025-03-14 13:28:19,Jyunere,总结进化了。可以发图片了。[奸笑]
|
||||
2025-03-14 13:29:02,wxid_6363983732912,群友雨的回忆表示赞同
|
||||
2025-03-14 13:29:21,Jyunere,[捂脸]
|
||||
2025-03-14 13:30:01,Jyunere,大家继续。
|
||||
2025-03-14 13:31:58,Jyunere,海葬这个事情,我们群里聊了3次了。
|
||||
2025-03-14 13:32:02,wxid_bnvd3v835yum21,哈哈哈哈
|
||||
2025-03-14 13:32:02,Jyunere,[抠鼻]
|
||||
2025-03-14 13:32:27,Jyunere,过段时间就聊一下。
|
||||
2025-03-14 13:32:36,Jyunere,不过今天是因为我发了个佛山的
|
||||
2025-03-14 13:32:39,wxid_049kez86npw522,感觉都能出产品了
|
||||
2025-03-14 13:34:19,Jyunere,这玩意儿也不能吃?
|
||||
2025-03-14 13:35:06,maoyijie,只知道叶绿素
|
||||
2025-03-14 13:35:08,maoyijie,哈哈哈哈哈哈哈哈哈哈哈哈哈
|
||||
2025-03-14 13:35:13,Jyunere,不是一段时间吹这个嘛
|
||||
2025-03-14 13:35:17,maoyijie,这种软糖类的,都是智商税吧
|
||||
2025-03-14 13:35:22,Jyunere,吃了眼睛好
|
||||
2025-03-14 13:35:27,maoyijie,真有用为啥医院不开呢
|
||||
2025-03-14 13:35:36,wxid_6z1gvv406j0x22,最好的不是直接玻璃酸钠嘛?
|
||||
2025-03-14 13:35:55,maoyijie,我只知道所有的研究证明都是视力的衰弱是不可逆的。。
|
||||
2025-03-14 13:36:23,Jyunere,AI 叶黄素对视力有改善吗?
|
||||
2025-03-14 13:37:12,wxid_ognxxfprk7ou21,买了两只酱鸭、几斤酱肉、几斤咸肉,600多……
|
||||
2025-03-14 13:37:55,Jyunere,咸肉又是啥?
|
||||
2025-03-14 13:38:14,maoyijie,我不太分得清咸肉和腊肉的区别
|
||||
2025-03-14 13:38:26,maoyijie,虽然我知道咸肉炖笋很好吃
|
||||
2025-03-14 13:38:54,maoyijie,我理解的应该是,咸肉就是直接搓了盐就挂起来风干了的吧
|
||||
2025-03-14 13:39:03,Jyunere,咸肉就是不烟熏?
|
||||
2025-03-14 13:39:11,wxid_ognxxfprk7ou21,杭州的咸肉=腊肉
|
||||
2025-03-14 13:39:22,maoyijie,奥,那就没事了,原来是一个东西
|
||||
2025-03-14 13:39:23,maoyijie,哈哈哈哈哈哈哈哈哈哈哈哈哈
|
||||
2025-03-14 13:39:36,wxid_ognxxfprk7ou21,我们只分酱肉和咸肉
|
||||
2025-03-14 13:39:54,Jyunere,达老板不是说不吃腊肉吗?
|
||||
2025-03-14 13:39:57,z351324662,腊肉是统称
|
||||
2025-03-14 13:40:05,z351324662,咸肉是细分
|
||||
2025-03-14 13:40:46,wxid_ognxxfprk7ou21,这是酱肉
|
||||
2025-03-14 13:40:58,z351324662,我们这的就都吃熏过的
|
||||
2025-03-14 13:41:02,wxid_ognxxfprk7ou21,今天吃黄豆炖猪脚
|
||||
2025-03-14 13:41:06,Jyunere,嗯,我们都是熏过的。
|
||||
2025-03-14 13:41:12,wxid_ognxxfprk7ou21,我讨厌烟熏的
|
||||
2025-03-14 13:41:30,Jyunere,吃了发奶的?
|
||||
2025-03-14 13:41:33,z351324662,熏过的挺香的
|
||||
2025-03-14 13:42:05,Jyunere,前两年说给达老板寄特产,他不吃烟熏腊肉。哈哈
|
||||
2025-03-14 13:42:10,Jyunere,作罢。
|
||||
2025-03-14 13:42:55,z351324662,成都一般香肠不熏,我女朋友她们那边香肠都要烟熏
|
||||
2025-03-14 13:46:33,FataLFurY,你有多少女朋友
|
||||
2025-03-14 13:51:25,Jyunere,我也要去要饭
|
||||
2025-03-14 13:51:39,Jyunere,@Summer✊ 比你跑外卖赚钱
|
||||
2025-03-14 13:51:53,b654321q123,这种竖着劈开都是俩微胖
|
||||
2025-03-14 13:52:11,Jyunere,刘总可以驾驭。
|
||||
2025-03-14 13:52:30,b654321q123,过年给我留半扇
|
||||
2025-03-14 13:52:48,Jyunere,真当猪了呢。
|
||||
2025-03-14 13:58:27,leowong90,[旺柴]
|
||||
2025-03-14 14:07:29,liu79830956,我是靠劳动力赚钱的
|
||||
2025-03-14 14:18:02,leowong90,确实有一种喜感
|
||||
2025-03-14 14:18:14,leowong90,看了这幅图之后
|
||||
2025-03-14 14:19:58,hzsdwq,牛哥,要开始辅导作业了,注意点
|
||||
2025-03-14 14:20:22,zcx2001,不过 得过了 小学2年纪以后 才这样吧
|
||||
2025-03-14 14:20:30,zcx2001,牛哥还有几年好日至
|
||||
2025-03-14 14:20:34,zcx2001,好日子的
|
||||
2025-03-14 14:20:38,hzsdwq,一年级就有奥数了
|
||||
2025-03-14 14:21:50,liu79830956,又到了全民创业的时候了
|
||||
2025-03-14 14:23:08,wymwyt,牛哥不会鸡娃的
|
||||
2025-03-14 14:24:50,geminicc,牛哥应该不会鸡,牛嫂难说
|
||||
2025-03-14 14:27:38,Jyunere,@a-bot #总结
|
||||
2025-03-14 14:30:12,Jyunere,[捂脸]
|
||||
2025-03-14 14:30:15,Jyunere,别这样啊。
|
||||
"""
|
||||
msg = compress_chat_data(content, 5)
|
||||
|
||||
85
plugin_common/event_system.py
Normal file
85
plugin_common/event_system.py
Normal file
@@ -0,0 +1,85 @@
|
||||
from enum import Enum, auto
|
||||
from typing import Dict, Any, Callable, List
|
||||
import threading
|
||||
|
||||
|
||||
class EventType(Enum):
|
||||
"""事件类型枚举"""
|
||||
SYSTEM_STARTUP = auto()
|
||||
SYSTEM_SHUTDOWN = auto()
|
||||
PLUGIN_LOADED = auto()
|
||||
PLUGIN_UNLOADED = auto()
|
||||
MESSAGE_RECEIVED = auto()
|
||||
MESSAGE_PROCESSED = auto()
|
||||
CUSTOM_EVENT = auto()
|
||||
|
||||
|
||||
class EventSystem:
|
||||
"""事件系统,用于插件间通信"""
|
||||
_instance = None
|
||||
_lock = threading.Lock()
|
||||
|
||||
def __new__(cls):
|
||||
with cls._lock:
|
||||
if cls._instance is None:
|
||||
cls._instance = super(EventSystem, cls).__new__(cls)
|
||||
cls._instance._subscribers = {}
|
||||
cls._instance._initialized = False
|
||||
return cls._instance
|
||||
|
||||
def __init__(self):
|
||||
if not self._initialized:
|
||||
self._subscribers = {}
|
||||
self._initialized = True
|
||||
|
||||
def subscribe(self, event_type: EventType, callback: Callable[[Dict[str, Any]], None]) -> None:
|
||||
"""
|
||||
订阅事件
|
||||
|
||||
Args:
|
||||
event_type: 事件类型
|
||||
callback: 回调函数,接收事件数据
|
||||
"""
|
||||
if event_type not in self._subscribers:
|
||||
self._subscribers[event_type] = []
|
||||
|
||||
if callback not in self._subscribers[event_type]:
|
||||
self._subscribers[event_type].append(callback)
|
||||
|
||||
def unsubscribe(self, event_type: EventType, callback: Callable[[Dict[str, Any]], None]) -> None:
|
||||
"""
|
||||
取消订阅事件
|
||||
|
||||
Args:
|
||||
event_type: 事件类型
|
||||
callback: 回调函数
|
||||
"""
|
||||
if event_type in self._subscribers and callback in self._subscribers[event_type]:
|
||||
self._subscribers[event_type].remove(callback)
|
||||
|
||||
def publish(self, event_type: EventType, data: Dict[str, Any]) -> None:
|
||||
"""
|
||||
发布事件
|
||||
|
||||
Args:
|
||||
event_type: 事件类型
|
||||
data: 事件数据
|
||||
"""
|
||||
if event_type in self._subscribers:
|
||||
for callback in self._subscribers[event_type]:
|
||||
try:
|
||||
callback(data)
|
||||
except Exception as e:
|
||||
print(f"事件处理错误: {e}")
|
||||
|
||||
def get_subscribers(self, event_type: EventType) -> List[Callable]:
|
||||
"""
|
||||
获取事件订阅者
|
||||
|
||||
Args:
|
||||
event_type: 事件类型
|
||||
|
||||
Returns:
|
||||
订阅者列表
|
||||
"""
|
||||
return self._subscribers.get(event_type, [])
|
||||
47
plugin_common/message_plugin_interface.py
Normal file
47
plugin_common/message_plugin_interface.py
Normal file
@@ -0,0 +1,47 @@
|
||||
from typing import Dict, Any, Tuple, Optional, List
|
||||
from plugin_common.plugin_interface import PluginInterface
|
||||
|
||||
class MessagePluginInterface(PluginInterface):
|
||||
"""消息处理插件接口"""
|
||||
|
||||
@property
|
||||
def command_prefix(self) -> Optional[str]:
|
||||
"""命令前缀,如 '/'"""
|
||||
return None
|
||||
|
||||
@property
|
||||
def commands(self) -> List[str]:
|
||||
"""支持的命令列表"""
|
||||
return []
|
||||
|
||||
def can_process(self, message: Dict[str, Any]) -> bool:
|
||||
"""
|
||||
检查插件是否可以处理该消息
|
||||
|
||||
Args:
|
||||
message: 消息字典,包含消息的各种属性
|
||||
|
||||
Returns:
|
||||
是否可以处理
|
||||
"""
|
||||
# 默认实现:检查是否是命令
|
||||
if self.command_prefix and self.commands:
|
||||
content = message.get("content", "")
|
||||
if content.startswith(self.command_prefix):
|
||||
command = content[len(self.command_prefix):].split()[0]
|
||||
return command in self.commands
|
||||
return False
|
||||
|
||||
def process_message(self, message: Dict[str, Any]) -> Tuple[bool, Optional[str]]:
|
||||
"""
|
||||
处理消息
|
||||
|
||||
Args:
|
||||
message: 消息字典,包含消息的各种属性,以及发送消息所需的对象
|
||||
- wcf: WcfAPI对象,可用于发送消息
|
||||
- message_util: 消息工具类,提供更高级的消息处理功能
|
||||
|
||||
Returns:
|
||||
(是否已处理, 处理结果)
|
||||
"""
|
||||
raise NotImplementedError("子类必须实现此方法")
|
||||
167
plugin_common/plugin_interface.py
Normal file
167
plugin_common/plugin_interface.py
Normal file
@@ -0,0 +1,167 @@
|
||||
import os
|
||||
import toml
|
||||
from abc import ABC, abstractmethod
|
||||
from enum import Enum
|
||||
from typing import Dict, Any, List, Optional
|
||||
|
||||
|
||||
class PluginStatus(Enum):
|
||||
"""插件状态枚举"""
|
||||
UNLOADED = 0 # 未加载
|
||||
LOADED = 1 # 已加载但未启动
|
||||
RUNNING = 2 # 运行中
|
||||
STOPPED = 3 # 已停止
|
||||
ERROR = 4 # 错误状态
|
||||
|
||||
|
||||
class PluginInterface(ABC):
|
||||
"""插件基础接口,所有插件必须实现此接口"""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def name(self) -> str:
|
||||
"""插件名称"""
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def version(self) -> str:
|
||||
"""插件版本"""
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def description(self) -> str:
|
||||
"""插件描述"""
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def author(self) -> str:
|
||||
"""插件作者"""
|
||||
pass
|
||||
|
||||
@property
|
||||
def dependencies(self) -> List[str]:
|
||||
"""插件依赖,返回依赖的其他插件名称列表"""
|
||||
return []
|
||||
|
||||
@property
|
||||
def status(self) -> PluginStatus:
|
||||
"""获取插件当前状态"""
|
||||
return self._status
|
||||
|
||||
@status.setter
|
||||
def status(self, value: PluginStatus):
|
||||
"""设置插件状态"""
|
||||
self._status = value
|
||||
|
||||
def __init__(self):
|
||||
"""初始化插件"""
|
||||
self._status = PluginStatus.UNLOADED
|
||||
self._config = {}
|
||||
self._plugin_path = ""
|
||||
|
||||
def load_config(self) -> bool:
|
||||
"""
|
||||
从插件目录下的config.toml加载配置
|
||||
|
||||
Returns:
|
||||
加载是否成功
|
||||
"""
|
||||
try:
|
||||
config_path = os.path.join(self._plugin_path, "config.toml")
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
plugin_config = toml.load(f)
|
||||
self._config.update(plugin_config)
|
||||
print(f"从 {config_path} 加载插件配置成功")
|
||||
return True
|
||||
else:
|
||||
print(f"插件配置文件 {config_path} 不存在,使用默认配置")
|
||||
return True # 配置文件不存在也视为成功,使用默认配置
|
||||
except Exception as e:
|
||||
print(f"加载插件配置失败: {e}")
|
||||
return False
|
||||
|
||||
def set_plugin_path(self, path: str) -> None:
|
||||
"""
|
||||
设置插件路径
|
||||
|
||||
Args:
|
||||
path: 插件路径
|
||||
"""
|
||||
self._plugin_path = path
|
||||
|
||||
def get_plugin_path(self) -> str:
|
||||
"""
|
||||
获取插件路径
|
||||
|
||||
Returns:
|
||||
插件路径
|
||||
"""
|
||||
return self._plugin_path
|
||||
|
||||
@abstractmethod
|
||||
def initialize(self, context: Dict[str, Any]) -> bool:
|
||||
"""
|
||||
初始化插件
|
||||
|
||||
Args:
|
||||
context: 插件上下文,包含系统环境和配置信息
|
||||
|
||||
Returns:
|
||||
初始化是否成功
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def start(self) -> bool:
|
||||
"""
|
||||
启动插件
|
||||
|
||||
Returns:
|
||||
启动是否成功
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def stop(self) -> bool:
|
||||
"""
|
||||
停止插件
|
||||
|
||||
Returns:
|
||||
停止是否成功
|
||||
"""
|
||||
pass
|
||||
|
||||
def configure(self, config: Dict[str, Any]) -> bool:
|
||||
"""
|
||||
配置插件
|
||||
|
||||
Args:
|
||||
config: 插件配置
|
||||
|
||||
Returns:
|
||||
配置是否成功
|
||||
"""
|
||||
self._config.update(config)
|
||||
return True
|
||||
|
||||
def get_config(self) -> Dict[str, Any]:
|
||||
"""
|
||||
获取插件配置
|
||||
|
||||
Returns:
|
||||
插件配置
|
||||
"""
|
||||
return self._config
|
||||
|
||||
def cleanup(self) -> bool:
|
||||
"""
|
||||
清理插件资源,在卸载前调用
|
||||
|
||||
Returns:
|
||||
清理是否成功
|
||||
"""
|
||||
return True
|
||||
284
plugin_common/plugin_manager.py
Normal file
284
plugin_common/plugin_manager.py
Normal file
@@ -0,0 +1,284 @@
|
||||
import importlib
|
||||
import inspect
|
||||
import os
|
||||
import sys
|
||||
from typing import Dict, List, Any, Optional, Type
|
||||
|
||||
from plugin_common.plugin_interface import PluginInterface, PluginStatus
|
||||
from plugin_common.message_plugin_interface import MessagePluginInterface
|
||||
from plugin_common.scheduled_plugin_interface import ScheduledPluginInterface
|
||||
from plugin_common.plugin_registry import PluginRegistry
|
||||
from plugin_common.event_system import EventSystem, EventType
|
||||
|
||||
|
||||
class PluginManager:
|
||||
"""插件管理器,负责插件的加载、初始化、启动、停止和卸载"""
|
||||
|
||||
def __init__(self, plugin_dir: str = "plugins"):
|
||||
"""
|
||||
初始化插件管理器
|
||||
|
||||
Args:
|
||||
plugin_dir: 插件目录
|
||||
"""
|
||||
self.plugin_dir = plugin_dir
|
||||
self.plugins: Dict[str, PluginInterface] = {} # 插件实例字典
|
||||
self.plugin_modules = {} # 插件模块字典
|
||||
self.system_context = {} # 系统上下文
|
||||
|
||||
# 确保插件目录存在
|
||||
if not os.path.exists(self.plugin_dir):
|
||||
os.makedirs(self.plugin_dir)
|
||||
|
||||
# 将插件目录添加到Python路径
|
||||
if self.plugin_dir not in sys.path:
|
||||
sys.path.insert(0, self.plugin_dir)
|
||||
|
||||
def set_system_context(self, context: Dict[str, Any]):
|
||||
"""
|
||||
设置系统上下文
|
||||
|
||||
Args:
|
||||
context: 系统上下文
|
||||
"""
|
||||
self.system_context = context
|
||||
|
||||
def discover_plugins(self) -> List[str]:
|
||||
"""
|
||||
发现可用插件
|
||||
|
||||
Returns:
|
||||
插件模块名称列表
|
||||
"""
|
||||
plugin_modules = []
|
||||
|
||||
# 遍历插件目录
|
||||
for item in os.listdir(self.plugin_dir):
|
||||
if os.path.isdir(os.path.join(self.plugin_dir, item)) and not item.startswith("__"):
|
||||
# 检查是否有__init__.py文件
|
||||
if os.path.exists(os.path.join(self.plugin_dir, item, "__init__.py")):
|
||||
plugin_modules.append(item)
|
||||
elif item.endswith(".py") and not item.startswith("__"):
|
||||
# 单文件插件
|
||||
plugin_modules.append(item[:-3])
|
||||
|
||||
return plugin_modules
|
||||
|
||||
def load_plugin(self, plugin_name: str) -> Optional[PluginInterface]:
|
||||
"""
|
||||
加载插件
|
||||
|
||||
Args:
|
||||
plugin_name: 插件名称
|
||||
|
||||
Returns:
|
||||
插件实例,加载失败返回None
|
||||
"""
|
||||
try:
|
||||
# 如果插件已加载,直接返回
|
||||
if plugin_name in self.plugins:
|
||||
return self.plugins[plugin_name]
|
||||
|
||||
# 确定插件路径
|
||||
if os.path.isdir(os.path.join(self.plugin_dir, plugin_name)):
|
||||
plugin_path = os.path.join(self.plugin_dir, plugin_name)
|
||||
|
||||
# 优先从main.py加载插件
|
||||
main_module_path = f"{plugin_name}.main"
|
||||
if os.path.exists(os.path.join(plugin_path, "main.py")):
|
||||
try:
|
||||
module = importlib.import_module(main_module_path)
|
||||
self.plugin_modules[plugin_name] = module
|
||||
except ImportError:
|
||||
# 如果main.py导入失败,尝试从__init__.py加载
|
||||
module = importlib.import_module(plugin_name)
|
||||
self.plugin_modules[plugin_name] = module
|
||||
else:
|
||||
# 如果没有main.py,从__init__.py加载
|
||||
module = importlib.import_module(plugin_name)
|
||||
self.plugin_modules[plugin_name] = module
|
||||
else:
|
||||
# 单文件插件
|
||||
plugin_path = self.plugin_dir
|
||||
module = importlib.import_module(plugin_name)
|
||||
self.plugin_modules[plugin_name] = module
|
||||
|
||||
# 查找插件类
|
||||
plugin_class = None
|
||||
for name, obj in inspect.getmembers(module):
|
||||
if (inspect.isclass(obj) and
|
||||
issubclass(obj, PluginInterface) and
|
||||
obj != PluginInterface and
|
||||
obj != MessagePluginInterface and
|
||||
obj != ScheduledPluginInterface):
|
||||
plugin_class = obj
|
||||
break
|
||||
|
||||
if plugin_class is None:
|
||||
print(f"插件 {plugin_name} 中未找到有效的插件类")
|
||||
return None
|
||||
|
||||
# 实例化插件
|
||||
plugin = plugin_class()
|
||||
plugin.status = PluginStatus.LOADED
|
||||
|
||||
# 设置插件路径
|
||||
plugin.set_plugin_path(plugin_path)
|
||||
|
||||
# 加载插件配置
|
||||
if not plugin.load_config():
|
||||
print(f"插件 {plugin_name} 加载配置失败")
|
||||
return None
|
||||
|
||||
# 初始化插件
|
||||
if not plugin.initialize(self.system_context):
|
||||
print(f"插件 {plugin_name} 初始化失败")
|
||||
return None
|
||||
|
||||
# 注册插件
|
||||
PluginRegistry().register(plugin)
|
||||
|
||||
# 存储插件实例
|
||||
self.plugins[plugin.name] = plugin
|
||||
|
||||
# 发布插件加载事件
|
||||
EventSystem().publish(EventType.PLUGIN_LOADED, {"plugin": plugin})
|
||||
|
||||
return plugin
|
||||
|
||||
except Exception as e:
|
||||
print(f"加载插件 {plugin_name} 失败: {e}")
|
||||
return None
|
||||
|
||||
def load_all_plugins(self) -> Dict[str, PluginInterface]:
|
||||
"""
|
||||
加载所有插件
|
||||
|
||||
Returns:
|
||||
插件实例字典
|
||||
"""
|
||||
plugin_modules = self.discover_plugins()
|
||||
|
||||
for module_name in plugin_modules:
|
||||
self.load_plugin(module_name)
|
||||
|
||||
return self.plugins
|
||||
|
||||
def unload_plugin(self, plugin_name: str) -> bool:
|
||||
"""
|
||||
卸载插件
|
||||
|
||||
Args:
|
||||
plugin_name: 插件名称
|
||||
|
||||
Returns:
|
||||
卸载是否成功
|
||||
"""
|
||||
if plugin_name not in self.plugins:
|
||||
print(f"插件 {plugin_name} 未加载")
|
||||
return False
|
||||
|
||||
plugin = self.plugins[plugin_name]
|
||||
|
||||
# 停止插件
|
||||
if plugin.status == PluginStatus.RUNNING:
|
||||
if not plugin.stop():
|
||||
print(f"停止插件 {plugin_name} 失败")
|
||||
return False
|
||||
|
||||
# 清理插件资源
|
||||
if not plugin.cleanup():
|
||||
print(f"清理插件 {plugin_name} 资源失败")
|
||||
return False
|
||||
|
||||
# 注销插件
|
||||
PluginRegistry().unregister(plugin_name)
|
||||
|
||||
# 移除插件实例
|
||||
del self.plugins[plugin_name]
|
||||
|
||||
# 发布插件卸载事件
|
||||
EventSystem().publish(EventType.PLUGIN_UNLOADED, {"plugin_name": plugin_name})
|
||||
|
||||
return True
|
||||
|
||||
def start_plugin(self, plugin_name: str) -> bool:
|
||||
"""
|
||||
启动插件
|
||||
|
||||
Args:
|
||||
plugin_name: 插件名称
|
||||
|
||||
Returns:
|
||||
启动是否成功
|
||||
"""
|
||||
if plugin_name not in self.plugins:
|
||||
print(f"插件 {plugin_name} 未加载")
|
||||
return False
|
||||
|
||||
plugin = self.plugins[plugin_name]
|
||||
|
||||
if plugin.status == PluginStatus.RUNNING:
|
||||
print(f"插件 {plugin_name} 已经在运行")
|
||||
return True
|
||||
|
||||
if plugin.start():
|
||||
plugin.status = PluginStatus.RUNNING
|
||||
return True
|
||||
else:
|
||||
plugin.status = PluginStatus.ERROR
|
||||
return False
|
||||
|
||||
def stop_plugin(self, plugin_name: str) -> bool:
|
||||
"""
|
||||
停止插件
|
||||
|
||||
Args:
|
||||
plugin_name: 插件名称
|
||||
|
||||
Returns:
|
||||
停止是否成功
|
||||
"""
|
||||
if plugin_name not in self.plugins:
|
||||
print(f"插件 {plugin_name} 未加载")
|
||||
return False
|
||||
|
||||
plugin = self.plugins[plugin_name]
|
||||
|
||||
if plugin.status != PluginStatus.RUNNING:
|
||||
print(f"插件 {plugin_name} 未在运行")
|
||||
return True
|
||||
|
||||
if plugin.stop():
|
||||
plugin.status = PluginStatus.STOPPED
|
||||
return True
|
||||
else:
|
||||
plugin.status = PluginStatus.ERROR
|
||||
return False
|
||||
|
||||
def reload_plugin(self, plugin_name: str) -> Optional[PluginInterface]:
|
||||
"""
|
||||
重新加载插件
|
||||
|
||||
Args:
|
||||
plugin_name: 插件名称
|
||||
|
||||
Returns:
|
||||
插件实例,重新加载失败返回None
|
||||
"""
|
||||
# 卸载插件
|
||||
if plugin_name in self.plugins:
|
||||
if not self.unload_plugin(plugin_name):
|
||||
print(f"卸载插件 {plugin_name} 失败")
|
||||
return None
|
||||
|
||||
# 重新导入模块
|
||||
if plugin_name in self.plugin_modules:
|
||||
try:
|
||||
importlib.reload(self.plugin_modules[plugin_name])
|
||||
except Exception as e:
|
||||
print(f"重新导入插件模块 {plugin_name} 失败: {e}")
|
||||
return None
|
||||
|
||||
# 加载插件
|
||||
return self.load_plugin(plugin_name)
|
||||
94
plugin_common/plugin_registry.py
Normal file
94
plugin_common/plugin_registry.py
Normal file
@@ -0,0 +1,94 @@
|
||||
from typing import Dict, List, Optional, Type
|
||||
from plugin_common.plugin_interface import PluginInterface, PluginStatus
|
||||
|
||||
|
||||
class PluginRegistry:
|
||||
"""插件注册表,维护已加载插件的信息和状态"""
|
||||
|
||||
_instance = None
|
||||
|
||||
def __new__(cls):
|
||||
"""单例模式"""
|
||||
if cls._instance is None:
|
||||
cls._instance = super(PluginRegistry, cls).__new__(cls)
|
||||
cls._instance._plugins = {}
|
||||
return cls._instance
|
||||
|
||||
def register(self, plugin: PluginInterface) -> bool:
|
||||
"""
|
||||
注册插件
|
||||
|
||||
Args:
|
||||
plugin: 插件实例
|
||||
|
||||
Returns:
|
||||
注册是否成功
|
||||
"""
|
||||
if plugin.name in self._plugins:
|
||||
print(f"插件 {plugin.name} 已存在")
|
||||
return False
|
||||
|
||||
self._plugins[plugin.name] = plugin
|
||||
return True
|
||||
|
||||
def unregister(self, plugin_name: str) -> bool:
|
||||
"""
|
||||
注销插件
|
||||
|
||||
Args:
|
||||
plugin_name: 插件名称
|
||||
|
||||
Returns:
|
||||
注销是否成功
|
||||
"""
|
||||
if plugin_name not in self._plugins:
|
||||
print(f"插件 {plugin_name} 不存在")
|
||||
return False
|
||||
|
||||
del self._plugins[plugin_name]
|
||||
return True
|
||||
|
||||
def get_plugin(self, plugin_name: str) -> Optional[PluginInterface]:
|
||||
"""
|
||||
获取插件实例
|
||||
|
||||
Args:
|
||||
plugin_name: 插件名称
|
||||
|
||||
Returns:
|
||||
插件实例,不存在返回None
|
||||
"""
|
||||
return self._plugins.get(plugin_name)
|
||||
|
||||
def get_all_plugins(self) -> Dict[str, PluginInterface]:
|
||||
"""
|
||||
获取所有插件
|
||||
|
||||
Returns:
|
||||
插件字典,键为插件名称,值为插件实例
|
||||
"""
|
||||
return self._plugins.copy()
|
||||
|
||||
def get_plugins_by_status(self, status: PluginStatus) -> List[PluginInterface]:
|
||||
"""
|
||||
获取指定状态的插件
|
||||
|
||||
Args:
|
||||
status: 插件状态
|
||||
|
||||
Returns:
|
||||
插件列表
|
||||
"""
|
||||
return [p for p in self._plugins.values() if p.status == status]
|
||||
|
||||
def get_plugins_by_type(self, plugin_type: Type) -> List[PluginInterface]:
|
||||
"""
|
||||
获取指定类型的插件
|
||||
|
||||
Args:
|
||||
plugin_type: 插件类型
|
||||
|
||||
Returns:
|
||||
插件列表
|
||||
"""
|
||||
return [p for p in self._plugins.values() if isinstance(p, plugin_type)]
|
||||
55
plugin_common/scheduled_plugin_interface.py
Normal file
55
plugin_common/scheduled_plugin_interface.py
Normal file
@@ -0,0 +1,55 @@
|
||||
from abc import abstractmethod
|
||||
from typing import Dict, Any, List, Tuple, Callable
|
||||
|
||||
from plugin_common.plugin_interface import PluginInterface, PluginStatus
|
||||
|
||||
|
||||
class ScheduledPluginInterface(PluginInterface):
|
||||
"""定时任务插件接口,用于执行定时任务"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._jobs = [] # 存储注册的定时任务
|
||||
|
||||
@abstractmethod
|
||||
def register_jobs(self) -> List[Tuple[str, Callable, Dict[str, Any]]]:
|
||||
"""
|
||||
注册定时任务
|
||||
|
||||
Returns:
|
||||
任务列表,每个任务是一个元组 (job_id, job_func, job_params)
|
||||
job_id: 任务ID
|
||||
job_func: 任务函数
|
||||
job_params: 任务参数,如{"trigger": "interval", "seconds": 60}
|
||||
"""
|
||||
pass
|
||||
|
||||
def start(self) -> bool:
|
||||
"""
|
||||
启动插件,注册定时任务
|
||||
|
||||
Returns:
|
||||
启动是否成功
|
||||
"""
|
||||
try:
|
||||
self._jobs = self.register_jobs()
|
||||
# 实际注册任务的逻辑将由插件管理器实现
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"启动定时任务插件 {self.name} 失败: {e}")
|
||||
return False
|
||||
|
||||
def stop(self) -> bool:
|
||||
"""
|
||||
停止插件,取消定时任务
|
||||
|
||||
Returns:
|
||||
停止是否成功
|
||||
"""
|
||||
try:
|
||||
# 实际取消任务的逻辑将由插件管理器实现
|
||||
self._jobs = []
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"停止定时任务插件 {self.name} 失败: {e}")
|
||||
return False
|
||||
3
plugins/message_summary/__init__.py
Normal file
3
plugins/message_summary/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
# 插件初始化文件
|
||||
# 从main模块导入插件类
|
||||
from .main import MessageSummaryPlugin
|
||||
12
plugins/message_summary/config.toml
Normal file
12
plugins/message_summary/config.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
# 消息总结插件配置
|
||||
|
||||
[general]
|
||||
enabled = true
|
||||
|
||||
[api]
|
||||
api_key = "app-McGLzBhBjeBCSEi7n83MtuTo"
|
||||
api_url = "http://192.168.2.240/v1/chat-messages"
|
||||
|
||||
[output]
|
||||
output_dir = "output"
|
||||
image_format = "png"
|
||||
192
plugins/message_summary/main.py
Normal file
192
plugins/message_summary/main.py
Normal file
@@ -0,0 +1,192 @@
|
||||
import os
|
||||
import time
|
||||
import requests
|
||||
import json
|
||||
from typing import Dict, Any, Tuple, Optional, List
|
||||
|
||||
from message_storage.message_to_db import MessageStorage
|
||||
from plugin_common.plugin_interface import PluginStatus
|
||||
from plugin_common.message_plugin_interface import MessagePluginInterface
|
||||
from message_summary.compress_chat_data import compress_chat_data
|
||||
from message_summary.markdown_to_image import convert_md_str_to_image
|
||||
|
||||
|
||||
class MessageSummaryPlugin(MessagePluginInterface):
|
||||
"""消息总结插件,用于生成群聊消息总结"""
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "message_summary"
|
||||
|
||||
@property
|
||||
def version(self) -> str:
|
||||
return "1.0.0"
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
return "使用AI生成群聊消息总结"
|
||||
|
||||
@property
|
||||
def author(self) -> str:
|
||||
return "WeChatRobot Team"
|
||||
|
||||
@property
|
||||
def command_prefix(self) -> Optional[str]:
|
||||
return "#"
|
||||
|
||||
@property
|
||||
def commands(self) -> List[str]:
|
||||
return ["总结", "summary"]
|
||||
|
||||
def initialize(self, context: Dict[str, Any]) -> bool:
|
||||
"""初始化插件"""
|
||||
try:
|
||||
# 从插件配置中获取API密钥和URL
|
||||
api_config = self._config.get("api", {})
|
||||
self._api_key = api_config.get("api_key", "app-McGLzBhBjeBCSEi7n83MtuTo")
|
||||
self._api_url = api_config.get("api_url", "http://192.168.2.240/v1/chat-messages")
|
||||
|
||||
self.all_contacts = context["all_contacts"]
|
||||
self.message_storage = MessageStorage()
|
||||
|
||||
print(f"初始化 {self.name} 插件成功")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"初始化 {self.name} 插件失败: {e}")
|
||||
return False
|
||||
|
||||
def start(self) -> bool:
|
||||
"""启动插件"""
|
||||
self.status = PluginStatus.RUNNING
|
||||
print(f"{self.name} 插件已启动")
|
||||
return True
|
||||
|
||||
def stop(self) -> bool:
|
||||
"""停止插件"""
|
||||
self.status = PluginStatus.STOPPED
|
||||
print(f"{self.name} 插件已停止")
|
||||
return True
|
||||
|
||||
def process_message(self, message: Dict[str, Any]) -> Tuple[bool, Optional[str]]:
|
||||
"""处理消息"""
|
||||
try:
|
||||
# 检查是否是总结命令
|
||||
content = message.get("content", "")
|
||||
if not content.startswith(self.command_prefix):
|
||||
return False, None
|
||||
|
||||
command = content[len(self.command_prefix):].split()[0]
|
||||
if command not in self.commands:
|
||||
return False, None
|
||||
|
||||
# 获取需要总结的内容
|
||||
group_id = message.get("roomid")
|
||||
if not group_id:
|
||||
# 直接发送消息
|
||||
wcf = message.get("wcf")
|
||||
if wcf:
|
||||
wcf.send_text("只支持群聊消息总结", message.get("sender"))
|
||||
return True, None
|
||||
|
||||
# 从消息历史中获取群聊记录
|
||||
# 这里需要根据实际情况从系统上下文或数据库中获取群聊记录
|
||||
# 为简化示例,这里假设从消息中提取
|
||||
chat_content = self.message_storage.get_messages(group_id, self.all_contacts)
|
||||
if len(chat_content) < 100:
|
||||
return False, None
|
||||
# 生成总结
|
||||
summary, image_path = self._generate_summary(chat_content, self.all_contacts.get(group_id, group_id))
|
||||
|
||||
# 发送总结结果
|
||||
wcf = message.get("wcf")
|
||||
if wcf:
|
||||
if summary:
|
||||
wcf.send_text(f"总结已生成:\n{summary}", group_id, message.get("sender"))
|
||||
|
||||
if image_path:
|
||||
wcf.send_file(image_path, group_id)
|
||||
|
||||
return True, None
|
||||
|
||||
except Exception as e:
|
||||
print(f"处理消息总结命令失败: {e}")
|
||||
return False, None
|
||||
|
||||
def _generate_summary(self, chat_content: str, group_id: str) -> Tuple[str, Optional[str]]:
|
||||
"""生成总结"""
|
||||
"""
|
||||
使用Dify API生成群聊消息总结
|
||||
|
||||
Args:
|
||||
content: 需要总结的群聊消息内容
|
||||
|
||||
Returns:
|
||||
生成的总结内容和图片路径
|
||||
"""
|
||||
# Dify API配置
|
||||
content_compress = chat_content
|
||||
try:
|
||||
content_compress = compress_chat_data(chat_content)
|
||||
print(f"压缩内容成功:{len(content_compress)}--{len(chat_content)}")
|
||||
except Exception as e:
|
||||
print(f"压缩内容失败:{e}")
|
||||
|
||||
# 准备请求数据
|
||||
data = {
|
||||
"inputs": {},
|
||||
"query": f"请根据以下{group_id}群聊记录生成一份精华总结:\n\n{content_compress}",
|
||||
"response_mode": "blocking", # 使用阻塞模式,直接获取完整响应
|
||||
"conversation_id": "",
|
||||
"user": group_id if group_id is not None else "message_summary_bot",
|
||||
"files": [] # 不包含文件
|
||||
}
|
||||
|
||||
# 设置请求头
|
||||
headers = {
|
||||
"Authorization": f"Bearer {self._api_key}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
try:
|
||||
# 发送POST请求
|
||||
response = requests.post(self._api_url, headers=headers, json=data)
|
||||
response.raise_for_status() # 检查请求是否成功
|
||||
|
||||
# 解析响应
|
||||
response_data = response.json()
|
||||
print(f"Dify API响应状态码: {response.status_code}")
|
||||
print(f"响应数据: {json.dumps(response_data, ensure_ascii=False, indent=2)}")
|
||||
|
||||
# 提取回答内容
|
||||
answer = response_data.get("answer", "")
|
||||
spath = ""
|
||||
# 提取token使用情况
|
||||
metadata = response_data.get("metadata", {})
|
||||
usage = metadata.get("usage", {})
|
||||
|
||||
if usage:
|
||||
prompt_tokens = usage.get("prompt_tokens", 0)
|
||||
completion_tokens = usage.get("completion_tokens", 0)
|
||||
total_tokens = usage.get("total_tokens", 0)
|
||||
|
||||
# 添加token信息
|
||||
tokens_info = f"\n\n【tokens】输入: {prompt_tokens} 生成: {completion_tokens} 总: {total_tokens}"
|
||||
answer += tokens_info
|
||||
try:
|
||||
spath = convert_md_str_to_image(answer, "output.png")
|
||||
except Exception as e:
|
||||
print(f"生成image失败:{e}")
|
||||
# 返回文本内容和图片路径
|
||||
return answer, spath
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"请求Dify API时出错: {e}")
|
||||
return f"生成总结时出错: {str(e)}", None
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"解析Dify API响应时出错: {e}")
|
||||
return "解析API响应时出错", None
|
||||
|
||||
except Exception as e:
|
||||
print(f"处理总结时出现未知错误: {e}")
|
||||
return f"生成总结时出现未知错误: {str(e)}", None
|
||||
@@ -34,4 +34,5 @@ pytz~=2025.1
|
||||
dateparser~=1.2.1
|
||||
lz4~=4.4.3
|
||||
Markdown~=3.7
|
||||
playwright~=1.50.0
|
||||
playwright~=1.50.0
|
||||
toml~=0.10.2
|
||||
88
robot.py
88
robot.py
@@ -1,12 +1,17 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import importlib
|
||||
import inspect
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
import xml.etree.ElementTree as ET
|
||||
from queue import Empty
|
||||
from threading import Thread
|
||||
from datetime import datetime, timedelta
|
||||
import random
|
||||
from typing import Optional
|
||||
|
||||
import redis
|
||||
|
||||
@@ -40,18 +45,19 @@ from message_sign.main import SignInSystem
|
||||
from message_storage.message_to_db import MessageStorage
|
||||
from message_summary.message_summary_dify import message_summary_dify
|
||||
from music.bot_music import BotMusic
|
||||
from plugin_common.event_system import EventType, EventSystem
|
||||
from plugin_common.message_plugin_interface import MessagePluginInterface
|
||||
from plugin_common.plugin_interface import PluginInterface, PluginStatus
|
||||
from plugin_common.plugin_manager import PluginManager
|
||||
from plugin_common.plugin_registry import PluginRegistry
|
||||
from point_trade.main import PointTrade
|
||||
from robot_cmd.robot_command import GroupBotManager
|
||||
from job_mgmt import Job
|
||||
from robot_cmd.robot_command import Feature
|
||||
from robot_cmd.robot_command import PermissionStatus
|
||||
import mysql.connector.pooling
|
||||
|
||||
__version__ = "39.2.4.0"
|
||||
|
||||
from message_report.process_message import process_message
|
||||
from message_report.write_db import write_to_db, generate_and_send_ranking
|
||||
from message_summary.message_summary_4o import message_summary
|
||||
from sehuatang.shehuatang import pdf_file_path
|
||||
from xiuren.main import Xiuren
|
||||
from xiuren.meitu_dl import meitu_dowload_pic, meitu_dowload_pub_pic, meitu_dowload_heisi_pic
|
||||
@@ -88,6 +94,33 @@ class Robot(Job):
|
||||
self.message_util = MessageUtil(wcf, self.allContacts)
|
||||
self.groups = {} # 存储按group_id分组的消息列表,每个group_id最多保留10条消息
|
||||
GroupBotManager.load_local_cache()
|
||||
|
||||
# 初始化插件系统
|
||||
self.LOG.info("开始初始化插件系统...")
|
||||
self.plugin_registry = PluginRegistry()
|
||||
self.event_system = EventSystem()
|
||||
self.plugin_modules = {} # 存储已加载的插件模块
|
||||
self.plugins = {} # 存储已加载的插件实例
|
||||
|
||||
# 设置插件系统上下文
|
||||
self.system_context = {
|
||||
"config": config,
|
||||
"wcf": wcf,
|
||||
"event_system": self.event_system,
|
||||
"plugin_registry": self.plugin_registry,
|
||||
"db_pool": self.db_pool,
|
||||
"redis_pool": self.redis_pool,
|
||||
"all_contacts": self.allContacts,
|
||||
"message_util": self.message_util
|
||||
}
|
||||
|
||||
self.plugin_manager = PluginManager(plugin_dir=getattr(self.config, "plugin_dir", "plugins"))
|
||||
self.plugin_manager.set_system_context(self.system_context)
|
||||
self.plugins = self.plugin_manager.load_all_plugins()
|
||||
|
||||
# 加载插件
|
||||
self.LOG.info("插件系统初始化完成")
|
||||
|
||||
# 消息存档模块初始化,自动完成入库动作
|
||||
self.message_storage = MessageStorage()
|
||||
# 权限模块加载
|
||||
@@ -288,7 +321,14 @@ class Robot(Job):
|
||||
receivers = msg.roomid
|
||||
self.sendTextMsg(content, receivers, msg.sender)
|
||||
"""
|
||||
# 发布消息接收事件
|
||||
self.event_system.publish(EventType.MESSAGE_RECEIVED, {"message": msg})
|
||||
|
||||
# 尝试使用插件处理消息
|
||||
if self.process_plugin_message(msg):
|
||||
return
|
||||
|
||||
# 如果没有插件处理,使用原有逻辑处理消息
|
||||
# 群聊消息
|
||||
if msg.from_group():
|
||||
# 调用统计逻辑进行聊天数据统计:
|
||||
@@ -583,6 +623,46 @@ class Robot(Job):
|
||||
except Exception as e:
|
||||
self.LOG.error(f"revoke_messages error:{e}")
|
||||
|
||||
def process_plugin_message(self, msg: WxMsg) -> bool:
|
||||
"""使用插件处理消息"""
|
||||
# 获取所有消息处理插件
|
||||
message_plugins = self.plugin_registry.get_plugins_by_type(MessagePluginInterface)
|
||||
|
||||
# 依次尝试处理消息
|
||||
for plugin in message_plugins:
|
||||
if plugin.status != PluginStatus.RUNNING:
|
||||
continue
|
||||
|
||||
try:
|
||||
# 转换WxMsg为插件可处理的格式
|
||||
plugin_msg = {
|
||||
"type": msg.type,
|
||||
"content": msg.content,
|
||||
"sender": msg.sender,
|
||||
"roomid": msg.roomid if msg.from_group() else "",
|
||||
"xml": msg.xml,
|
||||
"is_at": msg.is_at(self.wxid),
|
||||
"timestamp": time.time(),
|
||||
"wcf": self.wcf, # 提供wcf对象,让插件可以直接发送消息
|
||||
"message_util": self.message_util # 提供消息工具类
|
||||
}
|
||||
|
||||
# 检查插件是否可以处理该消息
|
||||
if plugin.can_process(plugin_msg):
|
||||
processed, _ = plugin.process_message(plugin_msg)
|
||||
if processed:
|
||||
# 发布消息处理事件
|
||||
self.event_system.publish(EventType.MESSAGE_PROCESSED, {
|
||||
"message": msg,
|
||||
"plugin": plugin.name
|
||||
})
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
self.LOG.error(f"插件 {plugin.name} 处理消息失败: {e}")
|
||||
|
||||
return False
|
||||
|
||||
# ============================================== 业务内容==========================================================
|
||||
|
||||
def news_baidu_report_auto(self) -> None:
|
||||
|
||||
Reference in New Issue
Block a user