插件化项目优化,支持将代码改造为插件,支持自动加载

This commit is contained in:
liuwei
2025-03-18 13:57:39 +08:00
parent 3c757161c4
commit bcca2dab28
13 changed files with 1033 additions and 388 deletions

10
main.py
View File

@@ -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)

View File

@@ -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)

View 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, [])

View 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("子类必须实现此方法")

View 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

View 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)

View 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)]

View 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

View File

@@ -0,0 +1,3 @@
# 插件初始化文件
# 从main模块导入插件类
from .main import MessageSummaryPlugin

View 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"

View 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

View File

@@ -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

View File

@@ -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: