From 39892fa5eefe1a0416e89157fcdda079c8ce67f2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kyoku=E2=AD=90?=
<144906395+KyokuKong@users.noreply.github.com>
Date: Sun, 22 Sep 2024 20:19:43 +0800
Subject: [PATCH 1/9] =?UTF-8?q?refact=EF=BC=9A=E5=87=86=E5=A4=87=E9=87=8D?=
=?UTF-8?q?=E6=9E=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 75 ---
adapter/adapter.go | 18 -
adapter/bot.go | 104 ----
adapter/lagrange/element_converter.go | 107 ----
adapter/lagrange/lagrange_adapter.go | 143 ------
adapter/lagrange/lagrange_bot.go | 533 -------------------
adapter/lagrange/lagrange_config.go | 21 -
adapter/lagrange/lagrange_handler.go | 97 ----
adapter/lagrange/lagrange_subscriber.go | 312 ------------
config/config_manager.go | 233 ---------
config/iceinu_config.go | 22 -
elements/elements.go | 650 ------------------------
go.mod | 25 -
ice/adapter_events.go | 27 -
ice/event_bus.go | 90 ----
ice/uni_events.go | 28 -
logger/logger.go | 141 -----
logger/protocol_logger.go | 49 --
main.go | 54 --
resource/channel.go | 8 -
resource/files.go | 35 --
resource/group.go | 23 -
resource/interaction.go | 11 -
resource/login.go | 10 -
resource/message.go | 18 -
resource/paged_list.go | 6 -
resource/reaction.go | 3 -
resource/user.go | 9 -
utils/jprint.go | 23 -
utils/satorize.go | 17 -
30 files changed, 2892 deletions(-)
delete mode 100644 README.md
delete mode 100644 adapter/adapter.go
delete mode 100644 adapter/bot.go
delete mode 100644 adapter/lagrange/element_converter.go
delete mode 100644 adapter/lagrange/lagrange_adapter.go
delete mode 100644 adapter/lagrange/lagrange_bot.go
delete mode 100644 adapter/lagrange/lagrange_config.go
delete mode 100644 adapter/lagrange/lagrange_handler.go
delete mode 100644 adapter/lagrange/lagrange_subscriber.go
delete mode 100644 config/config_manager.go
delete mode 100644 config/iceinu_config.go
delete mode 100644 elements/elements.go
delete mode 100644 go.mod
delete mode 100644 ice/adapter_events.go
delete mode 100644 ice/event_bus.go
delete mode 100644 ice/uni_events.go
delete mode 100644 logger/logger.go
delete mode 100644 logger/protocol_logger.go
delete mode 100644 main.go
delete mode 100644 resource/channel.go
delete mode 100644 resource/files.go
delete mode 100644 resource/group.go
delete mode 100644 resource/interaction.go
delete mode 100644 resource/login.go
delete mode 100644 resource/message.go
delete mode 100644 resource/paged_list.go
delete mode 100644 resource/reaction.go
delete mode 100644 resource/user.go
delete mode 100644 utils/jprint.go
delete mode 100644 utils/satorize.go
diff --git a/README.md b/README.md
deleted file mode 100644
index 6b2da8f..0000000
--- a/README.md
+++ /dev/null
@@ -1,75 +0,0 @@
-# 🧊Iceinu 氷犬
-
-![Go Badge](https://img.shields.io/badge/Go-1.22%2B-cyan?logo=go)
-[![workflow](https://github.com/Iceinu-Project/iceinu/actions/workflows/go.yml/badge.svg)](https://github.com/Iceinu-Project/iceinu/actions)
-[![goreportcard](https://img.shields.io/badge/go%20report-A+-brightgreen.svg?style=flat)](https://goreportcard.com/report/github.com/Iceinu-Project/iceinu)
-[![QQGroup Badge](https://img.shields.io/badge/QQ群-970801565-blue?)](https://qm.qq.com/q/93crfU39ny)
-
-氷犬Iceinu 是一个多用途的Go语言聊天机器人框架,可以将其作为开发套件来进行二次开发,亦或者作为库按需引入来快速编写自己的聊天机器人(暂时没有实现)。
-
-🚧暂时还在~~画饼~~施工中,晚点再来探索吧~
-
-## 开发进度
-
-目前距离第一个Release版本的发布还有很多需要进行的工作:
-- [x] 基础的事件总线系统
-- [x] 内置LagrangeGo适配器的部分事件发送
-- [x] 类Satori的消息解析
-- [x] 配置读取/生成/补全
-- [ ] 数据库连接,统一数据库接口设计
-- [ ] 消息发送/统一Client设计
-- [ ] 插件系统设计和实现
-- [ ] 完善内置LagrangeGo适配器的事件接收和处理
-- [ ] 集群模式设计和实现(集群的动态和静态总控模式)
-- [ ] 排障和性能优化
-- [ ] 自动化测试
-- [ ] 确定各项基础程序设计,编写使用文档
-
-## 特性
-
-~~哪里是特性,完全是画饼,一半都还没实现完~~
-
-- 基于Go开发,性能表现良好
-- 基于统一事件驱动的消息推送机制
-- 以Satori作为基础实现了统一消息标准
-- 可直接发送Satori标准的XHTML消息
-- 模块化适配器设计
-- 动态/静态集群,跨平台集群
-- 完整的动态权限管理系统
-- 在插件间共享数据库连接池
-- 类Alconna的命令解析器
-- 可配置自动向指定用户/频道发送日志
-- 主动信息推送/订阅机制
-- 从HTML+CSS模板渲染图片(基于wkhtmltoimage集成,未来可能会实现)
-
-## 直接部署
-
-(目前仍然属于开发前期,部署了也暂时没什么用处)
-
-访问[Github Action](https://github.com/Iceinu-Project/iceinu/actions)就可以获取Iceinu的自动构建二进制文件,Iceinu默认集成了`LagrangeGo`所以无需再配置onebot
-协议连接,第一次启动时会自动检测并生成配置文件,完成配置之后在命令行中输入回车即可开始运行。
-
-你可以参照Iceinu数据库配置指南来配置Iceinu使用的PostgreSQL数据库。
-
-Iceinu在设计上支持集群部署,且支持动态组网式集群(需要各个Bot实例之间可以相互访问)和静态总控式集群(需要一个Bot实例作为总控,这个实例本身不能下线)
-
-## 二次开发
-
-(文档还没写,Release之前会开始编写文档)
-
-Go语言的静态特性让它非常不怎么适合传统意义上的模块化加载,所以Iceinu并没有也不会实现从外部进行的插件加载。
-
-不过Iceinu通过接口定义了内部插件的实现,直接拉取代码跟着插件文档进行二次开发即可扩展更多的插件功能。
-
-```shell
-git clone git@github.com:Iceinu-Project/iceinu.git
-```
-
-## 鸣谢
-
-- [Lagrange.Core](https://github.com/LagrangeDev/Lagrange.Core) NTQQ通信协议的C#实现
-- [LagrangeGo](https://github.com/LagrangeDev/LagrangeGo) Lagrange.Core的Go实现
-- [LagrangeGo-Teamplate](https://github.com/ExquisiteCore/LagrangeGo-Template) LagrangeGo的模板示例项目
-- [go-cqhttp](https://github.com/Mrs4s/go-cqhttp) 基于 Mirai 以及 MiraiGo 的 OneBot Golang 原生实现
-- [ZeroBot](https://github.com/wdvxdr1123/ZeroBot) 基于onebot协议的Golang聊天机器人开发框架
-- [Logrus](https://github.com/sirupsen/logrus) 强大好用的Go日志库
\ No newline at end of file
diff --git a/adapter/adapter.go b/adapter/adapter.go
deleted file mode 100644
index f9806c0..0000000
--- a/adapter/adapter.go
+++ /dev/null
@@ -1,18 +0,0 @@
-package adapter
-
-// 继承IceAdapter接口即可实现适配器,具体的适配器实现模式参考文档
-
-// IceAdapterMeta 适配器元信息
-type IceAdapterMeta struct {
- AdapterName string // 适配器名称
- Version string // 适配器版本
- Platform string // 适配器平台
- Author []string // 适配器作者
- Introduce string // 适配器介绍
-}
-
-// IceAdapter Iceinu的适配器接口,用于实现适配器实例
-type IceAdapter interface {
- GetMeta() *IceAdapterMeta // 获取适配器的元信息
- Init() // 适配器初始化
-}
diff --git a/adapter/bot.go b/adapter/bot.go
deleted file mode 100644
index 0a4fa0f..0000000
--- a/adapter/bot.go
+++ /dev/null
@@ -1,104 +0,0 @@
-package adapter
-
-import (
- "github.com/Iceinu-Project/iceinu/elements"
- "github.com/Iceinu-Project/iceinu/resource"
-)
-
-// Bot Iceinu的客户端接口API,用于实现iceinu对平台客户端的直接操作
-type Bot interface {
- // 首先是基于Satori标准的API
- // 这部分可以参考Satori文档的资源部分,但是有一定的不同,为了方便兼容各种使用方式进行了一些扩展
- // 比如没有直接支持分页这个东西,方便使用
- // https://satori.js.org/zh-CN/resources/channel.html
-
- GetChannel(channelId string) *resource.Channel // 获取频道信息
- GetChannelList(groupId string) []*resource.Channel // 获取指定群组中的频道列表
- GetChannelListByToken(next string) *resource.PagedList // 获取指定群组中的频道列表(分页)
- CreateChannel(groupId string, data *resource.Channel) (*resource.Channel, error) // 创建频道
- UpdateChannel(groupId string, data *resource.Channel) error // 更新频道
- DeleteChannel(channelId string) error // 删除频道
- MuteChannel(channelId string, duration uint32) error // 禁言频道
- CreateUserChannel(userId string, groupId string) (*resource.Channel, error) // 创建用户(私聊)频道
-
- GetGroup(groupId string) *resource.Group // 获取群组信息
- GetGroupList() []*resource.Group // 获取群组列表
- GetGroupListByToken(next string) *resource.PagedList // 获取群组列表(分页)
- ApproveGroupInvite(messageId string, approve bool, comment string) error // 处理群组邀请bot加入请求
-
- GetGroupMember(groupId string, userId string) *resource.GroupMember // 获取指定群组成员信息
- GetGroupMemberList(groupId string) []*resource.GroupMember // 获取群组成员列表
- GetGroupMemberListByToken(groupId string, next string) *resource.PagedList // 获取群组成员列表(分页)
- KickGroupMember(groupId string, userId string, permanent bool) error // 踢出群组成员
- MuteGroupMember(groupId string, userId string, duration uint32) error // 禁言群组成员
- ApproveGroupRequest(messageId string, approve bool, comment string) error // 处理群组加入请求
-
- SetGroupMemberRole(groupId string, userId string, roleId string) error // 设置群组成员角色权限
- UnsetGroupMemberRole(groupId string, userId string, roleId string) error // 取消群组成员角色权限
- GetGroupRoleList(groupId string) []*resource.GroupRole // 获取群组角色权限列表
- GetGroupRoleListByToken(groupId string, next string) *resource.PagedList // 获取群组角色权限列表(分页)
- CreateGroupRole(groupId string, role *resource.GroupRole) (*resource.GroupRole, error) // 创建群组角色权限
- UpdateGroupRole(groupId string, roleId string, role *resource.GroupRole) error // 更新群组角色权限
- DeleteGroupRole(groupId string, roleId string) error // 删除群组角色权限
-
- GetLoginInfo() *resource.Login // 获取登录信息
-
- SendContent(groupId string, channelId string, content string) (*resource.Message, error) // 发送纯文本消息
- GetMessage(channelId string, messageId string) (*resource.Message, error) // (从缓存中)获取指定消息
- RecallMessage(channelId string, messageId string) error // 撤回指定消息
- UpdateMessage(channelId string, messageId string, content string) error // 编辑指定消息
- GetMessageList(channelId string, limit uint32, order bool) []*resource.Message // 获取一定数量的频道消息列表
- GetMessageListByRange(channelId string, messageId string, start uint32, count uint32) []*resource.Message // 获取频道消息列表(可指定范围)
-
- CreateReaction(channelId string, messageId string, emoji string) error // 添加消息反应
- DeleteReaction(channelId string, messageId string, emoji string, userId string) error // 删除消息反应
- ClearReaction(channelId string, messageId string, emoji string) error // 清除消息反应
- GetReactionList(channelId string, messageId string, emoji string) []resource.User // 获取消息反应列表
- GetReactionListByToken(channelId string, messageId string, emoji string, next string) *resource.PagedList // 获取消息反应列表(分页)
-
- GetUser(userId string) *resource.User // 获取指定用户信息
- GetFriendList() []*resource.User // 获取好友列表信息
- ApproveFriendRequest(messageId string, approve string, comment string) error // 处理好友请求
-
- // Iceinu的特有API
- // 其中一部分是对各个平台功能的扩展适配,还有一部分是其他功能的快捷方式
-
- Send(groupId string, channelId string, elements []elements.IceinuMessageElement) (*resource.Message, error) // 直接发送Iceinu通用元素
- SendSatori(groupId string, channelId string, satori string) (*resource.Message, error) // 发送Satori XHTML格式的消息字符串,自动解析成通用元素并发送
- SendPoke(groupId string, channelId string, userId string) error // 发送戳一戳
-
- GetSelfUserId() string // 获取自己的用户ID
- GetSelfUserName() string // 获取自己的用户名
- GetSelfAvatarUrl() string // 获取自己的头像URL
- GetSeldUserNickname() string // 获取自己的昵称
- GetGroupAvatarUrl(groupId string) string // 获取指定群组的头像URL
-
- RefreshUserListCache() error // 刷新用户列表
- RefreshGroupListCache() error // 刷新群组列表
- RefreshGroupMemberCache(groupId string, userId string) error // 刷新指定群组的指定成员的信息
- RefreshGroupAllMembersCache(groupId string) error // 刷新指定群组所有成员的信息
- RefreshChannelListCache(groupId string) error // 刷新指定群组的频道列表
-
- RenameGroup(groupId string, name string) error // 重命名群组
- RenameGroupMember(groupId string, userId string, nickname string) error // 重命名群组成员
- RemarkGroup(groupId string, remark string) error // 设置群组备注
- SetGroupGlobalMute(groupId string, mute bool) error // 设置群组全员禁言
- LeaveGroup(groupId string) error // 退出群组
- SetGroupMemberTitle(groupId string, userId string, title string) error // 给群组成员设置头衔
-
- // 这部分功能接口设计主要来自LagrangeGo,但是也可能在其他NTQQ平台上实现
-
- UploadChannelFile(channelId string, filePath string) error // 向频道上传文件
- UploadGroupFile(groupId string, filePath string, targetFilePath string) error // 向群组上传文件
- GetGroupFileSystemInfo(groupId string) *resource.GroupFileSystemInfo // 获取群组文件系统信息(暂未确定)
- GetGroupFilesByFolder(groupId string, folderId string) interface{} // 获取群组文件夹内的文件列表(暂未确定)
- GetGroupRootFiles(groupId string) interface{} // 获取群组根目录文件列表(暂未确定)
- MoveGroupFile(groupId string, fileId string, parentFolder string, targetFolderId string) error // 移动群组文件
- DeleteGroupFile(groupId string, fileId string) error // 删除群组文件
- CreateGroupFolder(groupId string, folderName string, parentFolder string) error // 创建群组文件夹
- RenameGroupFolder(groupId string, folderId string, newFolderName string) error // 重命名群组文件夹
- DeleteGroupFolder(groupId string, folderId string) error // 删除群组文件夹
-
- // GetOriginalClient 获取适配器的原始客户端对象,部分适配器可能不需要这个东西,只是方便直接传递原本的客户端实例
- GetOriginalClient() interface{} // 获取原始客户端对象
-}
diff --git a/adapter/lagrange/element_converter.go b/adapter/lagrange/element_converter.go
deleted file mode 100644
index 1840cf5..0000000
--- a/adapter/lagrange/element_converter.go
+++ /dev/null
@@ -1,107 +0,0 @@
-package lagrange
-
-import (
- "github.com/Iceinu-Project/iceinu/elements"
- "github.com/LagrangeDev/LagrangeGo/message"
- "strconv"
- "strings"
- "time"
-)
-
-// ConvertIceElement 将LagrangeGo的元素转换为Iceinu的元素
-func ConvertIceElement(e []message.IMessageElement) *[]elements.IceinuMessageElement {
- // 从LagrangeGo的元素转换为Iceinu的元素
- var IceinuElements []elements.IceinuMessageElement
- // 遍历传入的元素
- for _, ele := range e {
- switch ele.Type() {
- // 将元素依次对应转换并传入
- case message.Text:
- ele := ele.(*message.TextElement)
- // 检测文本中是否包含换行符
- if strings.Contains(ele.Content, "\n") {
- // 如果包含换行符,进行拆分和处理
- textParts := strings.Split(ele.Content, "\n")
- for i, part := range textParts {
- // 将每段文本添加到 IceinuElements
- IceinuElements = append(IceinuElements, &elements.TextElement{Text: part})
- // 如果不是最后一段文本,则插入 BrElement
- if i < len(textParts)-1 {
- IceinuElements = append(IceinuElements, &elements.BrElement{})
- }
- }
- } else {
- // 如果不包含换行符,直接添加文本元素
- IceinuElements = append(IceinuElements, &elements.TextElement{Text: ele.Content})
- }
- case message.At:
- ele := ele.(*message.AtElement)
- IceinuElements = append(IceinuElements, &elements.AtElement{
- Id: strconv.Itoa(int(ele.TargetUin)),
- Name: ele.Display,
- Role: "",
- Type: strconv.Itoa(int(ele.Type())),
- })
- case message.Face:
- ele := ele.(*message.FaceElement)
- IceinuElements = append(IceinuElements, &elements.FaceElement{
- Id: ele.FaceID,
- })
- case message.Voice:
- ele := ele.(*message.VoiceElement)
- IceinuElements = append(IceinuElements, &elements.AudioElement{
- Src: ele.Url,
- Title: ele.Name,
- Duration: ele.Size,
- Poster: "",
- })
- case message.Image:
- ele := ele.(*message.ImageElement)
- IceinuElements = append(IceinuElements, &elements.ImageElement{
- Src: ele.Url,
- Width: ele.Width,
- Height: ele.Height,
- Title: ele.ImageId,
- })
- case message.File:
- ele := ele.(*message.FileElement)
- IceinuElements = append(IceinuElements, &elements.FileElement{
- Src: ele.FileUrl,
- Title: ele.FileName,
- })
- case message.Reply:
- ele := ele.(*message.ReplyElement)
- IceinuElements = append(IceinuElements, &elements.QuoteElement{
- UserId: strconv.Itoa(int(ele.SenderUin)),
- UserName: ele.SenderUid,
- GroupId: strconv.Itoa(int(ele.GroupUin)),
- Timestamp: time.Unix(int64(ele.Time), 0),
- Elements: ConvertIceElement(ele.Elements),
- })
- case message.Forward:
- ele := ele.(*message.ForwardMessage)
- IceinuElements = append(IceinuElements, &elements.MessageElement{
- Forward: true,
- Elements: UnzipNodes(ele.Nodes),
- })
-
- default:
- IceinuElements = append(IceinuElements, &elements.UnsupportedElement{Type: strconv.Itoa(int(ele.Type()))})
- }
- }
- return &IceinuElements
-}
-
-func UnzipNodes(n []*message.ForwardNode) *[]elements.IceinuMessageElement {
- var IceinuElements []elements.IceinuMessageElement
- for _, node := range n {
- IceinuElements = append(IceinuElements, &elements.NodeElement{
- GroupId: node.GroupId,
- SenderId: node.SenderId,
- SenderName: node.SenderName,
- Time: node.Time,
- Message: ConvertIceElement(node.Message),
- })
- }
- return &IceinuElements
-}
diff --git a/adapter/lagrange/lagrange_adapter.go b/adapter/lagrange/lagrange_adapter.go
deleted file mode 100644
index f2d6ce5..0000000
--- a/adapter/lagrange/lagrange_adapter.go
+++ /dev/null
@@ -1,143 +0,0 @@
-package lagrange
-
-import (
- "github.com/Iceinu-Project/iceinu/adapter"
- "github.com/Iceinu-Project/iceinu/config"
- "github.com/Iceinu-Project/iceinu/ice"
- "github.com/Iceinu-Project/iceinu/logger"
- "github.com/Iceinu-Project/iceinu/utils"
- "github.com/LagrangeDev/LagrangeGo/client"
- "github.com/LagrangeDev/LagrangeGo/client/auth"
- "github.com/sirupsen/logrus"
- "os"
- "os/signal"
- "syscall"
- "time"
-)
-
-type AdapterLagrange struct {
-}
-
-type Bot struct {
- *client.QQClient
-}
-
-var LgrClient *Bot
-var lagrangeConfig *AdapterLagrangeConfig
-
-func (lgr *AdapterLagrange) GetMeta() *adapter.IceAdapterMeta {
- return &adapter.IceAdapterMeta{
- AdapterName: "Lagrange Adapter",
- Version: "β0.2.1",
- Platform: "NTQQ",
- Author: []string{
- "Kyoku",
- },
- Introduce: "基于Lagrange的NTQQ适配器,内置了LagrangeGo,无需再连接额外的协议端。",
- }
-}
-
-func (lgr *AdapterLagrange) Init() {
- logger.Infof("正在初始化Lagrange适配器,适配器当前版本: %s", lgr.GetMeta().Version)
-
- // 获取配置文件内容
- cm := config.GetManager()
- lagrangeConfig := cm.Get("lagrange.toml").(*AdapterLagrangeConfig)
- logger.Infof("%v", lagrangeConfig.Password == "")
-
- // 发送一个适配器初始化事件
- ice.Bus.Publish("AdapterInitEvent", ice.AdapterInitEvent{
- Timestamp: time.Time{},
- AdapterMeta: lgr.GetMeta(),
- })
-
- // 创建LagrangeGo的客户端实例
- plogger := logger.GetProtocolLogger()
- appInfo := auth.AppList["linux"]["3.2.10-25765"]
- deviceInfo := auth.NewDeviceInfo(lagrangeConfig.Account)
- qqClientInstance := client.NewClient(uint32(lagrangeConfig.Account), appInfo, lagrangeConfig.SignUrl)
- qqClientInstance.SetLogger(plogger)
- qqClientInstance.UseDevice(deviceInfo)
-
- data, err := os.ReadFile("signature.bin")
- if err != nil {
- logrus.Warnln("读取签名文件时发生错误:", err)
- } else {
- sig, err := auth.UnmarshalSigInfo(data, true)
- if err != nil {
- logrus.Warnln("加载签名文件时发生错误:", err)
- } else {
- qqClientInstance.UseSig(sig)
- }
- }
- LgrClient = &Bot{QQClient: qqClientInstance}
-
- defer LgrClient.Release()
- defer SaveSignature()
-
- // 登录
- err = Login()
- if err != nil {
- return
- }
-
- var bot = GetBot()
-
- // 刷新client的缓存
- err = LgrClient.RefreshAllGroupsInfo()
- if err != nil {
- return
- }
- err = LgrClient.RefreshFriendCache()
- if err != nil {
- return
- }
- err = LgrClient.RefreshFriendCache()
-
- utils.JPrint(bot.GetGroupMemberList("970801565"))
- bot.SendContent("0", "2913844577", "测试消息")
-
- // 设置事件订阅器,将LagrangeGo的事件转换并发送到iceinu的事件总线上
- SetAllHandler()
- SetAllSubscribes()
-
- // 主协程关闭通道
- mc := make(chan os.Signal, 2)
- signal.Notify(mc, os.Interrupt, syscall.SIGTERM)
- for {
- switch <-mc {
- case os.Interrupt, syscall.SIGTERM:
- return
- }
- }
-}
-
-// Login 登录
-func Login() error {
- // 声明 err 变量并进行错误处理
- err := LgrClient.Login("", "qrcode.png")
- if err != nil {
- logrus.Errorln("登录时发生错误:", err)
- return err
- }
- // 推送登录事件
- ice.Bus.Publish("AdapterLoginEvent", ice.AdapterInitEvent{
- Timestamp: time.Time{},
- })
- return nil
-}
-
-// SaveSignature 保存sign信息
-func SaveSignature() {
- data, err := LgrClient.Sig().Marshal()
- if err != nil {
- logger.Error("生成签名文件时发生错误err:", err)
- return
- }
- err = os.WriteFile("signature.bin", data, 0644)
- if err != nil {
- logger.Error("写入签名文件时发生错误 err:", err)
- return
- }
- logger.Info("签名已被写入签名文件")
-}
diff --git a/adapter/lagrange/lagrange_bot.go b/adapter/lagrange/lagrange_bot.go
deleted file mode 100644
index 54bab36..0000000
--- a/adapter/lagrange/lagrange_bot.go
+++ /dev/null
@@ -1,533 +0,0 @@
-package lagrange
-
-import (
- "github.com/Iceinu-Project/iceinu/elements"
- "github.com/Iceinu-Project/iceinu/resource"
- "github.com/LagrangeDev/LagrangeGo/message"
- "strconv"
- "time"
-)
-
-type BotLagrange struct {
-}
-
-func (b BotLagrange) GetChannel(channelId string) *resource.Channel {
- //TODO implement me
- panic("implement me")
-}
-
-func (b BotLagrange) GetChannelList(groupId string) []*resource.Channel {
- //TODO implement me
- panic("implement me")
-}
-
-func (b BotLagrange) GetChannelListByToken(next string) *resource.PagedList {
- //TODO implement me
- panic("implement me")
-}
-
-func (b BotLagrange) CreateChannel(groupId string, data *resource.Channel) (*resource.Channel, error) {
- //TODO implement me
- panic("implement me")
-}
-
-func (b BotLagrange) UpdateChannel(groupId string, data *resource.Channel) error {
- //TODO implement me
- panic("implement me")
-}
-
-func (b BotLagrange) DeleteChannel(channelId string) error {
- //TODO implement me
- panic("implement me")
-}
-
-func (b BotLagrange) MuteChannel(channelId string, duration uint32) error {
- //TODO implement me
- panic("implement me")
-}
-
-func (b BotLagrange) CreateUserChannel(userId string, groupId string) (*resource.Channel, error) {
- //TODO implement me
- panic("implement me")
-}
-
-func (b BotLagrange) GetGroup(groupId string) *resource.Group {
- groupUin, _ := strconv.Atoi(groupId)
- group := LgrClient.GetCachedGroupInfo(uint32(groupUin))
- return &resource.Group{
- Id: strconv.Itoa(int(group.GroupUin)),
- Name: group.GroupName,
- Avatar: group.Avatar,
- Maxcount: group.MaxMember,
- MemberCount: group.MemberCount,
- }
-}
-
-func (b BotLagrange) GetGroupList() []*resource.Group {
- info, _ := LgrClient.GetAllGroupsInfo()
- groups := make([]*resource.Group, 0)
- for _, group := range info {
- groups = append(groups, &resource.Group{
- Id: strconv.Itoa(int(group.GroupUin)),
- Name: group.GroupName,
- Avatar: group.Avatar,
- Maxcount: group.MaxMember,
- MemberCount: group.MemberCount,
- })
- }
- return groups
-}
-
-func (b BotLagrange) GetGroupListByToken(next string) *resource.PagedList {
- info, _ := LgrClient.GetAllGroupsInfo()
- groups := make([]*resource.Group, 0)
- for _, group := range info {
- groups = append(groups, &resource.Group{
- Id: strconv.Itoa(int(group.GroupUin)),
- Name: group.GroupName,
- Avatar: group.Avatar,
- Maxcount: group.MaxMember,
- MemberCount: group.MemberCount,
- })
- }
- next = ""
- return &resource.PagedList{
- Data: groups,
- Next: "",
- }
-}
-
-func (b BotLagrange) ApproveGroupInvite(messageId string, approve bool, comment string) error {
- messageId = ""
- approve = false
- comment = ""
- return nil
-}
-
-func (b BotLagrange) GetGroupMember(groupId string, userId string) *resource.GroupMember {
- groupIdInt, _ := strconv.Atoi(groupId)
- userIdInt, _ := strconv.Atoi(userId)
- members, err := LgrClient.GetGroupMembersData(uint32(groupIdInt))
- if err != nil {
- return nil
- }
- if member, ok := members[uint32(userIdInt)]; ok {
- return &resource.GroupMember{
- User: &resource.User{
- Id: userId,
- Name: member.MemberName,
- Nickname: member.DisplayName(),
- Avatar: member.Avatar,
- IsBot: false,
- },
- Nickname: member.DisplayName(),
- Avatar: member.Avatar,
- JoinedAt: time.Unix(int64(member.JoinTime), 0),
- }
- }
- // 否则返回空
- return nil
-}
-
-func (b BotLagrange) GetGroupMemberList(groupId string) []*resource.GroupMember {
- groupIdInt, _ := strconv.Atoi(groupId)
- members, err := LgrClient.GetGroupMembersData(uint32(groupIdInt))
- if err != nil {
- return nil
- }
- groupMembers := make([]*resource.GroupMember, 0)
- for _, member := range members {
- groupMembers = append(groupMembers, &resource.GroupMember{
- User: &resource.User{
- Id: strconv.Itoa(int(member.Uin)),
- Name: member.MemberName,
- Nickname: member.DisplayName(),
- Avatar: member.Avatar,
- IsBot: false,
- },
- Nickname: member.DisplayName(),
- Avatar: member.Avatar,
- JoinedAt: time.Unix(int64(member.JoinTime), 0),
- })
- }
- return groupMembers
-}
-
-func (b BotLagrange) GetGroupMemberListByToken(groupId string, _ string) *resource.PagedList {
- groupIdInt, _ := strconv.Atoi(groupId)
- members, err := LgrClient.GetGroupMembersData(uint32(groupIdInt))
- if err != nil {
- return nil
- }
- groupMembers := make([]*resource.GroupMember, 0)
- for _, member := range members {
- groupMembers = append(groupMembers, &resource.GroupMember{
- User: &resource.User{
- Id: strconv.Itoa(int(member.Uin)),
- Name: member.MemberName,
- Nickname: member.DisplayName(),
- Avatar: member.Avatar,
- IsBot: false,
- },
- Nickname: member.DisplayName(),
- Avatar: member.Avatar,
- JoinedAt: time.Unix(int64(member.JoinTime), 0),
- })
- }
- return &resource.PagedList{
- Data: groupMembers,
- Next: "",
- }
-}
-
-func (b BotLagrange) KickGroupMember(groupId string, userId string, permanent bool) error {
- groupIdInt, _ := strconv.Atoi(groupId)
- userIdInt, _ := strconv.Atoi(userId)
- err := LgrClient.GroupKickMember(uint32(groupIdInt), uint32(userIdInt), permanent)
- if err != nil {
- return err
- }
- return nil
-}
-
-func (b BotLagrange) MuteGroupMember(groupId string, userId string, duration uint32) error {
- groupIdInt, _ := strconv.Atoi(groupId)
- userIdInt, _ := strconv.Atoi(userId)
- err := LgrClient.GroupMuteMember(uint32(groupIdInt), uint32(userIdInt), duration)
- if err != nil {
- return err
- }
- return nil
-}
-
-func (b BotLagrange) ApproveGroupRequest(messageId string, approve bool, comment string) error {
- // 晚点实现
- panic("implement me")
-}
-
-// SetGroupMemberRole 设置群成员角色,在NTQQ中实际上只能是设置管理员
-func (b BotLagrange) SetGroupMemberRole(groupId string, userId string, _ string) error {
- groupIdInt, _ := strconv.Atoi(groupId)
- userIdInt, _ := strconv.Atoi(userId)
- err := LgrClient.GroupSetAdmin(uint32(groupIdInt), uint32(userIdInt), true)
- if err != nil {
- return err
- }
- return nil
-}
-
-// UnsetGroupMemberRole 取消群成员角色,在NTQQ中实际上只能是取消管理员
-func (b BotLagrange) UnsetGroupMemberRole(groupId string, userId string, _ string) error {
- groupIdInt, _ := strconv.Atoi(groupId)
- userIdInt, _ := strconv.Atoi(userId)
- err := LgrClient.GroupSetAdmin(uint32(groupIdInt), uint32(userIdInt), false)
- if err != nil {
- return err
- }
- return nil
-}
-
-func (b BotLagrange) GetGroupRoleList(_ string) []*resource.GroupRole {
- return []*resource.GroupRole{
- {
- Id: "1",
- Name: "管理员",
- },
- {
- Id: "2",
- Name: "群员",
- },
- }
-}
-
-func (b BotLagrange) GetGroupRoleListByToken(groupId string, next string) *resource.PagedList {
- return &resource.PagedList{
- Data: []*resource.GroupRole{
- {
- Id: "1",
- Name: "管理员",
- },
- {
- Id: "2",
- Name: "群员",
- },
- },
- Next: "",
- }
-}
-
-func (b BotLagrange) CreateGroupRole(_ string, _ *resource.GroupRole) (*resource.GroupRole, error) {
- return nil, nil
-}
-
-func (b BotLagrange) UpdateGroupRole(_ string, _ string, _ *resource.GroupRole) error {
- return nil
-}
-
-func (b BotLagrange) DeleteGroupRole(_ string, _ string) error {
- return nil
-}
-
-func (b BotLagrange) GetLoginInfo() *resource.Login {
- return nil
-}
-
-func (b BotLagrange) SendContent(groupId string, channelId string, content string) (*resource.Message, error) {
- groupIdInt, _ := strconv.Atoi(groupId)
- channelIdInt, _ := strconv.Atoi(channelId)
- var msg []message.IMessageElement
- if groupIdInt == 0 {
- msg = append(msg, message.NewText(content))
- res, err := LgrClient.SendPrivateMessage(uint32(channelIdInt), msg)
- if err != nil {
- return nil, err
- }
- return &resource.Message{
- Id: strconv.Itoa(int(res.Id)),
- Content: res.ToString(),
- Channel: nil,
- Group: nil,
- Member: nil,
- User: nil,
- CreatedAt: time.Unix(int64(res.Time), 0),
- UpdatedAt: time.Time{},
- MessageElements: ConvertIceElement(res.Elements),
- }, nil
- } else {
- msg = append(msg, message.NewText(content))
- res, err := LgrClient.SendGroupMessage(uint32(groupIdInt), msg)
- if err != nil {
- return nil, err
- }
- return &resource.Message{
- Id: strconv.Itoa(int(res.Id)),
- Content: res.ToString(),
- Channel: nil,
- Group: nil,
- Member: nil,
- User: nil,
- CreatedAt: time.Unix(int64(res.Time), 0),
- UpdatedAt: time.Time{},
- MessageElements: ConvertIceElement(res.Elements),
- }, nil
- }
-}
-
-func (b BotLagrange) GetMessage(channelId string, messageId string) (*resource.Message, error) {
- //TODO implement me
- panic("implement me")
-}
-
-func (b BotLagrange) RecallMessage(channelId string, messageId string) error {
- //TODO implement me
- panic("implement me")
-}
-
-func (b BotLagrange) UpdateMessage(channelId string, messageId string, content string) error {
- //TODO implement me
- panic("implement me")
-}
-
-func (b BotLagrange) GetMessageList(channelId string, limit uint32, order bool) []*resource.Message {
- //TODO implement me
- panic("implement me")
-}
-
-func (b BotLagrange) GetMessageListByRange(channelId string, messageId string, start uint32, count uint32) []*resource.Message {
- //TODO implement me
- panic("implement me")
-}
-
-func (b BotLagrange) CreateReaction(channelId string, messageId string, emoji string) error {
- //TODO implement me
- panic("implement me")
-}
-
-func (b BotLagrange) DeleteReaction(channelId string, messageId string, emoji string, userId string) error {
- //TODO implement me
- panic("implement me")
-}
-
-func (b BotLagrange) ClearReaction(channelId string, messageId string, emoji string) error {
- //TODO implement me
- panic("implement me")
-}
-
-func (b BotLagrange) GetReactionList(channelId string, messageId string, emoji string) []resource.User {
- //TODO implement me
- panic("implement me")
-}
-
-func (b BotLagrange) GetReactionListByToken(channelId string, messageId string, emoji string, next string) *resource.PagedList {
- //TODO implement me
- panic("implement me")
-}
-
-func (b BotLagrange) GetUser(userId string) *resource.User {
- //TODO implement me
- panic("implement me")
-}
-
-func (b BotLagrange) GetFriendList() []*resource.User {
- //TODO implement me
- panic("implement me")
-}
-
-func (b BotLagrange) ApproveFriendRequest(messageId string, approve string, comment string) error {
- //TODO implement me
- panic("implement me")
-}
-
-func (b BotLagrange) Send(groupId string, channelId string, elements []elements.IceinuMessageElement) (*resource.Message, error) {
- //TODO implement me
- panic("implement me")
-}
-
-func (b BotLagrange) SendSatori(groupId string, channelId string, satori string) (*resource.Message, error) {
- //TODO implement me
- panic("implement me")
-}
-
-func (b BotLagrange) SendPoke(groupId string, channelId string, userId string) error {
- //TODO implement me
- panic("implement me")
-}
-
-func (b BotLagrange) GetSelfUserId() string {
- //TODO implement me
- panic("implement me")
-}
-
-func (b BotLagrange) GetSelfUserName() string {
- //TODO implement me
- panic("implement me")
-}
-
-func (b BotLagrange) GetSelfAvatarUrl() string {
- //TODO implement me
- panic("implement me")
-}
-
-func (b BotLagrange) GetSeldUserNickname() string {
- //TODO implement me
- panic("implement me")
-}
-
-func (b BotLagrange) GetGroupAvatarUrl(groupId string) string {
- //TODO implement me
- panic("implement me")
-}
-
-func (b BotLagrange) RefreshUserListCache() error {
- //TODO implement me
- panic("implement me")
-}
-
-func (b BotLagrange) RefreshGroupListCache() error {
- //TODO implement me
- panic("implement me")
-}
-
-func (b BotLagrange) RefreshGroupMemberCache(groupId string, userId string) error {
- //TODO implement me
- panic("implement me")
-}
-
-func (b BotLagrange) RefreshGroupAllMembersCache(groupId string) error {
- //TODO implement me
- panic("implement me")
-}
-
-func (b BotLagrange) RefreshChannelListCache(groupId string) error {
- //TODO implement me
- panic("implement me")
-}
-
-func (b BotLagrange) RenameGroup(groupId string, name string) error {
- //TODO implement me
- panic("implement me")
-}
-
-func (b BotLagrange) RenameGroupMember(groupId string, userId string, nickname string) error {
- //TODO implement me
- panic("implement me")
-}
-
-func (b BotLagrange) RemarkGroup(groupId string, remark string) error {
- //TODO implement me
- panic("implement me")
-}
-
-func (b BotLagrange) SetGroupGlobalMute(groupId string, mute bool) error {
- //TODO implement me
- panic("implement me")
-}
-
-func (b BotLagrange) LeaveGroup(groupId string) error {
- //TODO implement me
- panic("implement me")
-}
-
-func (b BotLagrange) SetGroupMemberTitle(groupId string, userId string, title string) error {
- //TODO implement me
- panic("implement me")
-}
-
-func (b BotLagrange) UploadChannelFile(channelId string, filePath string) error {
- //TODO implement me
- panic("implement me")
-}
-
-func (b BotLagrange) UploadGroupFile(groupId string, filePath string, targetFilePath string) error {
- //TODO implement me
- panic("implement me")
-}
-
-func (b BotLagrange) GetGroupFileSystemInfo(groupId string) *resource.GroupFileSystemInfo {
- //TODO implement me
- panic("implement me")
-}
-
-func (b BotLagrange) GetGroupFilesByFolder(groupId string, folderId string) interface{} {
- //TODO implement me
- panic("implement me")
-}
-
-func (b BotLagrange) GetGroupRootFiles(groupId string) interface{} {
- //TODO implement me
- panic("implement me")
-}
-
-func (b BotLagrange) MoveGroupFile(groupId string, fileId string, parentFolder string, targetFolderId string) error {
- //TODO implement me
- panic("implement me")
-}
-
-func (b BotLagrange) DeleteGroupFile(groupId string, fileId string) error {
- //TODO implement me
- panic("implement me")
-}
-
-func (b BotLagrange) CreateGroupFolder(groupId string, folderName string, parentFolder string) error {
- //TODO implement me
- panic("implement me")
-}
-
-func (b BotLagrange) RenameGroupFolder(groupId string, folderId string, newFolderName string) error {
- //TODO implement me
- panic("implement me")
-}
-
-func (b BotLagrange) DeleteGroupFolder(groupId string, folderId string) error {
- //TODO implement me
- panic("implement me")
-}
-
-func (b BotLagrange) GetOriginalClient() interface{} {
- //TODO implement me
- panic("implement me")
-}
-
-func GetBot() *BotLagrange {
- return &BotLagrange{}
-}
diff --git a/adapter/lagrange/lagrange_config.go b/adapter/lagrange/lagrange_config.go
deleted file mode 100644
index a035aac..0000000
--- a/adapter/lagrange/lagrange_config.go
+++ /dev/null
@@ -1,21 +0,0 @@
-package lagrange
-
-import "github.com/Iceinu-Project/iceinu/config"
-
-type AdapterLagrangeConfig struct {
- Account int `toml:"account"`
- Password string `toml:"password"`
- SignUrl string `toml:"sign_url"`
- MusicSignUrl string `toml:"music_sign_url"`
-}
-
-var manager = config.GetManager()
-
-func RegisterConfig() {
- manager.InitConfig("lagrange.toml", &AdapterLagrangeConfig{
- Account: 0,
- Password: "",
- SignUrl: "https://sign.lagrangecore.org/api/sign/25765",
- MusicSignUrl: "",
- })
-}
diff --git a/adapter/lagrange/lagrange_handler.go b/adapter/lagrange/lagrange_handler.go
deleted file mode 100644
index 17e7ab2..0000000
--- a/adapter/lagrange/lagrange_handler.go
+++ /dev/null
@@ -1,97 +0,0 @@
-package lagrange
-
-import (
- "fmt"
- "strconv"
- "time"
-
- "github.com/LagrangeDev/LagrangeGo/client"
- "github.com/LagrangeDev/LagrangeGo/message"
-
- "github.com/Iceinu-Project/iceinu/ice"
- "github.com/Iceinu-Project/iceinu/logger"
- "github.com/Iceinu-Project/iceinu/resource"
- "github.com/Iceinu-Project/iceinu/utils"
-)
-
-func SetAllHandler() {
- Manager.RegisterPrivateMessageHandler(func(client *client.QQClient, event *message.PrivateMessage) {
- self, _ := client.FetchUserInfoUin(client.Uin)
- e := ice.PlatformEvent{
- EventId: uint64(event.Id),
- EventType: "PrivateMessageEvent",
- Platform: "QQNT",
- SelfId: strconv.Itoa(int(client.Uin)),
- Timestamp: time.Unix(int64(event.Time), 0),
- Group: &resource.Group{
- Id: "",
- Name: "",
- Avatar: "",
- },
- Channel: &resource.Channel{
- Id: strconv.Itoa(int(event.Sender.Uin)),
- Type: 1,
- Name: event.Sender.Uid,
- ParentId: "",
- },
- Message: &resource.Message{
- Id: strconv.Itoa(int(event.InternalId)),
- Content: event.ToString(),
- MessageElements: ConvertIceElement(event.Elements),
- },
- Operator: &resource.User{
- Id: strconv.Itoa(int(event.Sender.Uin)),
- Name: event.Sender.Uid,
- Nickname: event.Sender.Nickname,
- Avatar: self.Avatar,
- IsBot: false,
- },
- User: &resource.User{
- Id: strconv.Itoa(int(event.Target)),
- Name: client.GetUid(client.Uin),
- Nickname: client.NickName(),
- Avatar: self.Avatar,
- IsBot: false,
- },
- }
- logger.Infof("[私聊][%s]%s:%s", e.Operator.Id, e.Operator.Nickname, utils.SatorizeIceElements(e.Message.MessageElements))
- ice.Bus.Publish("PrivateMessageEvent", &e)
- })
- Manager.RegisterGroupMessageHandler(func(client *client.QQClient, event *message.GroupMessage) {
- groupinfo := client.GetCachedGroupInfo(event.GroupUin)
- fmt.Println(groupinfo)
- self, _ := client.FetchUserInfoUin(client.Uin)
- e := ice.PlatformEvent{
- EventId: uint64(event.Id),
- EventType: "GroupMessageEvent",
- Platform: "QQNT",
- SelfId: strconv.Itoa(int(client.Uin)),
- Timestamp: time.Unix(int64(event.Time), 0),
- Channel: &resource.Channel{
- Id: strconv.Itoa(int(event.GroupUin)),
- Type: 0,
- Name: event.GroupName,
- ParentId: "",
- },
- Group: &resource.Group{
- Id: strconv.Itoa(int(event.GroupUin)),
- Name: event.GroupName,
- Avatar: groupinfo.Avatar,
- },
- Message: &resource.Message{
- Id: strconv.Itoa(int(event.InternalId)),
- Content: event.ToString(),
- MessageElements: ConvertIceElement(event.Elements),
- },
- Operator: &resource.User{
- Id: strconv.Itoa(int(event.Sender.Uin)),
- Name: event.Sender.Uid,
- Nickname: event.Sender.Nickname,
- Avatar: self.Avatar,
- IsBot: false,
- },
- }
- logger.Infof("[群聊][来自群%s][%s]%s:%s", e.Group.Id, e.Operator.Id, e.Operator.Nickname, utils.SatorizeIceElements(e.Message.MessageElements))
- ice.Bus.Publish("GroupMessageEvent", &e)
- })
-}
diff --git a/adapter/lagrange/lagrange_subscriber.go b/adapter/lagrange/lagrange_subscriber.go
deleted file mode 100644
index a922704..0000000
--- a/adapter/lagrange/lagrange_subscriber.go
+++ /dev/null
@@ -1,312 +0,0 @@
-package lagrange
-
-import (
- "github.com/LagrangeDev/LagrangeGo/client"
- "github.com/LagrangeDev/LagrangeGo/client/event"
- "github.com/LagrangeDev/LagrangeGo/message"
-)
-
-// 定义各个消息类型的处理函数
-
-type PrivateMessageHandler func(client *client.QQClient, message *message.PrivateMessage)
-type GroupMessageHandler func(client *client.QQClient, message *message.GroupMessage)
-type TempMessageHandler func(client *client.QQClient, message *message.TempMessage)
-
-type SelfPrivateMessageHandler func(client *client.QQClient, message *message.PrivateMessage)
-type SelfGroupMessageHandler func(client *client.QQClient, message *message.GroupMessage)
-type SelfTempMessageHandler func(client *client.QQClient, message *message.TempMessage)
-
-// 定义各个事件类型的处理函数
-
-type GroupJoinEventHandler func(client *client.QQClient, event *event.GroupMemberIncrease) // bot进群
-type GroupLeaveEventHandler func(client *client.QQClient, event *event.GroupMemberDecrease) // bot退群
-
-type GroupInvitedEventHandler func(client *client.QQClient, event *event.GroupInvite) // bot被邀请进群
-type GroupMemberJoinRequestEventHandler func(client *client.QQClient, event *event.GroupMemberJoinRequest) // 加群邀请
-type GroupMemberJoinEventHandler func(client *client.QQClient, event *event.GroupMemberIncrease) // 成员加群
-type GroupMemberLeaveEventHandler func(client *client.QQClient, event *event.GroupMemberDecrease) // 成员退群
-type GroupMuteEventHandler func(client *client.QQClient, event *event.GroupMute) // 禁言事件
-type GroupRecallEventHandler func(client *client.QQClient, event *event.GroupRecall)
-type GroupMemberPermissionChangedEventHandler func(client *client.QQClient, event *event.GroupMemberPermissionChanged)
-type GroupNameUpdatedEventHandler func(client *client.QQClient, event *event.GroupNameUpdated) // 群更名
-type MemberSpecialTitleUpdatedEventHandler func(client *client.QQClient, event *event.MemberSpecialTitleUpdated) // 群成员特殊头衔更新
-type NewFriendRequestHandler func(client *client.QQClient, event *event.NewFriendRequest) // 新朋友请求
-type FriendRecallEventHandler func(client *client.QQClient, event *event.FriendRecall) // 好友消息撤回
-type RenameEventHandler func(client *client.QQClient, event *event.Rename) // 好友昵称更改
-type FriendNotifyEventHandler func(client *client.QQClient, event event.INotifyEvent) // 好友通知
-type GroupNotifyEventHandler func(client *client.QQClient, event event.INotifyEvent) // 群通知
-
-// SubscribeManager 订阅管理器实现,用于向LagrangeGo注册各类消息/事件处理函数
-type SubscribeManager struct {
- privateMessageHandlers []PrivateMessageHandler
- groupMessageHandlers []GroupMessageHandler
- tempMessageHandlers []TempMessageHandler
- selfPrivateMessageHandlers []SelfPrivateMessageHandler
- selfGroupMessageHandlers []SelfGroupMessageHandler
- selfTempMessageHandlers []SelfTempMessageHandler
- groupJoinEventHandlers []GroupJoinEventHandler
- groupLeaveEventHandlers []GroupLeaveEventHandler
- groupInviteEventHandlers []GroupInvitedEventHandler
- groupMemberJoinRequestEventHandlers []GroupMemberJoinRequestEventHandler
- groupMemberJoinEventHandlers []GroupMemberJoinEventHandler
- groupMemberLeaveEventHandlers []GroupMemberLeaveEventHandler
- groupMuteEventHandlers []GroupMuteEventHandler
- groupRecallEventHandlers []GroupRecallEventHandler
- groupMemberPermissionChangedEventHandlers []GroupMemberPermissionChangedEventHandler
- groupNameUpdatedEventHandlers []GroupNameUpdatedEventHandler
- memberSpecialTitleUpdatedEventHandlers []MemberSpecialTitleUpdatedEventHandler
- newFriendRequestHandlers []NewFriendRequestHandler
- friendRecallEventHandlers []FriendRecallEventHandler
- renameEventHandlers []RenameEventHandler
- friendNotifyEventHandlers []FriendNotifyEventHandler
- groupNotifyEventHandlers []GroupNotifyEventHandler
-}
-
-// Manager 全局 SubscribeManager 实例
-var Manager = &SubscribeManager{}
-
-// RegisterPrivateMessageHandler 注册私聊消息处理函数
-func (sm *SubscribeManager) RegisterPrivateMessageHandler(handler PrivateMessageHandler) {
- sm.privateMessageHandlers = append(sm.privateMessageHandlers, handler)
-}
-
-// RegisterGroupMessageHandler 注册群消息处理函数
-func (sm *SubscribeManager) RegisterGroupMessageHandler(handler GroupMessageHandler) {
- sm.groupMessageHandlers = append(sm.groupMessageHandlers, handler)
-}
-
-// RegisterTempMessageHandler 注册临时消息处理函数
-func (sm *SubscribeManager) RegisterTempMessageHandler(handler TempMessageHandler) {
- sm.tempMessageHandlers = append(sm.tempMessageHandlers, handler)
-}
-
-// RegisterSelfPrivateMessageHandler 注册自己的私聊消息处理函数
-func (sm *SubscribeManager) RegisterSelfPrivateMessageHandler(handler SelfPrivateMessageHandler) {
- sm.selfPrivateMessageHandlers = append(sm.selfPrivateMessageHandlers, handler)
-}
-
-// RegisterSelfGroupMessageHandler 注册自己的群消息处理函数
-func (sm *SubscribeManager) RegisterSelfGroupMessageHandler(handler SelfGroupMessageHandler) {
- sm.selfGroupMessageHandlers = append(sm.selfGroupMessageHandlers, handler)
-}
-
-// RegisterSelfTempMessageHandler 注册自己的临时消息处理函数
-func (sm *SubscribeManager) RegisterSelfTempMessageHandler(handler SelfTempMessageHandler) {
- sm.selfTempMessageHandlers = append(sm.selfTempMessageHandlers, handler)
-}
-
-// RegisterGroupJoinEventHandler 注册群成员加入事件处理函数
-func (sm *SubscribeManager) RegisterGroupJoinEventHandler(handler GroupJoinEventHandler) {
- sm.groupJoinEventHandlers = append(sm.groupJoinEventHandlers, handler)
-}
-
-// RegisterGroupLeaveEventHandler 注册群成员离开事件处理函数
-func (sm *SubscribeManager) RegisterGroupLeaveEventHandler(handler GroupLeaveEventHandler) {
- sm.groupLeaveEventHandlers = append(sm.groupLeaveEventHandlers, handler)
-}
-
-// RegisterGroupInviteEventHandler 注册群邀请事件处理函数
-func (sm *SubscribeManager) RegisterGroupInviteEventHandler(handler GroupInvitedEventHandler) {
- sm.groupInviteEventHandlers = append(sm.groupInviteEventHandlers, handler)
-
-}
-
-// RegisterGroupMemberJoinRequestEventHandler 注册群成员加群邀请事件处理函数
-func (sm *SubscribeManager) RegisterGroupMemberJoinRequestEventHandler(handler GroupMemberJoinRequestEventHandler) {
- sm.groupMemberJoinRequestEventHandlers = append(sm.groupMemberJoinRequestEventHandlers, handler)
-}
-
-// RegisterGroupMemberJoinEventHandler 注册群成员加入事件处理函数
-func (sm *SubscribeManager) RegisterGroupMemberJoinEventHandler(handler GroupMemberJoinEventHandler) {
- sm.groupMemberJoinEventHandlers = append(sm.groupMemberJoinEventHandlers, handler)
-}
-
-// RegisterGroupMemberLeaveEventHandler 注册群成员离开事件处理函数
-func (sm *SubscribeManager) RegisterGroupMemberLeaveEventHandler(handler GroupMemberLeaveEventHandler) {
- sm.groupMemberLeaveEventHandlers = append(sm.groupMemberLeaveEventHandlers, handler)
-}
-
-// RegisterGroupMuteEventHandler 注册群成员禁言事件处理函数
-func (sm *SubscribeManager) RegisterGroupMuteEventHandler(handler GroupMuteEventHandler) {
- sm.groupMuteEventHandlers = append(sm.groupMuteEventHandlers, handler)
-}
-
-// RegisterGroupRecallEventHandler 注册群消息撤回事件处理函数
-func (sm *SubscribeManager) RegisterGroupRecallEventHandler(handler GroupRecallEventHandler) {
- sm.groupRecallEventHandlers = append(sm.groupRecallEventHandlers, handler)
-}
-
-// RegisterGroupMemberPermissionChangedEventHandler 注册群成员权限变更事件处理函数
-func (sm *SubscribeManager) RegisterGroupMemberPermissionChangedEventHandler(handler GroupMemberPermissionChangedEventHandler) {
- sm.groupMemberPermissionChangedEventHandlers = append(sm.groupMemberPermissionChangedEventHandlers, handler)
-}
-
-// RegisterGroupNameUpdatedEventHandler 注册群名称变更事件处理函数
-func (sm *SubscribeManager) RegisterGroupNameUpdatedEventHandler(handler GroupNameUpdatedEventHandler) {
- sm.groupNameUpdatedEventHandlers = append(sm.groupNameUpdatedEventHandlers, handler)
-}
-
-// RegisterMemberSpecialTitleUpdatedEventHandler 注册群成员特殊头衔变更事件处理函数
-func (sm *SubscribeManager) RegisterMemberSpecialTitleUpdatedEventHandler(handler MemberSpecialTitleUpdatedEventHandler) {
- sm.memberSpecialTitleUpdatedEventHandlers = append(sm.memberSpecialTitleUpdatedEventHandlers, handler)
-}
-
-// RegisterNewFriendRequestHandler 注册新朋友请求处理函数
-func (sm *SubscribeManager) RegisterNewFriendRequestHandler(handler NewFriendRequestHandler) {
- sm.newFriendRequestHandlers = append(sm.newFriendRequestHandlers, handler)
-}
-
-// RegisterFriendRecallEventHandler 注册好友消息撤回事件处理函数
-func (sm *SubscribeManager) RegisterFriendRecallEventHandler(handler FriendRecallEventHandler) {
- sm.friendRecallEventHandlers = append(sm.friendRecallEventHandlers, handler)
-}
-
-// RegisterRenameEventHandler 注册好友昵称更改事件处理函数
-func (sm *SubscribeManager) RegisterRenameEventHandler(handler RenameEventHandler) {
- sm.renameEventHandlers = append(sm.renameEventHandlers, handler)
-}
-
-// RegisterFriendNotifyEventHandler 注册好友通知事件处理函数
-func (sm *SubscribeManager) RegisterFriendNotifyEventHandler(handler FriendNotifyEventHandler) {
- sm.friendNotifyEventHandlers = append(sm.friendNotifyEventHandlers, handler)
-}
-
-// RegisterGroupNotifyEventHandler 注册群通知事件处理函数
-func (sm *SubscribeManager) RegisterGroupNotifyEventHandler(handler GroupNotifyEventHandler) {
- sm.groupNotifyEventHandlers = append(sm.groupNotifyEventHandlers, handler)
-}
-
-// SetAllSubscribes 设置所有订阅处理
-func SetAllSubscribes() {
- LgrClient.QQClient.PrivateMessageEvent.Subscribe(func(client *client.QQClient, event *message.PrivateMessage) {
- for _, handler := range Manager.privateMessageHandlers {
- handler(client, event)
- }
- })
-
- LgrClient.QQClient.GroupMessageEvent.Subscribe(func(client *client.QQClient, event *message.GroupMessage) {
- for _, handler := range Manager.groupMessageHandlers {
- handler(client, event)
- }
- })
-
- LgrClient.QQClient.TempMessageEvent.Subscribe(func(client *client.QQClient, event *message.TempMessage) {
- for _, handler := range Manager.tempMessageHandlers {
- handler(client, event)
- }
- })
-
- LgrClient.QQClient.SelfPrivateMessageEvent.Subscribe(func(client *client.QQClient, event *message.PrivateMessage) {
- for _, handler := range Manager.selfPrivateMessageHandlers {
- handler(client, event)
- }
- })
-
- LgrClient.QQClient.SelfGroupMessageEvent.Subscribe(func(client *client.QQClient, event *message.GroupMessage) {
- for _, handler := range Manager.selfGroupMessageHandlers {
- handler(client, event)
- }
- })
-
- LgrClient.QQClient.SelfTempMessageEvent.Subscribe(func(client *client.QQClient, event *message.TempMessage) {
- for _, handler := range Manager.selfTempMessageHandlers {
- handler(client, event)
- }
- })
-
- LgrClient.QQClient.GroupJoinEvent.Subscribe(func(client *client.QQClient, event *event.GroupMemberIncrease) {
- for _, handler := range Manager.groupJoinEventHandlers {
- handler(client, event)
- }
- })
-
- LgrClient.QQClient.GroupLeaveEvent.Subscribe(func(client *client.QQClient, event *event.GroupMemberDecrease) {
- for _, handler := range Manager.groupLeaveEventHandlers {
- handler(client, event)
- }
- })
-
- LgrClient.QQClient.GroupInvitedEvent.Subscribe(func(client *client.QQClient, event *event.GroupInvite) {
- for _, handler := range Manager.groupInviteEventHandlers {
- handler(client, event)
- }
- })
-
- LgrClient.QQClient.GroupMemberJoinRequestEvent.Subscribe(func(client *client.QQClient, event *event.GroupMemberJoinRequest) {
- for _, handler := range Manager.groupMemberJoinRequestEventHandlers {
- handler(client, event)
- }
- })
-
- LgrClient.QQClient.GroupMemberJoinEvent.Subscribe(func(client *client.QQClient, event *event.GroupMemberIncrease) {
- for _, handler := range Manager.groupMemberJoinEventHandlers {
- handler(client, event)
- }
- })
-
- LgrClient.QQClient.GroupMemberLeaveEvent.Subscribe(func(client *client.QQClient, event *event.GroupMemberDecrease) {
- for _, handler := range Manager.groupMemberLeaveEventHandlers {
- handler(client, event)
- }
- })
-
- LgrClient.QQClient.GroupMuteEvent.Subscribe(func(client *client.QQClient, event *event.GroupMute) {
- for _, handler := range Manager.groupMuteEventHandlers {
- handler(client, event)
- }
- })
-
- LgrClient.QQClient.GroupRecallEvent.Subscribe(func(client *client.QQClient, event *event.GroupRecall) {
- for _, handler := range Manager.groupRecallEventHandlers {
- handler(client, event)
- }
- })
-
- LgrClient.QQClient.GroupMemberPermissionChangedEvent.Subscribe(func(client *client.QQClient, event *event.GroupMemberPermissionChanged) {
- for _, handler := range Manager.groupMemberPermissionChangedEventHandlers {
- handler(client, event)
- }
- })
-
- LgrClient.QQClient.GroupNameUpdatedEvent.Subscribe(func(client *client.QQClient, event *event.GroupNameUpdated) {
- for _, handler := range Manager.groupNameUpdatedEventHandlers {
- handler(client, event)
- }
- })
-
- LgrClient.QQClient.MemberSpecialTitleUpdatedEvent.Subscribe(func(client *client.QQClient, event *event.MemberSpecialTitleUpdated) {
- for _, handler := range Manager.memberSpecialTitleUpdatedEventHandlers {
- handler(client, event)
- }
- })
-
- LgrClient.QQClient.NewFriendRequestEvent.Subscribe(func(client *client.QQClient, event *event.NewFriendRequest) {
- for _, handler := range Manager.newFriendRequestHandlers {
- handler(client, event)
- }
- })
-
- LgrClient.QQClient.FriendRecallEvent.Subscribe(func(client *client.QQClient, event *event.FriendRecall) {
- for _, handler := range Manager.friendRecallEventHandlers {
- handler(client, event)
- }
- })
-
- LgrClient.QQClient.RenameEvent.Subscribe(func(client *client.QQClient, event *event.Rename) {
- for _, handler := range Manager.renameEventHandlers {
- handler(client, event)
- }
- })
-
- LgrClient.QQClient.FriendNotifyEvent.Subscribe(func(client *client.QQClient, event event.INotifyEvent) {
- for _, handler := range Manager.friendNotifyEventHandlers {
- handler(client, event)
- }
- })
-
- LgrClient.QQClient.GroupNotifyEvent.Subscribe(func(client *client.QQClient, event event.INotifyEvent) {
- for _, handler := range Manager.groupNotifyEventHandlers {
- handler(client, event)
- }
- })
-}
diff --git a/config/config_manager.go b/config/config_manager.go
deleted file mode 100644
index a2fae5e..0000000
--- a/config/config_manager.go
+++ /dev/null
@@ -1,233 +0,0 @@
-package config
-
-import (
- "github.com/Iceinu-Project/iceinu/logger"
- "github.com/pelletier/go-toml"
- "os"
- "path/filepath"
- "reflect"
- "sync"
-)
-
-// Iceinu的配置文件处理器,除了iceinu自带的配置文件之外也负责处理适配器和插件的配置文件设置
-// 这配置文件管理器设计一大半是ChatGPT帮我优化的,o1模型真的是太斯巴拉西了
-// 总之是实现了动态的配置文件注册加载解析修正等等功能
-
-// ConfManager 管理配置文件的结构体
-type ConfManager struct {
- configs map[string]interface{}
- defaults map[string]interface{}
- mutex sync.RWMutex
- configChanged bool // 标志位,指示配置文件是否有生成或修改
-}
-
-// manager 是全局的配置管理器实例
-var manager = NewManager()
-
-// NewManager 创建一个新的配置管理器
-func NewManager() *ConfManager {
- logger.Debugf("正在初始化配置文件管理器...")
- return &ConfManager{
- configs: make(map[string]interface{}),
- defaults: make(map[string]interface{}),
- }
-}
-
-// InitConfig 注册一个配置文件和对应的结构体
-func (cm *ConfManager) InitConfig(filename string, configStruct interface{}) {
- cm.mutex.Lock()
- defer cm.mutex.Unlock()
- cm.configs[filename] = configStruct
-
- // 保存默认配置的深拷贝
- defaultConfig := deepCopy(configStruct)
- cm.defaults[filename] = defaultConfig
-
- logger.Debugf("已注册配置文件:%s", filename)
-}
-
-// LoadConfigs 加载并解析所有已注册的配置文件,如果不存在则自动生成
-// 如果已有的配置文件缺少字段,则补全并写回,并在覆盖前备份原始文件
-func (cm *ConfManager) LoadConfigs() error {
- cm.mutex.Lock()
- defer cm.mutex.Unlock()
-
- // 获取当前工作目录
- dir, err := os.Getwd()
- if err != nil {
- logger.Errorf("获取工作目录失败:%v", err)
- return err
- }
-
- // 遍历已注册的配置文件
- for filename, configStruct := range cm.configs {
- fullPath := filepath.Join(dir, filename)
-
- // 检查文件是否存在
- if _, err := os.Stat(fullPath); os.IsNotExist(err) {
- // 如果文件不存在,生成默认配置文件
- data, err := toml.Marshal(configStruct)
- if err != nil {
- logger.Errorf("序列化默认配置失败:%v", err)
- return err
- }
-
- err = os.WriteFile(fullPath, data, 0644)
- if err != nil {
- logger.Errorf("写入配置文件失败:%v", err)
- return err
- }
-
- logger.Infof("已生成配置文件:%s", fullPath)
- cm.configChanged = true // 设置标志位
- } else {
- // 如果文件存在,读取并解析配置文件
- data, err := os.ReadFile(fullPath)
- if err != nil {
- logger.Errorf("读取配置文件失败:%v", err)
- return err
- }
-
- // 创建一个新的配置实例,用于加载文件内容
- newConfig := deepCopy(cm.defaults[filename])
-
- err = toml.Unmarshal(data, newConfig)
- if err != nil {
- logger.Errorf("解析配置文件失败:%v", err)
- return err
- }
-
- // 比较新配置和默认配置,补全缺失的字段
- changed := mergeConfig(newConfig, cm.defaults[filename])
-
- // 将补全后的配置赋值回 configs
- cm.configs[filename] = newConfig
-
- if changed {
- // 在覆盖前备份原始配置文件
- backupPath := fullPath + ".backup.toml"
- err = backupFile(fullPath, backupPath)
- if err != nil {
- logger.Errorf("备份配置文件失败:%v", err)
- return err
- }
-
- // 将完整的配置(包含默认值和新解析的值)写回配置文件
- data, err = toml.Marshal(newConfig)
- if err != nil {
- logger.Errorf("序列化配置文件失败:%v", err)
- return err
- }
-
- err = os.WriteFile(fullPath, data, 0644)
- if err != nil {
- logger.Errorf("写入配置文件失败:%v", err)
- return err
- }
-
- logger.Infof("配置文件已更新并备份:%s", fullPath)
- cm.configChanged = true // 设置标志位
- } else {
- logger.Debugf("配置文件已加载,无需更新:%s", fullPath)
- }
- }
- }
-
- // 如果配置文件有变动,提示用户并退出程序
- if cm.configChanged {
- logger.Warnf("配置文件已生成或更新,请检查配置文件后重新启动程序。")
- os.Exit(0)
- }
-
- return nil
-}
-
-// Get 获取指定名称的配置
-func (cm *ConfManager) Get(filename string) interface{} {
- cm.mutex.RLock()
- defer cm.mutex.RUnlock()
- return cm.configs[filename]
-}
-
-// GetManager 获取配置管理器实例
-func GetManager() *ConfManager {
- return manager
-}
-
-// deepCopy 深拷贝一个配置结构体
-func deepCopy(src interface{}) interface{} {
- // 将 src 序列化为 TOML
- data, err := toml.Marshal(src)
- if err != nil {
- logger.Errorf("深拷贝失败:%v", err)
- return nil
- }
-
- // 创建一个新的与 src 类型相同的实例
- dst := reflect.New(reflect.TypeOf(src).Elem()).Interface()
-
- // 将 TOML 反序列化到 dst
- err = toml.Unmarshal(data, dst)
- if err != nil {
- logger.Errorf("深拷贝失败:%v", err)
- return nil
- }
-
- return dst
-}
-
-// mergeConfig 递归地将 src 中的非零值合并到 dst 中,返回是否有修改
-func mergeConfig(dst, src interface{}) bool {
- dstVal := reflect.ValueOf(dst).Elem()
- srcVal := reflect.ValueOf(src).Elem()
-
- changed := false
-
- for i := 0; i < dstVal.NumField(); i++ {
- dstField := dstVal.Field(i)
- srcField := srcVal.Field(i)
-
- switch dstField.Kind() {
- case reflect.Struct:
- if mergeConfig(dstField.Addr().Interface(), srcField.Addr().Interface()) {
- changed = true
- }
- case reflect.Slice, reflect.Map:
- if dstField.IsNil() && !srcField.IsNil() {
- dstField.Set(srcField)
- changed = true
- }
- default:
- // 如果 dstField 是零值,则使用 srcField 的值
- if isZeroValue(dstField) && !isZeroValue(srcField) {
- dstField.Set(srcField)
- changed = true
- }
- }
- }
-
- return changed
-}
-
-// isZeroValue 判断一个值是否是零值
-func isZeroValue(v reflect.Value) bool {
- return reflect.DeepEqual(v.Interface(), reflect.Zero(v.Type()).Interface())
-}
-
-// backupFile 备份原始配置文件
-func backupFile(originalPath, backupPath string) error {
- // 读取原始文件内容
- data, err := os.ReadFile(originalPath)
- if err != nil {
- return err
- }
-
- // 写入备份文件
- err = os.WriteFile(backupPath, data, 0644)
- if err != nil {
- return err
- }
-
- logger.Infof("已备份配置文件:%s", backupPath)
- return nil
-}
diff --git a/config/iceinu_config.go b/config/iceinu_config.go
deleted file mode 100644
index 9825991..0000000
--- a/config/iceinu_config.go
+++ /dev/null
@@ -1,22 +0,0 @@
-package config
-
-type IceinuConfig struct {
- BotName string `toml:"bot_name"`
- LogLevel string `toml:"log_level"`
- Database DatabaseConfig `toml:"database"`
-}
-
-type DatabaseConfig struct {
- URL string `toml:"url"`
-}
-
-func init() {
- // 注册Iceinu的配置文件
- manager.InitConfig("iceinu.toml", &IceinuConfig{
- BotName: "Iceinu",
- LogLevel: "INFO",
- Database: DatabaseConfig{
- URL: "sqlite://iceinu.db",
- },
- })
-}
diff --git a/elements/elements.go b/elements/elements.go
deleted file mode 100644
index 4618a76..0000000
--- a/elements/elements.go
+++ /dev/null
@@ -1,650 +0,0 @@
-package elements
-
-import (
- "encoding/base64"
- "fmt"
- "io"
- "strings"
- "time"
-)
-
-var DefaultThumb, _ = base64.StdEncoding.DecodeString("/9j/4AAQSkZJRgABAQAAAQABAAD//gAXR2VuZXJhdGVkIGJ5IFNuaXBhc3Rl/9sAhAAKBwcIBwYKCAgICwoKCw4YEA4NDQ4dFRYRGCMfJSQiHyIhJis3LyYpNCkhIjBBMTQ5Oz4+PiUuRElDPEg3PT47AQoLCw4NDhwQEBw7KCIoOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozv/wAARCAF/APADAREAAhEBAxEB/8QBogAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoLEAACAQMDAgQDBQUEBAAAAX0BAgMABBEFEiExQQYTUWEHInEUMoGRoQgjQrHBFVLR8CQzYnKCCQoWFxgZGiUmJygpKjQ1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4eLj5OXm5+jp6vHy8/T19vf4+foBAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKCxEAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDiAayNxwagBwNAC5oAM0xBmgBM0ANJoAjY0AQsaBkTGgCM0DEpAFAC0AFMBaACgAoEJTASgQlACUwCgQ4UAOFADhQA4UAOFADxQIkBqDQUGgBwagBQaBC5pgGaAELUAMLUARs1AETGgBhNAxhoASkAUALQIKYxaBBQAUwEoAQ0CEoASmAUAOoEKKAHCgBwoAeKAHigQ7NZmoZpgLmgBd1Ahd1ABupgNLUAMLUAMY0AMJoAYaAENACUCCgAoAWgAoAWgBKYCUAJQISgApgLQAooEOFACigB4oAeKBDxQAVmaiZpgGaAFzQAbqAE3UAIWpgNJoAYTQIaaAEoAQ0CEoASgBaACgBaACmAUAJQAlAgoAKYC0AKKBCigB4FADgKBDwKAHigBuazNRM0DEzTAM0AJmgAzQAhNAhpNACGmA2gQlACUCEoAKACgBaAFpgFACUAJQAUCCmAUALQIcBQA4CgB4FADgKBDhQA4UAMzWZqNzTGJQAZoATNABmgBKAEoEIaYCUCEoASgQlABQAtABQAtMBKACgAoEFABimAYoEKBQA4CgB4FADwKBDgKAFFADhQBCazNhKAEpgFACUAFACUAFAhDTAbQISgAoEJQAUALQAtMAoAKADFABigQYoAMUALimIUCgBwFAh4FADgKAHUALQAtAENZmwlACUwEoAKAEoAKACgQlMBpoEJQAUCCgBcUAFABTAXFAC4oAMUAGKBBigAxQIKYCigQ8UAOFADhQAtAC0ALQBDWZqJQMSgBKYBQAlABQISgBKYCGgQlAC0CCgBcUAFABTAUCkA7FMAxQAYoEJQAUCCmAooEOFADxQA4UAFAC0ALQBDWZqJQAlACUxhQAlABQIKAEoASmISgBcUCCgBaACgBcUAKBQAuKYC0CEoAQ0AJQISmAooEPFADhQA4UALQAtAC0AQ1maiUAFACUAJTAKAEoAKAEoAMUxBigAxQIWgAoAKAFAoAWgBaYBQIQ0ANNACUCCmIUUAOFADxQA4UALQAtABQBFWZqFACUAFACYpgFACUAFACUAFAgxTEFABQAUALQAooAWgAoAKYDTQIaaAEpiCgQ4UAOFAh4oGOFAC0ALSAKYEdZmglABQAUDDFACUwEoASgAoAKBBQIKYBQAUALQAtAC0AJQAhpgNJoENJoATNMQCgQ8UCHigB4oAWgYtABQAUAMrM0CgAoAKADFACUxiUAJQAlAgoAKYgoAKACgYtAC0AFAhDTAQmgBhNAhpNACZpiFBoEPFAEi0CHigB1ABQAUDEoAbWZoFABQAtABTAQ0ANNAxDQAlAhaAEpiCgAoGFAC0AFABmgBCaYhpNADCaBDSaBBmgABpiJFNAEimgB4NADqAFzQAlACE0AJWZoFAC0AFAC0wEIoAaaAG0AJQAUCCgApjCgAoAKADNABmgBpNMQ0mgBpNAhhNAgzQAoNADwaAHqaAJAaBDgaYC5oATNACZoAWszQKACgBaBDqYCGgBpoAYaBiUCCgBKYBQMKACgAoAM0AITQIaTQA0mmA0mgQ3NAhKAHCgBwNADwaAHg0AOBpiFzQAZoATNAD6zNAoAKAFoEOpgBoAaaAGGmAw0AJmgAzQMM0AGaADNABmgBM0AITQIaTQAhNMQw0AJQIKAFFADhQA4GgBwNADs0xC5oAM0CDNAEtZmoUCCgBaAHUwCgBppgRtQAw0ANzQAZoAM0AGaADNABmgBKAEoAQ0ANNMQhoEJQAlMBaQDgaAFBoAcDTAdmgQuaADNAgzQBPWZqFAgoAWgBaYC0CGmmBG1AyM0ANJoATNACZoAXNABmgAzQAUAJQAhoAQ0xDTQISmAUALQAUgHA0AKDTAdmgQuaBBQAtAFiszQKACgBaAFFMAoEIaYEbUDI2oAYaAEoASgAzQAuaACgAoAKAENMQ00AJTEFAhKACgAoAXNACg0AOBoAWgQtAC0AWazNAoAKACgBaYBQIQ0AMNMYw0AMIoAbQAlMAoAKACgAzSAKYhKAENACUxBQIKACgBKACgBaAHCgQ4UALQAUAWqzNAoAKACgApgFACGgQ00xjTQAwigBCKAG4pgJQAlABQAUCCgBKACgBKYgoEFABQISgAoAWgBRQA4UALQAUCLdZmoUAFABQAlMAoASgBDQA00wENACYoATFMBpFADSKAEoEJQAUAFABQAlMQtAgoASgQUAJQAUAKKAHCgBaBBQBbrM1CgAoAKACmAUAJQAlADaYBQAlACYpgIRQA0igBpFAhtABQAUAFMAoEFABQIKAEoASgQUALQAooAWgQUAW81mbC0CCgApgFACUAIaAEpgJQAUAFABQAhFMBpFADSKAGkUCExQAYoAMUAGKADFMQYoAMUCExSATFABQIKYBQAtABQIt5qDYM0ALmgQtIApgIaAENADaACmAlAC0ALQAUwGkUANIoAaRQAmKBBigAxQAYoAMUAGKBBigBMUAJigQmKAExTAKBC0AFAFnNQaig0AKDQAtAgoASgBDQAlMBKACgAFADhQAtMBCKAGkUAIRQAmKADFABigQmKADFACYoAXFABigQmKAExQAmKBCYpgJigAoAnzUGgZoAcDQAuaBC0AJQAhoASmAlABQAtADhQAtMAoATFACEUAJigAxQAYoATFAhMUAFABQAuKADFABigBpWgBCKBCYpgJigB+ag0DNADgaBDgaAFzQITNACUAJTAKACgBRQAopgOoAWgBKAEoAKACgAoASgBpoEJQAooAWgBaBhigBMUCEIoAQigBMUAJSLCgBQaBDgaQC5oEFACUwCgBKACmAtADhQA4UALQAUAJQAUAJQAUAJQAhoENoAWgBRQAooGLQAUAGKAGkUAIRQIZSKEoGKKBDhQAUCCgAoAKBBQAUwFoGKKAHCgBaACgAoASgAoASgBCaAEoEJmgAoAUGgBQaAHZoGFABQAUANoAjpDEoAWgBaAFoEFACUALQAUCCmAUAOFAxRQAtAC0AJQAUAJQAmaBDSaAEzQAmaYBmgBQaAHA0gFzQAuaBhmgAzQAlAEdIYUALQAtAgoAKAEoEFAC0AFMAoAUUDFFAC0ALQAUAJQAhoENNACE0wEoATNABmgBc0ALmgBc0gDNAC5oATNABmgBKRQlACigB1AgoASgQlABTAWgBKACgBaBi0ALQAZoAM0AFACGgQ00wENACUAJQAUCFzQMM0ALmgAzQAZoAM0AGaQC0igoAUUALQIWgBDQISmAUAFACUAFABQAuaBi5oAM0AGaBBmgBKAEpgIaAG0AJQAUCFoAM0DDNAC5oATNABmgAzQBJUlBQAooAWgQtACGmIaaACgAoASgBKACgBc0DCgQUAGaADNABTASgBDQAlACUAFAgoAKBhQAUAFABQAlAE1SUFAxRQIWgQtMBDQIQ0AJQAlAhKBiUAFABmgBc0AGaADNABTAKACgBKAEoASgQlABQAUAFAC0AFACUAFAE1SaBQAUCHCgQtMBKBCUAJQISgBDQA00DEzQAuaADNMBc0AGaADNABQAUAJQAlABQISgAoAKACgBaACgBKAEoAnqTQSgBRQIcKBC0xCUAJQISgBKAENADDQAmaYwzQAuaADNAC0AFABQAUAFAhKACgBKACgAoAWgAoELQAlAxKAJqk0EoAWgQooELTEFADaBCUABoENNMY00ANNAwzQAZoAXNAC0AFAC0CFoASgAoASgBKACgAoAWgQtABQAUANNAyWpNAoAKBCimIWgQUCEoASmIQ0ANNADTQMaaAEoGLmgAzQAtADhQIWgBaACgQhoASgYlACUALQIWgBaACgBKAENAyWpNBKYBQIcKBC0CEoEJTAKBCUANNADDQMQ0ANoGFAC5oAUGgBwNAhRQIWgBaAENACGgBtAwoAKAFzQIXNABmgAoAQ0DJKRoJQAtAhRQSLQIKYCUCCgBDQA00AMNAxpoGNoAM0AGaAFBoAcDQIcKBDqACgBDQAhoAQ0DEoAKADNAC5oEGaBhmgAoAkpGgUCCgQooELQIKYhKACgBKAGmgBpoGMNAxDQAlAwzQIUUAOFAhwoAcKBC0AJQAhoGNNACUAFABQAZoAXNABQAUAS0ixKACgQoNAhaYgoEFACUABoAaaAGmgYw0DENAxtABQAooEOFADhQIcKAFoASgBDQAhoGJQAUAFACUALQIKBi0CJDSLEoATNAhc0CHZpiCgQUAJQIKBjTQAhoGNNAxpoATFABigBQKAHCgBwoAWgAoAKACgBKAEoASgAoASgBaAAUAOoEONIoaTQAZoAUGmIUGgQtAgzQISgAoAQ0DGmgYlAxKACgAxQAtACigBRQAtAxaACgAoATFABigBCKAG0CEoAWgBRTAUUAf//Z")
-
-// IceinuMessageElement Iceinu的通用消息元素接口,参考了Satori的标准消息元素设计
-// https://satori.js.org/zh-CN/protocol/elements.html
-// 基于对不同平台的支持,Iceinu对标准消息元素进行扩展和调整来方便一些平台特殊设计的实现
-// 带*号的消息元素是Iceinu自定义的消息元素,方便实现一些针对平台设计的特殊功能
-type IceinuMessageElement interface {
- GetType() string // 获取消息元素类型
- ToSatori() string // 转换为Satori消息元素字符串
-}
-
-// TextElement 文本消息元素
-type TextElement struct {
- Text string
-}
-
-func (t *TextElement) GetType() string {
- return "text"
-}
-
-func (t *TextElement) ToSatori() string {
- return t.Text
-}
-
-// AtElement At提及消息元素
-type AtElement struct {
- Id string // 目标用户ID
- Name string // 目标用户名称
- Role string // 目标用户角色
- Type string // At请求类型,0为全体成员,1为指定成员
-}
-
-func (a *AtElement) GetType() string {
- return "at"
-}
-
-func (a *AtElement) ToSatori() string {
- var attributes []string
- if a.Id != "" {
- attributes = append(attributes, fmt.Sprintf("id=\"%s\"", a.Id))
- }
- if a.Name != "" {
- attributes = append(attributes, fmt.Sprintf("name=\"%s\"", a.Name))
- }
- if a.Role != "" {
- attributes = append(attributes, fmt.Sprintf("role=\"%s\"", a.Role))
- }
- if a.Type != "" {
- attributes = append(attributes, fmt.Sprintf("type=\"%s\"", a.Type))
- }
- return fmt.Sprintf("", strings.Join(attributes, " "))
-}
-
-// SharpElement Sharp提及频道消息元素
-type SharpElement struct {
- Id string // 目标频道ID
- Name string // 目标频道名称
-}
-
-func (s *SharpElement) GetType() string {
- return "sharp"
-}
-
-func (s *SharpElement) ToSatori() string {
- var attributes []string
- if s.Id != "" {
- attributes = append(attributes, fmt.Sprintf("id=\"%s\"", s.Id))
- }
- if s.Name != "" {
- attributes = append(attributes, fmt.Sprintf("name=\"%s\"", s.Name))
- }
- return fmt.Sprintf("", strings.Join(attributes, " "))
-}
-
-// LinkElement A超链接消息元素
-type LinkElement struct {
- Href string // 链接地址
-}
-
-func (a *LinkElement) GetType() string {
- return "link"
-}
-
-func (a *LinkElement) ToSatori() string {
- if a.Href != "" {
- return fmt.Sprintf("", a.Href)
- }
- return ""
-}
-
-// ImageElement 图片消息元素
-type ImageElement struct {
- // 用于接收图片
-
- ImageId string // 图片ID
- Src string // 图片源地址
- Title string // 图片标题
- Width uint32 // 图片宽度
- Height uint32 // 图片高度
-
- EffectId int // 图片特效ID
- IsFlash bool // 是否是闪图
-
- // 用于发送图片
- Summary string // 图片描述
- Path string // 图片路径或URL
- Stream io.ReadSeeker // 图片流
-}
-
-func (i *ImageElement) GetType() string {
- return "image"
-}
-
-func (i *ImageElement) ToSatori() string {
- var attributes []string
- if i.ImageId != "" {
- attributes = append(attributes, fmt.Sprintf("id=\"%s\"", i.ImageId))
- }
- if i.Src != "" {
- attributes = append(attributes, fmt.Sprintf("src=\"%s\"", i.Src))
- }
- if i.Title != "" {
- attributes = append(attributes, fmt.Sprintf("title=\"%s\"", i.Title))
- }
- if i.Width != 0 {
- attributes = append(attributes, fmt.Sprintf("width=\"%d\"", i.Width))
- }
- if i.Height != 0 {
- attributes = append(attributes, fmt.Sprintf("height=\"%d\"", i.Height))
- }
- if i.EffectId != 0 {
- attributes = append(attributes, fmt.Sprintf("effect=\"%d\"", i.EffectId))
- }
- if i.IsFlash {
- attributes = append(attributes, fmt.Sprintf("flash=\"%t\"", i.IsFlash))
- }
- if i.Summary != "" {
- attributes = append(attributes, fmt.Sprintf("summary=\"%s\"", i.Summary))
- }
- if i.Path != "" {
- attributes = append(attributes, fmt.Sprintf("path=\"%s\"", i.Path))
- }
- return fmt.Sprintf("", strings.Join(attributes, " "))
-}
-
-// AudioElement 音频消息元素
-type AudioElement struct {
- Src string
- Title string
- Duration uint32
- Poster string
- Stream io.ReadSeeker
- Summary string
- Path string
-}
-
-func (a *AudioElement) GetType() string {
- return "audio"
-}
-
-func (a *AudioElement) ToSatori() string {
- var attributes []string
- if a.Src != "" {
- attributes = append(attributes, fmt.Sprintf("src=\"%s\"", a.Src))
- }
- if a.Title != "" {
- attributes = append(attributes, fmt.Sprintf("title=\"%s\"", a.Title))
- }
- if a.Duration != 0 {
- attributes = append(attributes, fmt.Sprintf("duration=\"%d\"", a.Duration))
- }
- if a.Poster != "" {
- attributes = append(attributes, fmt.Sprintf("poster=\"%s\"", a.Poster))
- }
- if a.Summary != "" {
- attributes = append(attributes, fmt.Sprintf("summary=\"%s\"", a.Summary))
- }
- if a.Path != "" {
- attributes = append(attributes, fmt.Sprintf("path=\"%s\"", a.Path))
- }
- return fmt.Sprintf("", strings.Join(attributes, " "))
-}
-
-// VideoElement 视频消息元素
-type VideoElement struct {
- Src string
- Title string
- Width uint32
- Height uint32
- Duration uint32
- Poster string
- Path string
-}
-
-func (v *VideoElement) GetType() string {
- return "video"
-}
-
-func (v *VideoElement) ToSatori() string {
- var attributes []string
- if v.Src != "" {
- attributes = append(attributes, fmt.Sprintf("src=\"%s\"", v.Src))
- }
- if v.Title != "" {
- attributes = append(attributes, fmt.Sprintf("title=\"%s\"", v.Title))
- }
- if v.Width != 0 {
- attributes = append(attributes, fmt.Sprintf("width=\"%d\"", v.Width))
- }
- if v.Height != 0 {
- attributes = append(attributes, fmt.Sprintf("height=\"%d\"", v.Height))
- }
- if v.Duration != 0 {
- attributes = append(attributes, fmt.Sprintf("duration=\"%d\"", v.Duration))
- }
- if v.Poster != "" {
- attributes = append(attributes, fmt.Sprintf("poster=\"%s\"", v.Poster))
- }
- if v.Path != "" {
- attributes = append(attributes, fmt.Sprintf("path=\"%s\"", v.Path))
- }
- return fmt.Sprintf("", strings.Join(attributes, " "))
-}
-
-// ShortVideoElement 短视频消息元素
-type ShortVideoElement struct {
- Title string
- Src string
- Duration uint32
- Summary string
- Stream io.ReadSeeker
- Path string
-}
-
-func (s *ShortVideoElement) GetType() string {
- return "shortvideo"
-}
-
-func (s *ShortVideoElement) ToSatori() string {
- var attributes []string
- if s.Title != "" {
- attributes = append(attributes, fmt.Sprintf("title=\"%s\"", s.Title))
- }
- if s.Src != "" {
- attributes = append(attributes, fmt.Sprintf("src=\"%s\"", s.Src))
- }
- if s.Duration != 0 {
- attributes = append(attributes, fmt.Sprintf("duration=\"%d\"", s.Duration))
- }
- if s.Summary != "" {
- attributes = append(attributes, fmt.Sprintf("summary=\"%s\"", s.Summary))
- }
- if s.Path != "" {
- attributes = append(attributes, fmt.Sprintf("path=\"%s\"", s.Path))
- }
- return fmt.Sprintf("", strings.Join(attributes, " "))
-}
-
-// FileElement 文件消息元素
-type FileElement struct {
- Src string
- Title string
- Poster string
- Size uint64
- Stream io.ReadSeeker
- Path string
-}
-
-func (f *FileElement) GetType() string {
- return "file"
-}
-
-func (f *FileElement) ToSatori() string {
- var attributes []string
- if f.Src != "" {
- attributes = append(attributes, fmt.Sprintf("src=\"%s\"", f.Src))
- }
- if f.Title != "" {
- attributes = append(attributes, fmt.Sprintf("title=\"%s\"", f.Title))
- }
- if f.Poster != "" {
- attributes = append(attributes, fmt.Sprintf("poster=\"%s\"", f.Poster))
- }
- if f.Size != 0 {
- attributes = append(attributes, fmt.Sprintf("size=\"%d\"", f.Size))
- }
- if f.Path != "" {
- attributes = append(attributes, fmt.Sprintf("path=\"%s\"", f.Path))
- }
- return fmt.Sprintf("", strings.Join(attributes, " "))
-}
-
-// StrongElement 粗体消息元素
-type StrongElement struct {
- Text string
-}
-
-func (b *StrongElement) GetType() string {
- return "strong"
-}
-
-func (b *StrongElement) ToSatori() string {
- return fmt.Sprintf("%s", b.Text)
-}
-
-// EmElement 斜体消息元素
-type EmElement struct {
- Text string
-}
-
-func (e *EmElement) GetType() string {
- return "em"
-}
-
-func (e *EmElement) ToSatori() string {
- return fmt.Sprintf("%s", e.Text)
-}
-
-// InsElement 下划线消息元素
-type InsElement struct {
- Text string
-}
-
-func (i *InsElement) GetType() string {
- return "ins"
-}
-
-func (i *InsElement) ToSatori() string {
- return fmt.Sprintf("%s", i.Text)
-}
-
-// DelElement 删除线消息元素
-type DelElement struct {
- Text string
-}
-
-func (d *DelElement) GetType() string {
- return "del"
-}
-
-func (d *DelElement) ToSatori() string {
- return fmt.Sprintf("%s", d.Text)
-}
-
-// SplElement 剧透消息元素
-type SplElement struct {
- Text string
-}
-
-func (s *SplElement) GetType() string {
- return "spl"
-}
-
-func (s *SplElement) ToSatori() string {
- return fmt.Sprintf("%s", s.Text)
-}
-
-// CodeElement 代码消息元素
-type CodeElement struct {
- Text string
-}
-
-func (c *CodeElement) GetType() string {
- return "code"
-}
-
-func (c *CodeElement) ToSatori() string {
- return fmt.Sprintf("%s
", c.Text)
-}
-
-// SupElement 上标消息元素
-type SupElement struct {
- Text string
-}
-
-func (s *SupElement) GetType() string {
- return "sup"
-}
-
-func (s *SupElement) ToSatori() string {
- return fmt.Sprintf("%s", s.Text)
-}
-
-// SubElement 下标消息元素
-type SubElement struct {
- Text string
-}
-
-func (s *SubElement) GetType() string {
- return "sub"
-}
-
-func (s *SubElement) ToSatori() string {
- return fmt.Sprintf("%s", s.Text)
-}
-
-// BrElement 换行消息元素
-type BrElement struct{}
-
-func (b *BrElement) GetType() string {
- return "br"
-}
-
-func (b *BrElement) ToSatori() string {
- return "
"
-}
-
-// HrElement *水平线消息元素
-type HrElement struct{}
-
-func (h *HrElement) GetType() string {
- return "hr"
-}
-
-func (h *HrElement) ToSatori() string {
- return "
"
-}
-
-// AuthorElement 作者消息元素
-type AuthorElement struct {
- Id string
- Name string
- Avatar string
-}
-
-func (a *AuthorElement) GetType() string {
- return "author"
-}
-
-func (a *AuthorElement) ToSatori() string {
- var attributes []string
- if a.Id != "" {
- attributes = append(attributes, fmt.Sprintf("id=\"%s\"", a.Id))
- }
- if a.Name != "" {
- attributes = append(attributes, fmt.Sprintf("name=\"%s\"", a.Name))
- }
- if a.Avatar != "" {
- attributes = append(attributes, fmt.Sprintf("avatar=\"%s\"", a.Avatar))
- }
- return fmt.Sprintf("", strings.Join(attributes, " "))
-}
-
-// ButtonElement 按钮消息元素
-type ButtonElement struct {
- Id string
- Type string
- Href string
- Text string
-}
-
-func (b *ButtonElement) GetType() string {
- return "button"
-}
-
-func (b *ButtonElement) ToSatori() string {
- var attributes []string
- if b.Id != "" {
- attributes = append(attributes, fmt.Sprintf("id=\"%s\"", b.Id))
- }
- if b.Type != "" {
- attributes = append(attributes, fmt.Sprintf("type=\"%s\"", b.Type))
- }
- if b.Href != "" {
- attributes = append(attributes, fmt.Sprintf("href=\"%s\"", b.Href))
- }
- if b.Text != "" {
- attributes = append(attributes, b.Text)
- }
- return fmt.Sprintf("", strings.Join(attributes, " "))
-}
-
-// FaceElement *表情消息元素,可以传递用于一些平台内置的表情
-type FaceElement struct {
- Id uint16
- IsLargeFace bool
-}
-
-func (f *FaceElement) GetType() string {
- return "face"
-}
-
-func (f *FaceElement) ToSatori() string {
- var attributes []string
- if f.Id != 0 {
- attributes = append(attributes, fmt.Sprintf("id=\"%d\"", f.Id))
- }
- if f.IsLargeFace {
- attributes = append(attributes, "size=\"large\"")
- }
- return fmt.Sprintf("", strings.Join(attributes, " "))
-}
-
-// QuoteElement *引用消息元素,可以引用消息中的某一部分内容
-//
-// # Satori中虽然也标注了引用消息元素,但是没有给出具体设计,这里主要用于QQ的引用消息设计
-//
-// 一般只用于解析接受到的回复消息事件,主动进行回复消息需要通过对应适配器的bot API进行
-type QuoteElement struct {
- UserId string
- UserName string
- GroupId string
- Timestamp time.Time
- Elements *[]IceinuMessageElement
-}
-
-func (q *QuoteElement) GetType() string {
- return "quote"
-}
-
-func (q *QuoteElement) ToSatori() string {
- var attributes []string
- if q.UserId != "" {
- attributes = append(attributes, fmt.Sprintf("id=\"%s\"", q.UserId))
- }
- if q.UserName != "" {
- attributes = append(attributes, fmt.Sprintf("name=\"%s\"", q.UserName))
- }
- if q.GroupId != "" {
- attributes = append(attributes, fmt.Sprintf("group=\"%s\"", q.GroupId))
- }
- if !q.Timestamp.IsZero() {
- attributes = append(attributes, fmt.Sprintf("time=\"%s\"", q.Timestamp.Format(time.RFC3339)))
- }
-
- // 构建开始标签,包含属性
- openingTag := fmt.Sprintf("", strings.Join(attributes, " "))
-
- // 将子元素转换为 Satori 字符串
- var elements []string
- if q.Elements != nil {
- for _, element := range *q.Elements {
- elements = append(elements, element.ToSatori())
- }
- }
-
- // 定义结束标签
- closingTag := "
"
-
- // 组合所有部分
- return openingTag + strings.Join(elements, "") + closingTag
-}
-
-// UnsupportedElement 未支持的消息元素
-type UnsupportedElement struct {
- Type string
-}
-
-func (u *UnsupportedElement) GetType() string {
- return "unsupported"
-}
-
-func (u *UnsupportedElement) ToSatori() string {
- return fmt.Sprintf("", u.Type)
-}
-
-// MessageElement 消息封装元素
-type MessageElement struct {
- Id string
- Forward bool
- NtForward bool
- Elements *[]IceinuMessageElement
-}
-
-func (m *MessageElement) GetType() string {
- return "message"
-}
-
-func (m *MessageElement) ToSatori() string {
- var attributes []string
- if m.Id != "" {
- attributes = append(attributes, fmt.Sprintf("id=\"%s\"", m.Id))
- }
- if m.Forward {
- attributes = append(attributes, "forward=\"true\"")
- }
-
- // 构建开始标签,包含属性
- openingTag := fmt.Sprintf("", strings.Join(attributes, " "))
-
- // 将子元素转换为 Satori 字符串
- var elements []string
- if m.Elements != nil {
- for _, element := range *m.Elements {
- elements = append(elements, element.ToSatori())
- }
- }
-
- // 定义结束标签
- closingTag := ""
-
- // 组合所有部分
- return openingTag + strings.Join(elements, "") + closingTag
-}
-
-// NodeElement *消息节点封装元素,用于QQ平台的节点合并转发消息
-type NodeElement struct {
- GroupId int64
- SenderId int64
- SenderName string
- Time int32
- Message *[]IceinuMessageElement
-}
-
-func (n *NodeElement) GetType() string {
- return "node"
-}
-
-func (n *NodeElement) ToSatori() string {
- var attributes []string
- if n.GroupId != 0 {
- attributes = append(attributes, fmt.Sprintf("group=\"%d\"", n.GroupId))
- }
- if n.SenderId != 0 {
- attributes = append(attributes, fmt.Sprintf("sender_id=\"%d\"", n.SenderId))
- }
- if n.SenderName != "" {
- attributes = append(attributes, fmt.Sprintf("sender_name=\"%s\"", n.SenderName))
- }
- if n.Time != 0 {
- attributes = append(attributes, fmt.Sprintf("time=\"%d\"", n.Time))
- }
-
- // 构建开始标签,包含属性
- openingTag := fmt.Sprintf("", strings.Join(attributes, " "))
-
- // 将消息内容转换为 Satori 字符串
- var messages []string
- if n.Message != nil {
- for _, element := range *n.Message {
- messages = append(messages, element.ToSatori())
- }
- }
-
- // 定义结束标签
- closingTag := ""
-
- // 组合所有部分
- return openingTag + strings.Join(messages, "") + closingTag
-}
diff --git a/go.mod b/go.mod
deleted file mode 100644
index 5e09ef8..0000000
--- a/go.mod
+++ /dev/null
@@ -1,25 +0,0 @@
-module github.com/Iceinu-Project/iceinu
-
-go 1.22
-
-require (
- github.com/KyokuKong/gradients v0.0.0-20240910005044-45c73fcaed90
- github.com/LagrangeDev/LagrangeGo v0.0.0-20240916064511-7e6af3753ba4
- github.com/pelletier/go-toml v1.9.5
- github.com/sirupsen/logrus v1.9.3
-)
-
-require (
- github.com/RomiChan/protobuf v0.1.1-0.20230204044148-2ed269a2e54d // indirect
- github.com/RomiChan/syncx v0.0.0-20240418144900-b7402ffdebc7 // indirect
- github.com/fumiama/gofastTEA v0.0.10 // indirect
- github.com/fumiama/imgsz v0.0.4 // indirect
- github.com/pkg/errors v0.9.1 // indirect
- github.com/tidwall/gjson v1.17.3 // indirect
- github.com/tidwall/match v1.1.1 // indirect
- github.com/tidwall/pretty v1.2.1 // indirect
- golang.org/x/image v0.20.0 // indirect
- golang.org/x/net v0.29.0 // indirect
- golang.org/x/sync v0.7.0 // indirect
- golang.org/x/sys v0.25.0 // indirect
-)
diff --git a/ice/adapter_events.go b/ice/adapter_events.go
deleted file mode 100644
index 4b3c564..0000000
--- a/ice/adapter_events.go
+++ /dev/null
@@ -1,27 +0,0 @@
-package ice
-
-import (
- "github.com/Iceinu-Project/iceinu/adapter"
- "time"
-)
-
-// AdapterInitEvent 适配器初始化事件
-type AdapterInitEvent struct {
- Timestamp time.Time
- AdapterMeta *adapter.IceAdapterMeta
-}
-
-// AdapterConnectEvent 适配器连接事件
-type AdapterConnectEvent struct {
- Timestamp time.Time
-}
-
-// AdapterDisconnectEvent 适配器断开连接事件
-type AdapterDisconnectEvent struct {
- Timestamp time.Time
-}
-
-// AdapterLoginEvent 适配器登录事件
-type AdapterLoginEvent struct {
- Timestamp time.Time
-}
diff --git a/ice/event_bus.go b/ice/event_bus.go
deleted file mode 100644
index 16ed85d..0000000
--- a/ice/event_bus.go
+++ /dev/null
@@ -1,90 +0,0 @@
-package ice
-
-import "sync"
-
-// iceinu的事件总线实现,启动后生成全局共享的Bus实例
-// 一般情况下事件的推送和订阅由适配器和框架完成,但是也可以自己直接调用Bus实例的方法来绕过已有的事件封装(不推荐)
-
-func init() {
- // 创建事件总线
- Bus = CreateBus()
-}
-
-// Bus 全局事件总线实例
-var Bus *EventBus
-
-// Middleware 中间件结构体,可以通过实现中间件来对事件的发布过程进行一些处理
-type Middleware func(eventType string, payload interface{})
-
-// Event 在事件总线中传递的事件结构体
-type Event struct {
- EventType string
- Payload interface{}
-}
-
-// EventChan 事件总线中的事件通道
-type EventChan chan Event
-
-// EventBus 事件总线结构体
-type EventBus struct {
- subscribers map[string][]EventChan
- lock sync.RWMutex
- middlewares []Middleware
-}
-
-// CreateBus 创建事件总线,这个函数一般不需要单独调用,iceinu会在运行时自动创建事件总线
-func CreateBus() *EventBus {
- newBus := &EventBus{
- subscribers: make(map[string][]EventChan),
- }
- return newBus
-}
-
-// Subscribe 订阅指定类型的事件
-func (bus *EventBus) Subscribe(eventType string, ch EventChan) {
- bus.lock.Lock()
- defer bus.lock.Unlock()
- bus.subscribers[eventType] = append(bus.subscribers[eventType], ch)
-}
-
-// Unsubscribe 取消订阅指定类型的事件
-func (bus *EventBus) Unsubscribe(eventType string, ch EventChan) {
- bus.lock.Lock()
- defer bus.lock.Unlock()
- subscribers := bus.subscribers[eventType]
- for i, subscriber := range subscribers {
- if subscriber == ch {
- bus.subscribers[eventType] = append(subscribers[:i], subscribers[i+1:]...)
- break
- }
- }
-}
-
-// Publish 推送事件到事件总线
-func (bus *EventBus) Publish(eventType string, payload interface{}) {
- // 调用所有中间件
- for _, mw := range bus.middlewares {
- mw(eventType, payload)
- }
-
- // 持有锁
- bus.lock.RLock()
- subscribers, found := bus.subscribers[eventType]
- bus.lock.RUnlock()
-
- // 进入事件处理
- if found {
- event := Event{EventType: eventType, Payload: payload}
- // 把每个订阅的事件丢给协程处理避免阻塞
- for _, ch := range subscribers {
- go func(ch EventChan) {
- ch <- event
- }(ch)
- }
- }
-}
-
-// AddMiddleware 向事件总线中添加自定义的中间件
-func (bus *EventBus) AddMiddleware(mw Middleware) {
- bus.middlewares = append(bus.middlewares, mw)
-}
diff --git a/ice/uni_events.go b/ice/uni_events.go
deleted file mode 100644
index 307e158..0000000
--- a/ice/uni_events.go
+++ /dev/null
@@ -1,28 +0,0 @@
-package ice
-
-import (
- "time"
-
- "github.com/Iceinu-Project/iceinu/resource"
-)
-
-// PlatformEvent Iceinu的基础平台事件结构体,基本参考了Satori的设计,用于实现跨平台的统一事件格式
-//
-// 当然,也参考了Satori的资源系统
-type PlatformEvent struct {
- EventId uint64 // 事件ID
- EventType string // 事件类型
- Platform string // 接收者平台名称
- SelfId string // 接收者平台账号
- Timestamp time.Time // 事件推送的时间戳
- Argv *resource.Argv // 交互指令
- Button *resource.Button // 交互按钮
- Channel *resource.Channel // 事件所属的频道
- Group *resource.Group // 事件所属的群组
- Login *resource.Login // 事件的登录信息
- Member *resource.GroupMember // 事件的目标成员
- Message *resource.Message // 事件的消息
- Operator *resource.User // 事件的操作者
- Role *resource.GroupRole // 事件的目标角色
- User *resource.User // 事件的目标用户
-}
diff --git a/logger/logger.go b/logger/logger.go
deleted file mode 100644
index 3575324..0000000
--- a/logger/logger.go
+++ /dev/null
@@ -1,141 +0,0 @@
-package logger
-
-import (
- "fmt"
- "github.com/KyokuKong/gradients"
- "github.com/sirupsen/logrus"
- "os"
-)
-
-var logger *logrus.Logger
-
-// 初始化日志Logger
-func init() {
- // 创建一个新的logrus.Logger实例
- logger = logrus.New()
-
- // 设置日志级别为InfoLevel
- logger.SetLevel(logrus.InfoLevel)
-
- // 设置输出格式为JSON格式
- logger.SetFormatter(&IceinuFormatter{})
-
- // 将日志输出到标准输出
- logger.SetOutput(os.Stdout)
-}
-
-type IceinuFormatter struct{}
-
-func (f *IceinuFormatter) Format(entry *logrus.Entry) ([]byte, error) {
- // 根据日志级别设置不同的颜色
- var textColor string
- var levelColor string
- var levelText string
- switch entry.Level {
- case logrus.DebugLevel, logrus.TraceLevel:
- levelColor = gradients.DarkGreen
- textColor = gradients.DarkGreen
- levelText = "DEBUG"
- case logrus.InfoLevel:
- levelColor = gradients.DarkCyan
- textColor = gradients.White
- levelText = "_INFO"
- case logrus.WarnLevel:
- levelColor = gradients.Orange
- textColor = gradients.DarkYellow
- levelText = "_WARN"
- case logrus.ErrorLevel, logrus.FatalLevel, logrus.PanicLevel:
- levelColor = gradients.Red
- textColor = gradients.Red
- levelText = "ERROR"
- default:
- levelColor = gradients.White
- textColor = gradients.White
- levelText = "UNKNOWN"
- }
-
- // 构建日志格式,可以按需修改
- logMsg := fmt.Sprintf(
- "%s• %s %s[%s%s%s] %s%s\n",
- gradients.Gray,
- entry.Time.Format("2006-01-02 15:04:05"),
- textColor,
- levelColor,
- levelText,
- textColor,
- entry.Message,
- gradients.ResetColor,
- )
-
- return []byte(logMsg), nil
-}
-
-// Info 输出Info级别的日志
-func Info(args ...interface{}) {
- logger.Info(args...)
-}
-
-// Infof 格式化输出Info级别的日志
-func Infof(format string, args ...interface{}) {
- logger.Infof(format, args...)
-}
-
-// Debug 输出Debug级别的日志
-func Debug(args ...interface{}) {
- logger.Debug(args...)
-}
-
-// Debugf 格式化输出Debug级别的日志
-func Debugf(format string, args ...interface{}) {
- logger.Debugf(format, args...)
-}
-
-// Warn 输出Warn级别的日志
-func Warn(args ...interface{}) {
- logger.Warn(args...)
-}
-
-// Warnf 格式化输出Warn级别的日志
-func Warnf(format string, args ...interface{}) {
- logger.Warnf(format, args...)
-}
-
-// Error 输出Error级别的日志
-func Error(args ...interface{}) {
- logger.Error(args...)
-}
-
-// Errorf 格式化输出Error级别的日志
-func Errorf(format string, args ...interface{}) {
- logger.Errorf(format, args...)
-}
-
-// Fatal 输出Fatal级别的日志
-func Fatal(args ...interface{}) {
- logger.Fatal(args...)
-}
-
-// Fatalf 格式化输出Fatal级别的日志
-func Fatalf(format string, args ...interface{}) {
- logger.Fatalf(format, args...)
-}
-
-// Panic 输出Panic级别的日志
-func Panic(args ...interface{}) {
- logger.Panic(args...)
-}
-
-// Panicf 格式化输出Panic级别的日志
-func Panicf(format string, args ...interface{}) {
- logger.Panicf(format, args...)
-}
-
-func SetLevel(level string) {
- l, err := logrus.ParseLevel(level)
- if err != nil {
- logger.Warnf("设置日志等级失败:%v", err)
- return
- }
- logger.Infof("日志等级已设置为:%s", level)
- logger.SetLevel(l)
-}
diff --git a/logger/protocol_logger.go b/logger/protocol_logger.go
deleted file mode 100644
index 94709d8..0000000
--- a/logger/protocol_logger.go
+++ /dev/null
@@ -1,49 +0,0 @@
-package logger
-
-import (
- "fmt"
- "os"
- "path"
- "time"
-)
-
-// ProtocolLogger from https://github.com/Mrs4s/go-cqhttp/blob/a5923f179b360331786a6509eb33481e775a7bd1/cmd/gocq/main.go#L501
-type ProtocolLogger struct{}
-
-var dumpspath = "dump"
-
-const fromProtocol = "LGR | "
-
-func (p ProtocolLogger) Info(format string, arg ...any) {
- Infof(fromProtocol+format, arg...)
-}
-
-func (p ProtocolLogger) Warning(format string, arg ...any) {
- Warnf(fromProtocol+format, arg...)
-}
-
-func (p ProtocolLogger) Debug(format string, arg ...any) {
- Debugf(fromProtocol+format, arg...)
-}
-
-func (p ProtocolLogger) Error(format string, arg ...any) {
- Errorf(fromProtocol+format, arg...)
-}
-
-func (p ProtocolLogger) Dump(data []byte, format string, arg ...any) {
- message := fmt.Sprintf(format, arg...)
- if _, err := os.Stat(dumpspath); err != nil {
- err = os.MkdirAll(dumpspath, 0o755)
- if err != nil {
- Errorf("出现错误 %v. 详细信息转储失败", message)
- return
- }
- }
- dumpFile := path.Join(dumpspath, fmt.Sprintf("%v.dump", time.Now().Unix()))
- Errorf("出现错误 %v. 详细信息已转储至文件 %v 请连同日志提交给开发者处理", message, dumpFile)
- _ = os.WriteFile(dumpFile, data, 0o644)
-}
-
-func GetProtocolLogger() ProtocolLogger {
- return ProtocolLogger{}
-}
diff --git a/main.go b/main.go
deleted file mode 100644
index af689c9..0000000
--- a/main.go
+++ /dev/null
@@ -1,54 +0,0 @@
-package main
-
-import (
- "flag"
- "github.com/Iceinu-Project/iceinu/adapter"
- "github.com/Iceinu-Project/iceinu/adapter/lagrange"
- "github.com/Iceinu-Project/iceinu/config"
- "github.com/Iceinu-Project/iceinu/ice"
- "github.com/Iceinu-Project/iceinu/logger"
-)
-
-func main() {
- logger.Infof("正在启动Iceinu,请稍等...")
-
- // 解析启动参数
- IsDebug := flag.Bool("debug", false, "是否启用调试模式")
- flag.Parse()
-
- // 设置调试日志等级
- if *IsDebug {
- logger.SetLevel("DEBUG")
- }
-
- // 初始化适配器,默认使用LagrangeGo适配器
- var a adapter.IceAdapter
- a = &lagrange.AdapterLagrange{}
-
- // 读取配置文件
- lagrange.RegisterConfig()
-
- // 处理所有配置文件
- cm := config.GetManager()
- err := cm.LoadConfigs()
- if err != nil {
- logger.Fatalf("读取配置文件时发生错误:%v", err)
- }
-
- // 设置日志等级
- iceConf := cm.Get("iceinu.toml").(*config.IceinuConfig)
- if !*IsDebug {
- logger.Debugf("调试模式已启用,跳过默认的日志等级设置")
- if iceConf.LogLevel != "" {
- logger.SetLevel(iceConf.LogLevel)
- }
- }
-
- // 自定义事件总线中间件
- ice.Bus.AddMiddleware(func(eventType string, payload interface{}) {
- logger.Debugf("事件推送:%s", eventType)
- })
-
- // 启动适配器初始化
- a.Init()
-}
diff --git a/resource/channel.go b/resource/channel.go
deleted file mode 100644
index 97f57f1..0000000
--- a/resource/channel.go
+++ /dev/null
@@ -1,8 +0,0 @@
-package resource
-
-type Channel struct {
- Id string // 频道ID
- Type uint8 // 频道类型, 0: 文本频道, 1: 私聊频道,2:分类频道,3:语音频道
- Name string // 频道名称
- ParentId string // 父频道ID
-}
diff --git a/resource/files.go b/resource/files.go
deleted file mode 100644
index 7edd10c..0000000
--- a/resource/files.go
+++ /dev/null
@@ -1,35 +0,0 @@
-package resource
-
-// From LagrangeGo:https://github.com/LagrangeDev/LagrangeGo/blob/master/client/entity/group_file.go
-
-type GroupFileSystemInfo struct {
- GroupUin uint32
- FileCount uint32
- LimitCount uint32
- UsedSpace uint64
- TotalSpace uint64
-}
-
-type GroupFile struct {
- GroupUin uint32
- FileId string
- FileName string
- BusId uint32
- FileSize uint64
- UploadTime uint32
- DeadTime uint32
- ModifyTime uint32
- DownloadTimes uint32
- Uploader uint32
- UploaderName string
-}
-
-type GroupFolder struct {
- GroupUin uint32
- FolderId string
- FolderName string
- CreateTime uint32
- Creator uint32
- CreatorName string
- TotalFileCount uint32
-}
diff --git a/resource/group.go b/resource/group.go
deleted file mode 100644
index f7ce108..0000000
--- a/resource/group.go
+++ /dev/null
@@ -1,23 +0,0 @@
-package resource
-
-import "time"
-
-type Group struct {
- Id string // 群组ID
- Name string // 群组名称
- Avatar string // 群组头像
- Maxcount uint32 // 群组最大人数
- MemberCount uint32 // 群组成员数量
-}
-
-type GroupMember struct {
- User *User
- Nickname string
- Avatar string
- JoinedAt time.Time
-}
-
-type GroupRole struct {
- Id string
- Name string
-}
diff --git a/resource/interaction.go b/resource/interaction.go
deleted file mode 100644
index 034e9f8..0000000
--- a/resource/interaction.go
+++ /dev/null
@@ -1,11 +0,0 @@
-package resource
-
-type Argv struct {
- Name string
- Arguments []interface{}
- Options interface{}
-}
-
-type Button struct {
- Id string
-}
diff --git a/resource/login.go b/resource/login.go
deleted file mode 100644
index 24c5717..0000000
--- a/resource/login.go
+++ /dev/null
@@ -1,10 +0,0 @@
-package resource
-
-type Login struct {
- User *User // 用户对象
- SelfId string // 平台账号
- Platform string // 平台名称
- Status uint8 // 登录状态,0为离线,1为在线,2为连接中,3为断开连接,4为重新连接
- Features []string // 平台特性列表
- ProxyUrls []string // 代理路由列表
-}
diff --git a/resource/message.go b/resource/message.go
deleted file mode 100644
index 0845e74..0000000
--- a/resource/message.go
+++ /dev/null
@@ -1,18 +0,0 @@
-package resource
-
-import (
- "github.com/Iceinu-Project/iceinu/elements"
- "time"
-)
-
-type Message struct {
- Id string
- Content string
- Channel *Channel
- Group *Group
- Member *GroupMember
- User *User
- CreatedAt time.Time
- UpdatedAt time.Time
- MessageElements *[]elements.IceinuMessageElement
-}
diff --git a/resource/paged_list.go b/resource/paged_list.go
deleted file mode 100644
index 60d6ef8..0000000
--- a/resource/paged_list.go
+++ /dev/null
@@ -1,6 +0,0 @@
-package resource
-
-type PagedList struct {
- Data interface{}
- Next string
-}
diff --git a/resource/reaction.go b/resource/reaction.go
deleted file mode 100644
index 246281e..0000000
--- a/resource/reaction.go
+++ /dev/null
@@ -1,3 +0,0 @@
-package resource
-
-// 暂未支持
diff --git a/resource/user.go b/resource/user.go
deleted file mode 100644
index bb9fe6f..0000000
--- a/resource/user.go
+++ /dev/null
@@ -1,9 +0,0 @@
-package resource
-
-type User struct {
- Id string // 用户ID
- Name string // 用户名称
- Nickname string // 用户昵称
- Avatar string // 用户头像
- IsBot bool // 是否是机器人
-}
diff --git a/utils/jprint.go b/utils/jprint.go
deleted file mode 100644
index 994a7f7..0000000
--- a/utils/jprint.go
+++ /dev/null
@@ -1,23 +0,0 @@
-package utils
-
-import (
- "encoding/json"
- "fmt"
- "os"
-)
-
-func JPrint(input interface{}) {
- // 创建 JSON 编码器并禁用 HTML 转义
- encoder := json.NewEncoder(os.Stdout)
- encoder.SetEscapeHTML(false) // 禁用自动转义
-
- // 美化输出
- encoder.SetIndent("", " ")
-
- // 尝试将输入转换为 JSON 字符串并输出到 stdout
- err := encoder.Encode(input)
- if err != nil {
- fmt.Println("无法将输入转换为 JSON:", err)
- return
- }
-}
diff --git a/utils/satorize.go b/utils/satorize.go
deleted file mode 100644
index 9a842e7..0000000
--- a/utils/satorize.go
+++ /dev/null
@@ -1,17 +0,0 @@
-package utils
-
-import (
- "github.com/Iceinu-Project/iceinu/elements"
- "strings"
-)
-
-// SatorizeIceElements 将Iceinu的元素切片解压成Satori字符串
-func SatorizeIceElements(e *[]elements.IceinuMessageElement) string {
- var satoriStrings []string
- if e != nil {
- for _, element := range *e {
- satoriStrings = append(satoriStrings, element.ToSatori())
- }
- }
- return strings.Join(satoriStrings, "")
-}
From 0302647ba659b9ded2546e6a516816468473462f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kyoku=E2=AD=90?=
<144906395+KyokuKong@users.noreply.github.com>
Date: Mon, 23 Sep 2024 03:34:32 +0800
Subject: [PATCH 2/9] =?UTF-8?q?refact=EF=BC=9A=E9=87=8D=E6=9E=84=E6=A1=86?=
=?UTF-8?q?=E6=9E=B6=E8=AE=BE=E8=AE=A1?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 63 +++++++++
REFACT.md | 43 ++++++
go.mod | 10 ++
ice/event_bus.go | 1 +
log/logger.go | 94 +++++++++++++
main.go | 68 ++++++++++
models/event.go | 17 +++
models/satori/README.md | 9 ++
models/satori/satori_elements.go | 226 +++++++++++++++++++++++++++++++
models/satori/satori_model.go | 103 ++++++++++++++
models/satori/tools.go | 11 ++
static/README.md | 3 +
12 files changed, 648 insertions(+)
create mode 100644 README.md
create mode 100644 REFACT.md
create mode 100644 go.mod
create mode 100644 ice/event_bus.go
create mode 100644 log/logger.go
create mode 100644 main.go
create mode 100644 models/event.go
create mode 100644 models/satori/README.md
create mode 100644 models/satori/satori_elements.go
create mode 100644 models/satori/satori_model.go
create mode 100644 models/satori/tools.go
create mode 100644 static/README.md
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..b185ef5
--- /dev/null
+++ b/README.md
@@ -0,0 +1,63 @@
+# 🧊Iceinu 氷犬
+
+![Go Badge](https://img.shields.io/badge/Go-1.22%2B-cyan?logo=go)
+[![workflow](https://github.com/Iceinu-Project/iceinu/actions/workflows/go.yml/badge.svg)](https://github.com/Iceinu-Project/iceinu/actions)
+[![goreportcard](https://img.shields.io/badge/go%20report-A+-brightgreen.svg?style=flat)](https://goreportcard.com/report/github.com/Iceinu-Project/iceinu)
+[![QQGroup Badge](https://img.shields.io/badge/QQ群-970801565-blue?)](https://qm.qq.com/q/93crfU39ny)
+
+氷犬Iceinu 是一个多用途的Go语言聊天机器人框架,可以将其作为开发套件来进行二次开发,亦或者作为库按需引入来快速编写自己的聊天机器人(暂时没有实现)。
+
+🚧暂时还在~~画饼~~施工中,晚点再来探索吧~
+
+## 开发进度
+
+Refact分支正在进行重构,目前进度还在推进,过几天再来探索吧~
+
+## 特性
+
+~~哪里是特性,完全是画饼,一半都还没实现完~~
+
+- 基于Go开发,性能表现良好
+- 基于统一事件驱动的消息推送机制
+- 以Satori作为基础实现了统一消息标准
+- 可直接发送Satori标准的XHTML消息
+- 模块化适配器设计
+- 动态/静态集群,跨平台集群
+- 完整的动态权限管理系统
+- 在插件间共享数据库连接池
+- 类Alconna的命令解析器
+- 可配置自动向指定用户/频道发送日志
+- 主动信息推送/订阅机制
+- 从HTML+CSS模板渲染图片(基于wkhtmltoimage集成,未来可能会实现)
+
+## 直接部署
+
+(目前仍然属于开发前期,部署了也暂时没什么用处)
+
+访问[Github Action](https://github.com/Iceinu-Project/iceinu/actions)就可以获取Iceinu的自动构建二进制文件,Iceinu默认集成了`LagrangeGo`所以无需再配置onebot
+协议连接,第一次启动时会自动检测并生成配置文件,完成配置之后在命令行中输入回车即可开始运行。
+
+你可以参照Iceinu数据库配置指南来配置Iceinu使用的PostgreSQL数据库。
+
+Iceinu在设计上支持集群部署,且支持动态组网式集群(需要各个Bot实例之间可以相互访问)和静态总控式集群(需要一个Bot实例作为总控,这个实例本身不能下线)
+
+## 二次开发
+
+(文档还没写,Release之前会开始编写文档)
+
+Go语言的静态特性让它非常不怎么适合传统意义上的模块化加载,所以Iceinu并没有也不会实现从外部进行的插件加载。
+
+不过Iceinu通过接口定义了内部插件的实现,直接拉取代码跟着插件文档进行二次开发即可扩展更多的插件功能。
+
+```shell
+git clone git@github.com:Iceinu-Project/iceinu.git
+```
+
+## 鸣谢
+
+- [Lagrange.Core](https://github.com/LagrangeDev/Lagrange.Core) NTQQ通信协议的C#实现
+- [LagrangeGo](https://github.com/LagrangeDev/LagrangeGo) Lagrange.Core的Go实现
+- [LagrangeGo-Teamplate](https://github.com/ExquisiteCore/LagrangeGo-Template) LagrangeGo的模板示例项目
+- [go-cqhttp](https://github.com/Mrs4s/go-cqhttp) 基于 Mirai 以及 MiraiGo 的 OneBot Golang 原生实现
+- [ZeroBot](https://github.com/wdvxdr1123/ZeroBot) 基于onebot协议的Golang聊天机器人开发框架
+- [Logrus](https://github.com/sirupsen/logrus) 强大好用的Go日志库
\ No newline at end of file
diff --git a/REFACT.md b/REFACT.md
new file mode 100644
index 0000000..c11a273
--- /dev/null
+++ b/REFACT.md
@@ -0,0 +1,43 @@
+这个分支是对Iceinu现有设计的重构
+
+因为写的实在是有点太混乱并且写的过程中又有了一些新的想法,需要对代码进行一遍重构来改善质量。
+
+在之后完成度超过主分支之后会进行合并并替代主分支
+
+## 重构目标
+
+之前对于Iceinu的组网设计比较抽象,虽然设想了集群但是完全没有做出相应的设计。
+
+Iceinu会在启动时为实例分配一个ID,作为对每个实例的操作基础,机器人的实例组网有多种方式。
+
+首先是单节点模式,也就是默认的Bot运行模式,不进行集群,实例的事件总线完全本地运行。
+
+静态集群模式,需要一个可以被访问的主实例,这个模式下每个实例的事件总线仍然是本地运行的,但是节点会向主实例推送
+自己的用户树,主节点会和所有子节点维护一个长连接网络,用于互相推送事件,在接收到用户树推送/更新后动态计算分配
+各个节点的用户可用性,实现各个bot之间消息处理的去重
+
+动态集群模式,基本和静态集群模式一致,但是需要各个节点之间都可以相互访问,当主节点出现任何问题导致无法访问时,其
+他任何一个节点都可以重新成为主实例。
+
+分布式模式,这个模式下的事件总线只在主实例上运行,主实例会通过长连接网络接受每一个节点的消息事件推送,并将消息处
+理事件对所有节点进行广播,子节点自动匹配对应的消息处理事件。
+
+集群模式下各个节点各自具备完整的信息处理能力,有自己的消息缓存,适合有多台性能比较高的机器的情况,且动态集群模式
+如果可以实现应该是可用性总体来讲最优秀的状态。分布式实际上是将子节点变成了传统意义上的协议端,对低性能设备更友好
+,非常适合分散部署大量实例。
+
+同时还要引入一个新的设计,模型(Model),用来定义一个或多个平台的消息事件,默认模型应该是基于Satori的,但是如果
+出现了自己定制私有的消息事件模型和适配器的情况,可以直接通过更换模型来实现,模型无需实现接口,它本身就不是通用的
+,而是在特殊情况下的一个解决方案,可以让适配器的设计更加灵活,用来设计一些不考虑全平台兼容的适配器,只需要实现适
+配单个平台的消息事件。
+
+消息缓存,这个也是之前的设计基本没有考虑的一个东西,适配器可以自由实现对应平台的消息模型,Iceinu自动将消息模型实
+现为缓存数据库中的一个表,这个缓存数据表具有固定的缓存数量,用来缓存对应平台的消息,供适配器进行取用。
+
+目前消息缓存基本上会使用一个额外的DuckDB来实现,因为消息可能需要一定程度上的持久化,加上嵌入式数据库部署更方便,
+所以也就暂时不考虑Redis之类的缓存数据库。
+
+在Iceinu的设计里,main.go实际上更接近于一个启动脚本,如果直接将Iceinu作为开发框架引入那么修改这个启动脚本就可以
+实现大部分对框架本身的配置,我希望如果其他开发者使用Iceinu进行二次开发的话那么他们在开发过程中能集中精力主要实
+现自己的想法和功能,框架能把绝大多数大家需要的功能都封装好,能够让大家都用最轻松的方式进行开发;同时如果将Iceinu
+作为库来导入自由利用其中的部分设计来实现自己的功能也可以比较方便的进行调用。
\ No newline at end of file
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..56e2ea9
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,10 @@
+module github.com/Iceinu-Project/iceinu
+
+go 1.23.1
+
+require (
+ github.com/KyokuKong/gradients v0.0.0-20240910005044-45c73fcaed90
+ github.com/sirupsen/logrus v1.9.3
+)
+
+require golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
diff --git a/ice/event_bus.go b/ice/event_bus.go
new file mode 100644
index 0000000..f1c4f73
--- /dev/null
+++ b/ice/event_bus.go
@@ -0,0 +1 @@
+package ice
diff --git a/log/logger.go b/log/logger.go
new file mode 100644
index 0000000..c805469
--- /dev/null
+++ b/log/logger.go
@@ -0,0 +1,94 @@
+package log
+
+import (
+ "github.com/sirupsen/logrus"
+ "os"
+)
+
+var logger *logrus.Logger
+
+// 初始化日志Logger
+func init() {
+ // 创建一个新的logrus.Logger实例
+ logger = logrus.New()
+
+ // 设置日志级别为InfoLevel
+ logger.SetLevel(logrus.InfoLevel)
+
+ // 将日志输出到标准输出
+ logger.SetOutput(os.Stdout)
+}
+
+// Info 输出Info级别的日志
+func Info(args ...interface{}) {
+ logger.Info(args...)
+}
+
+// Infof 格式化输出Info级别的日志
+func Infof(format string, args ...interface{}) {
+ logger.Infof(format, args...)
+}
+
+// Debug 输出Debug级别的日志
+func Debug(args ...interface{}) {
+ logger.Debug(args...)
+}
+
+// Debugf 格式化输出Debug级别的日志
+func Debugf(format string, args ...interface{}) {
+ logger.Debugf(format, args...)
+}
+
+// Warn 输出Warn级别的日志
+func Warn(args ...interface{}) {
+ logger.Warn(args...)
+}
+
+// Warnf 格式化输出Warn级别的日志
+func Warnf(format string, args ...interface{}) {
+ logger.Warnf(format, args...)
+}
+
+// Error 输出Error级别的日志
+func Error(args ...interface{}) {
+ logger.Error(args...)
+}
+
+// Errorf 格式化输出Error级别的日志
+func Errorf(format string, args ...interface{}) {
+ logger.Errorf(format, args...)
+}
+
+// Fatal 输出Fatal级别的日志
+func Fatal(args ...interface{}) {
+ logger.Fatal(args...)
+}
+
+// Fatalf 格式化输出Fatal级别的日志
+func Fatalf(format string, args ...interface{}) {
+ logger.Fatalf(format, args...)
+}
+
+// Panic 输出Panic级别的日志
+func Panic(args ...interface{}) {
+ logger.Panic(args...)
+}
+
+// Panicf 格式化输出Panic级别的日志
+func Panicf(format string, args ...interface{}) {
+ logger.Panicf(format, args...)
+}
+
+func SetLevel(level string) {
+ l, err := logrus.ParseLevel(level)
+ if err != nil {
+ logger.Warnf("设置日志等级失败:%v", err)
+ return
+ }
+ logger.Infof("日志等级已设置为:%s", level)
+ logger.SetLevel(l)
+}
+
+func SetFormatter(formatter logrus.Formatter) {
+ logger.SetFormatter(formatter)
+}
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..1faa861
--- /dev/null
+++ b/main.go
@@ -0,0 +1,68 @@
+package main
+
+import (
+ "fmt"
+ "github.com/Iceinu-Project/iceinu/log"
+ "github.com/KyokuKong/gradients"
+ "github.com/sirupsen/logrus"
+)
+
+// Iceinu的程序入口
+// 可以参照文档来对这其进行修改
+
+func main() {
+ // 定义日志格式
+ formatter := &LogFormatter{}
+ log.SetFormatter(formatter)
+ // 输出日志
+ log.Info("Hello, World!")
+}
+
+// LogFormatter 可以通过修改这个结构体的Format方法来设置你想要的日志格式
+//
+// 其本身实现了Logrus的Formatter接口
+type LogFormatter struct{}
+
+func (f *LogFormatter) Format(entry *logrus.Entry) ([]byte, error) {
+ // 根据日志级别设置不同的颜色
+ var textColor string
+ var levelColor string
+ var levelText string
+ switch entry.Level {
+ case logrus.DebugLevel, logrus.TraceLevel:
+ levelColor = gradients.DarkGreen
+ textColor = gradients.DarkGreen
+ levelText = "DEBUG"
+ case logrus.InfoLevel:
+ levelColor = gradients.DarkCyan
+ textColor = gradients.White
+ levelText = "_INFO"
+ case logrus.WarnLevel:
+ levelColor = gradients.Orange
+ textColor = gradients.DarkYellow
+ levelText = "_WARN"
+ case logrus.ErrorLevel, logrus.FatalLevel, logrus.PanicLevel:
+ levelColor = gradients.Red
+ textColor = gradients.Red
+ levelText = "ERROR"
+ default:
+ levelColor = gradients.White
+ textColor = gradients.White
+ levelText = "UNKNOWN"
+ }
+
+ // 构建日志格式,可以按需修改
+ logMsg := fmt.Sprintf(
+ "%s• %s %s[%s%s%s] %s%s\n",
+ gradients.Gray,
+ entry.Time.Format("2006-01-02 15:04:05"),
+ textColor,
+ levelColor,
+ levelText,
+ textColor,
+ entry.Message,
+ gradients.ResetColor,
+ )
+
+ return []byte(logMsg), nil
+}
diff --git a/models/event.go b/models/event.go
new file mode 100644
index 0000000..acad8b0
--- /dev/null
+++ b/models/event.go
@@ -0,0 +1,17 @@
+package models
+
+type IceinuEvent struct {
+ // 事件种类,用于确定其中承载的消息类型
+ // 0:WebSocket心跳事件
+ // 1:节点连接事件
+ // 2:节点断开事件
+ // 3:节点更新推送事件
+ // 4:节点更新请求事件
+ // 5:消息接受事件(从子节点到主节点)
+ // 6:消息处理事件(从主节点到子节点)
+ Type uint8 `json:"type"`
+ From string `json:"from"` // 消息事件来源节点ID
+ Target string `json:"target"` // 消息事件目标节点ID
+ Timestamp int64 `json:"timestamp"` // 事件推送的时间戳
+ Event interface{} `json:"event"` // 事件内容,用于承载不同类型的消息,由事件总线进行断言
+}
diff --git a/models/satori/README.md b/models/satori/README.md
new file mode 100644
index 0000000..02184f9
--- /dev/null
+++ b/models/satori/README.md
@@ -0,0 +1,9 @@
+# Satori Model
+
+Iceinu默认的消息事件接收/发送模型,基于跨平台的Satori标准,可以用于方便的实现适配不同平台的消息适配器。
+
+虽然说是基于Satori,但是对标准的一些细节进行了修改,主要是Guild之类的元素名称太神秘了(所以我至今也不理解为什么群组要叫做协会),初次之外扩展了一下Satori的一些元素,方便使用。
+
+在使用Satori Model的情况下,可以直接构建Satori XHTML风格的消息进行发送,Iceinu会自动将其解析成Satori元素切片并传递到事件系统中。
+
+## 事件类型
\ No newline at end of file
diff --git a/models/satori/satori_elements.go b/models/satori/satori_elements.go
new file mode 100644
index 0000000..7bab417
--- /dev/null
+++ b/models/satori/satori_elements.go
@@ -0,0 +1,226 @@
+package satori
+
+// ElementSatori Satori标准事件元素接口
+type ElementSatori interface {
+ GetType() string
+}
+
+type TextElement struct {
+ Text string
+}
+
+func (t *TextElement) GetType() string {
+ return "text"
+}
+
+type AtElement struct {
+ Id string
+ Name string
+ Role string
+ Type string
+}
+
+func (a *AtElement) GetType() string {
+ return "at"
+}
+
+type SharpElement struct {
+ Id string
+ Name string
+}
+
+func (s *SharpElement) GetType() string {
+ return "sharp"
+}
+
+type AElement struct {
+ Href string
+}
+
+func (a *AElement) GetType() string {
+ return "a"
+}
+
+type ImgElement struct {
+ Src string
+ Title string
+ Cache bool
+ Timeout uint32
+ Width uint32
+ Height uint32
+}
+
+func (i *ImgElement) GetType() string {
+ return "img"
+}
+
+type AudioElement struct {
+ Src string
+ Title string
+ Cache bool
+ Timeout uint32
+ Duration uint32
+ Poster string
+}
+
+func (a *AudioElement) GetType() string {
+ return "audio"
+}
+
+type VideoElement struct {
+ Src string
+ Title string
+ Cache bool
+ Timeout uint32
+ Width uint32
+ Height uint32
+ Duration uint32
+ Poster string
+}
+
+func (v *VideoElement) GetType() string {
+ return "video"
+}
+
+type FileElement struct {
+ Src string
+ Title string
+ Cache bool
+ Timeout uint32
+ Poster string
+}
+
+func (f *FileElement) GetType() string {
+ return "file"
+}
+
+type StrongElement struct {
+ Elements *[]ElementSatori
+}
+
+func (s *StrongElement) GetType() string {
+ return "strong"
+}
+
+type EmElement struct {
+ Elements *[]ElementSatori
+}
+
+func (e *EmElement) GetType() string {
+ return "em"
+}
+
+type InsElement struct {
+ Elements *[]ElementSatori
+}
+
+func (i *InsElement) GetType() string {
+ return "ins"
+}
+
+type DelElement struct {
+ Elements *[]ElementSatori
+}
+
+func (d *DelElement) GetType() string {
+ return "del"
+}
+
+type SpoilerElement struct {
+ Elements *[]ElementSatori
+}
+
+func (s *SpoilerElement) GetType() string {
+ return "spoiler"
+}
+
+type CodeElement struct {
+ Elements *[]ElementSatori
+}
+
+func (c *CodeElement) GetType() string {
+ return "code"
+}
+
+type SupElement struct {
+ Elements *[]ElementSatori
+}
+
+func (s *SupElement) GetType() string {
+ return "sup"
+}
+
+type SubElement struct {
+ Elements *[]ElementSatori
+}
+
+func (s *SubElement) GetType() string {
+ return "sub"
+}
+
+type BrElement struct {
+}
+
+func (b *BrElement) GetType() string {
+ return "br"
+}
+
+type HrElement struct {
+}
+
+func (h *HrElement) GetType() string {
+ return "hr"
+}
+
+type PElement struct {
+ Elements *[]ElementSatori
+}
+
+func (p *PElement) GetType() string {
+ return "p"
+}
+
+type MessageElement struct {
+ Id string
+ Forward bool
+ Elements *[]ElementSatori
+}
+
+func (m *MessageElement) GetType() string {
+ return "message"
+}
+
+type QuoteElement struct {
+ Id string
+ Name string
+ GroupId string
+ ChannelId string
+ Timestamp int64
+ Elements *[]ElementSatori
+}
+
+func (q *QuoteElement) GetType() string {
+ return "quote"
+}
+
+type AuthorElement struct {
+ Id string
+ Name string
+ Avatar string
+}
+
+func (a *AuthorElement) GetType() string {
+ return "author"
+}
+
+type ButtonElement struct {
+ Id string
+ Type string
+ Href string
+ Text string
+ Theme string
+}
+
+func (b *ButtonElement) GetType() string {
+ return "button"
+}
diff --git a/models/satori/satori_model.go b/models/satori/satori_model.go
new file mode 100644
index 0000000..30019ed
--- /dev/null
+++ b/models/satori/satori_model.go
@@ -0,0 +1,103 @@
+package satori
+
+import "time"
+
+// EventSatori 基于Satori标准设计的事件接收结构体,出于方便使用进行了部分修改和拓展
+type EventSatori struct {
+ EventId uint64 // 事件ID
+ EventType string // 事件类型
+ Platform string // 接收者平台名称
+ SelfId string // 接收者平台账号
+ Timestamp int64 // 事件推送的时间戳
+ Argv *Argv // 交互指令
+ Button *Button // 交互按钮
+ Channel *Channel // 事件所属的频道
+ Group *Group // 事件所属的群组
+ Login *Login // 事件的登录信息
+ Member *GroupMember // 事件的目标成员
+ Message *Message // 事件的消息
+ Operator *User // 事件的操作者
+ Role *GroupRole // 事件的目标角色
+ User *User // 事件的目标用户
+}
+
+// Channel 频道结构体
+type Channel struct {
+ Id string // 频道ID
+ Type uint8 // 频道类型, 0: 文本频道, 1: 私聊频道,2:分类频道,3:语音频道
+ Name string // 频道名称
+ ParentId string // 父频道ID
+}
+
+// Group 群组结构体
+type Group struct {
+ Id string // 群组ID
+ Name string // 群组名称
+ Avatar string // 群组头像
+ Maxcount uint32 // 群组最大人数
+ MemberCount uint32 // 群组成员数量
+}
+
+// GroupMember 群组成员结构体
+type GroupMember struct {
+ User *User
+ Nickname string
+ Avatar string
+ JoinedAt time.Time
+}
+
+// GroupRole 群组角色结构体
+type GroupRole struct {
+ Id string
+ Name string
+}
+
+// Argv 交互指令结构体
+type Argv struct {
+ Name string
+ Arguments []interface{}
+ Options interface{}
+}
+
+// Button 交互按钮结构体
+type Button struct {
+ Id string
+}
+
+// Login 登录信息结构体
+type Login struct {
+ User *User // 用户对象
+ SelfId string // 平台账号
+ Platform string // 平台名称
+ Status uint8 // 登录状态,0为离线,1为在线,2为连接中,3为断开连接,4为重新连接
+ Features []string // 平台特性列表
+ ProxyUrls []string // 代理路由列表
+}
+
+// Message 消息结构体
+type Message struct {
+ Id string
+ Content string
+ Channel *Channel
+ Group *Group
+ Member *GroupMember
+ User *User
+ CreatedAt time.Time
+ UpdatedAt time.Time
+ MessageElements *[]ElementSatori
+}
+
+// PagedList 分页列表结构体
+type PagedList struct {
+ Data interface{}
+ Next string
+}
+
+// User 用户结构体
+type User struct {
+ Id string // 用户ID
+ Name string // 用户名称
+ Nickname string // 用户昵称
+ Avatar string // 用户头像
+ IsBot bool // 是否是机器人
+}
diff --git a/models/satori/tools.go b/models/satori/tools.go
new file mode 100644
index 0000000..194b9c5
--- /dev/null
+++ b/models/satori/tools.go
@@ -0,0 +1,11 @@
+package satori
+
+// ParseSatori 从Satori标准XHTML消息中解析出Satori标准事件元素
+func ParseSatori(content string) *[]ElementSatori {
+ return nil
+}
+
+// ElementsToSatori 将Satori标准事件元素转换为Satori标准XHTML消息字符串
+func ElementsToSatori(elements *[]ElementSatori) string {
+ return ""
+}
diff --git a/static/README.md b/static/README.md
new file mode 100644
index 0000000..23da300
--- /dev/null
+++ b/static/README.md
@@ -0,0 +1,3 @@
+这个文件夹主要用来存放静态文件,比如json、xml数据,图片、音频、视频等等。
+
+因为Iceinu最后要被编译成一个二进制文件,你可以直接在使用图片时将他们用注释引用来让编译器直接把它们编译进程序二进制文件里,也可以在编译之后把static文件夹拿出来和程序放在一起。
\ No newline at end of file
From 2a14da2dd96625f5bd49d1cd8c364f74f6d29c39 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kyoku=E2=AD=90?=
<144906395+KyokuKong@users.noreply.github.com>
Date: Tue, 24 Sep 2024 02:07:58 +0800
Subject: [PATCH 3/9] =?UTF-8?q?refact=EF=BC=9A=E9=87=8D=E6=9E=84=E4=BA=8B?=
=?UTF-8?q?=E4=BB=B6=E6=80=BB=E7=BA=BF=EF=BC=8C=E5=AE=8C=E5=96=84=E4=BA=8B?=
=?UTF-8?q?=E4=BB=B6=E8=AE=BE=E8=AE=A1?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
adpters/adapter.go | 1 +
adpters/bot.go | 1 +
...77\344\270\255\351\227\264\344\273\266.md" | 30 +++
...45\345\277\227\346\240\274\345\274\217.md" | 73 ++++++
go.mod | 1 +
ice/event.go | 46 ++++
ice/event_bus.go | 226 ++++++++++++++++++
log_format.go | 54 +++++
main.go | 91 ++++---
models/event.go | 17 --
models/satori/README.md | 4 +-
models/satori/satori_model.go | 112 ++++-----
12 files changed, 534 insertions(+), 122 deletions(-)
create mode 100644 adpters/adapter.go
create mode 100644 adpters/bot.go
create mode 100644 "doc/draft/\350\207\252\345\256\232\344\271\211\344\272\213\344\273\266\346\200\273\347\272\277\344\270\255\351\227\264\344\273\266.md"
create mode 100644 "doc/draft/\350\207\252\345\256\232\344\271\211\346\227\245\345\277\227\346\240\274\345\274\217.md"
create mode 100644 ice/event.go
create mode 100644 log_format.go
delete mode 100644 models/event.go
diff --git a/adpters/adapter.go b/adpters/adapter.go
new file mode 100644
index 0000000..2ce74f6
--- /dev/null
+++ b/adpters/adapter.go
@@ -0,0 +1 @@
+package adpters
diff --git a/adpters/bot.go b/adpters/bot.go
new file mode 100644
index 0000000..2ce74f6
--- /dev/null
+++ b/adpters/bot.go
@@ -0,0 +1 @@
+package adpters
diff --git "a/doc/draft/\350\207\252\345\256\232\344\271\211\344\272\213\344\273\266\346\200\273\347\272\277\344\270\255\351\227\264\344\273\266.md" "b/doc/draft/\350\207\252\345\256\232\344\271\211\344\272\213\344\273\266\346\200\273\347\272\277\344\270\255\351\227\264\344\273\266.md"
new file mode 100644
index 0000000..f1d9880
--- /dev/null
+++ "b/doc/draft/\350\207\252\345\256\232\344\271\211\344\272\213\344\273\266\346\200\273\347\272\277\344\270\255\351\227\264\344\273\266.md"
@@ -0,0 +1,30 @@
+Iceinu的事件总线具备自定义中间件支持,允许开发者自行在事件处理前后进行一些操作,比如日志记录、性能监控等。
+
+中间件函数可以在如下情景被触发:
+
+1. 任意事件发布
+2. 指定类型事件发布
+3. 指定摘要事件发布
+4. 事件被订阅者处理
+
+Iceinu的中间件函数遵循洋葱模型,即事件发布时,中间件函数的执行顺序为:
+
+1. 事件发布前的中间件函数
+2. 事件发布者发布事件
+3. 事件发布后的中间件函数
+
+以下是一个中间件函数封包的示例:
+```go
+customPublishLogger := func(event *ice.IceinuEvent, next func(event *ice.IceinuEvent)) {
+ log.Infof("Publish event: %s", event)
+ next(event)
+ log.Infof("Event published: %s", event)
+}
+```
+
+在中间件函数中,可以通过event参数获取事件的详细信息,通过next参数控制中间件函数的执行顺序。
+
+然后可以通过对应阶段的注册函数来将中间件添加到事件总线中:
+```go
+ice.UseGlobalPublishMiddleware(customPublishLogger)
+```
\ No newline at end of file
diff --git "a/doc/draft/\350\207\252\345\256\232\344\271\211\346\227\245\345\277\227\346\240\274\345\274\217.md" "b/doc/draft/\350\207\252\345\256\232\344\271\211\346\227\245\345\277\227\346\240\274\345\274\217.md"
new file mode 100644
index 0000000..56f1785
--- /dev/null
+++ "b/doc/draft/\350\207\252\345\256\232\344\271\211\346\227\245\345\277\227\346\240\274\345\274\217.md"
@@ -0,0 +1,73 @@
+Iceinu框架内置了Logrus作为日志库,从而也自然支持了Logrus的日志格式化功能。
+
+在log包中,已经预先定义了一些常用的快捷方式,无需再手动引入Logrus。
+
+在`main`包的`log_format.go`中,是Iceinu的默认日志格式化配置,如下所示:
+
+```go
+package main
+
+import (
+ "fmt"
+ "github.com/KyokuKong/gradients"
+ "github.com/sirupsen/logrus"
+)
+
+// LogFormatter 可以通过修改这个结构体的Format方法来设置你想要的日志格式
+type LogFormatter struct{}
+
+func (f *LogFormatter) Format(entry *logrus.Entry) ([]byte, error) {
+ // 根据日志级别设置不同的颜色
+ var textColor string
+ var levelColor string
+ var levelText string
+ switch entry.Level {
+ case logrus.DebugLevel, logrus.TraceLevel:
+ levelColor = gradients.DarkGreen
+ textColor = gradients.DarkGreen
+ levelText = "DEBUG"
+ case logrus.InfoLevel:
+ levelColor = gradients.DarkCyan
+ textColor = gradients.White
+ levelText = "_INFO"
+ case logrus.WarnLevel:
+ levelColor = gradients.Orange
+ textColor = gradients.DarkYellow
+ levelText = "_WARN"
+ case logrus.ErrorLevel, logrus.FatalLevel, logrus.PanicLevel:
+ levelColor = gradients.Red
+ textColor = gradients.Red
+ levelText = "ERROR"
+ default:
+ levelColor = gradients.White
+ textColor = gradients.White
+ levelText = "UNKNOWN"
+ }
+
+ // 构建日志格式,可以按需修改
+ logMsg := fmt.Sprintf(
+ "%s• %s %s[%s%s%s] %s%s\n",
+ gradients.Gray,
+ entry.Time.Format("2006-01-02 15:04:05"),
+ textColor,
+ levelColor,
+ levelText,
+ textColor,
+ entry.Message,
+ gradients.ResetColor,
+ )
+
+ return []byte(logMsg), nil
+}
+```
+
+你可以根据自己的需求修改这个结构体的`Format`方法来设置你想要的日志格式,然后在`main`包的`main`函数中设置为全局的日志格式化器:
+
+
+```go
+// 定义日志格式
+formatter := &LogFormatter{}
+log.SetFormatter(formatter)
+```
+
+这里的`log.SetFormatter`方法是Logrus的日志格式化Formatter的一个快捷方式,在`log`包下的`logger.go`中进行定义。
\ No newline at end of file
diff --git a/go.mod b/go.mod
index 56e2ea9..633aee1 100644
--- a/go.mod
+++ b/go.mod
@@ -4,6 +4,7 @@ go 1.23.1
require (
github.com/KyokuKong/gradients v0.0.0-20240910005044-45c73fcaed90
+ github.com/google/uuid v1.6.0
github.com/sirupsen/logrus v1.9.3
)
diff --git a/ice/event.go b/ice/event.go
new file mode 100644
index 0000000..bf91068
--- /dev/null
+++ b/ice/event.go
@@ -0,0 +1,46 @@
+package ice
+
+// IceinuEvent Iceinu全局事件结构体
+//
+// 默认的全局事件总线结构体,在直接使用Iceinu框架进行二次开发时使用这个事件结构
+//
+// 引用Iceinu框架作为第三方库时如需自定义自己的事件结构体,需要自行实现事件总线
+//
+// 事件种类:
+// 0:WebSocket心跳事件
+//
+// 1:节点连接事件
+//
+// 2:节点断开事件
+//
+// 3:节点更新推送事件
+//
+// 4:节点更新请求事件
+//
+// 5:消息接受事件(从子节点到主节点)
+//
+// 6:消息处理事件(从主节点到子节点)
+type IceinuEvent struct {
+ Type uint8 `json:"type"`
+ From string `json:"from"` // 消息事件来源节点ID
+ Target string `json:"target"` // 消息事件目标节点ID
+ Timestamp int64 `json:"timestamp"` // 事件推送的时间戳
+ Summary string `json:"summary"` // 事件摘要,一般是承载的事件的事件类型,用于在事件总线层快速识别事件类型
+ Event interface{} `json:"event"` // 事件内容,用于承载不同类型的消息,使用时需要进行断言
+}
+
+type WebSocketHeartbeatEvent struct {
+ OK bool `json:"ok"`
+}
+
+type NodeConnectEvent struct {
+}
+
+type NodeDisconnectEvent struct {
+}
+
+type NodeUpdatePushEvent struct {
+}
+
+type NodeUpdateRequestEvent struct {
+}
diff --git a/ice/event_bus.go b/ice/event_bus.go
index f1c4f73..2049514 100644
--- a/ice/event_bus.go
+++ b/ice/event_bus.go
@@ -1 +1,227 @@
package ice
+
+import (
+ "github.com/google/uuid"
+ "sync"
+)
+
+var Bus *EventBus
+
+// 初始化事件总线
+func init() {
+ Bus = NewEventBus()
+}
+
+// EventHandler 事件处理函数类型
+type EventHandler func(event *IceinuEvent)
+
+// PublishMiddleware 发布中间件类型
+type PublishMiddleware func(event *IceinuEvent, next func(event *IceinuEvent))
+
+// SubscribeMiddleware 订阅中间件类型
+type SubscribeMiddleware func(event *IceinuEvent, next func(event *IceinuEvent))
+
+// 订阅结构体,包含订阅 ID 和处理函数
+type subscription struct {
+ id string
+ handler EventHandler
+}
+
+// EventBus 事件总线结构
+type EventBus struct {
+ subscribers map[uint8]map[string][]subscription // 按类型和摘要存储订阅者
+ globalPublishMWs []PublishMiddleware // 全局发布中间件
+ typePublishMWs map[uint8][]PublishMiddleware // 指定类型发布中间件
+ summaryPublishMWs map[string][]PublishMiddleware // 指定摘要发布中间件
+ subscribeMiddlewares []SubscribeMiddleware // 订阅者接收事件中间件
+ lock sync.RWMutex
+}
+
+// NewEventBus 创建新的事件总线
+func NewEventBus() *EventBus {
+ return &EventBus{
+ subscribers: make(map[uint8]map[string][]subscription),
+ typePublishMWs: make(map[uint8][]PublishMiddleware),
+ summaryPublishMWs: make(map[string][]PublishMiddleware),
+ }
+}
+
+// 生成唯一的订阅 ID
+func generateSubscriberID() string {
+ return uuid.New().String()
+}
+
+// Subscribe 订阅事件,返回订阅 ID
+func (bus *EventBus) Subscribe(eventType uint8, summary string, handler EventHandler) string {
+ bus.lock.Lock()
+ defer bus.lock.Unlock()
+
+ if bus.subscribers[eventType] == nil {
+ bus.subscribers[eventType] = make(map[string][]subscription)
+ }
+
+ subID := generateSubscriberID()
+ sub := subscription{
+ id: subID,
+ handler: handler,
+ }
+
+ bus.subscribers[eventType][summary] = append(bus.subscribers[eventType][summary], sub)
+
+ return subID
+}
+
+// Unsubscribe 取消订阅,使用订阅 ID
+func (bus *EventBus) Unsubscribe(eventType uint8, summary string, subID string) {
+ bus.lock.Lock()
+ defer bus.lock.Unlock()
+
+ subs := bus.subscribers[eventType][summary]
+ for i, sub := range subs {
+ if sub.id == subID {
+ bus.subscribers[eventType][summary] = append(subs[:i], subs[i+1:]...)
+ break
+ }
+ }
+}
+
+// GetSubscribers 获取订阅者列表,返回订阅 ID 和处理函数
+func (bus *EventBus) GetSubscribers(eventType uint8, summary string) []subscription {
+ bus.lock.RLock()
+ defer bus.lock.RUnlock()
+
+ return bus.subscribers[eventType][summary]
+}
+
+// UseGlobalPublishMiddleware 添加全局发布中间件
+func (bus *EventBus) UseGlobalPublishMiddleware(middleware PublishMiddleware) {
+ bus.lock.Lock()
+ defer bus.lock.Unlock()
+
+ bus.globalPublishMWs = append(bus.globalPublishMWs, middleware)
+}
+
+// UseTypePublishMiddleware 添加指定类型发布中间件
+func (bus *EventBus) UseTypePublishMiddleware(eventType uint8, middleware PublishMiddleware) {
+ bus.lock.Lock()
+ defer bus.lock.Unlock()
+
+ bus.typePublishMWs[eventType] = append(bus.typePublishMWs[eventType], middleware)
+}
+
+// UseSummaryPublishMiddleware 添加指定摘要发布中间件
+func (bus *EventBus) UseSummaryPublishMiddleware(summary string, middleware PublishMiddleware) {
+ bus.lock.Lock()
+ defer bus.lock.Unlock()
+
+ bus.summaryPublishMWs[summary] = append(bus.summaryPublishMWs[summary], middleware)
+}
+
+// UseSubscribeMiddleware 添加订阅者接收事件中间件
+func (bus *EventBus) UseSubscribeMiddleware(middleware SubscribeMiddleware) {
+ bus.lock.Lock()
+ defer bus.lock.Unlock()
+
+ bus.subscribeMiddlewares = append(bus.subscribeMiddlewares, middleware)
+}
+
+// Publish 发布事件
+func (bus *EventBus) Publish(event *IceinuEvent) {
+ // 最终的发布函数
+ finalPublish := func(event *IceinuEvent) {
+ bus.lock.RLock()
+ handlers := bus.collectHandlers(event)
+ bus.lock.RUnlock()
+
+ for _, handler := range handlers {
+ wrappedHandler := bus.wrapSubscribeMiddlewares(handler)
+ go wrappedHandler(event)
+ }
+ }
+
+ // 包装发布中间件
+ wrappedPublish := bus.wrapPublishMiddlewares(event, finalPublish)
+
+ // 异步执行发布函数
+ go wrappedPublish(event)
+}
+
+// 收集订阅者的处理函数
+func (bus *EventBus) collectHandlers(event *IceinuEvent) []EventHandler {
+ var handlers []EventHandler
+
+ // 收集按类型订阅的处理函数
+ if summaries, ok := bus.subscribers[event.Type]; ok {
+ // 收集按摘要订阅的处理函数
+ if subs, ok := summaries[event.Summary]; ok {
+ for _, sub := range subs {
+ handlers = append(handlers, sub.handler)
+ }
+ }
+ }
+
+ return handlers
+}
+
+// 包装发布中间件
+func (bus *EventBus) wrapPublishMiddlewares(event *IceinuEvent, finalPublish func(event *IceinuEvent)) func(event *IceinuEvent) {
+ bus.lock.RLock()
+ var middlewares []PublishMiddleware
+
+ // 添加全局发布中间件
+ middlewares = append(middlewares, bus.globalPublishMWs...)
+
+ // 添加指定类型的发布中间件
+ if mw, ok := bus.typePublishMWs[event.Type]; ok {
+ middlewares = append(middlewares, mw...)
+ }
+
+ // 添加指定摘要的发布中间件
+ if mw, ok := bus.summaryPublishMWs[event.Summary]; ok {
+ middlewares = append(middlewares, mw...)
+ }
+ bus.lock.RUnlock()
+
+ // 按顺序应用中间件
+ for i := len(middlewares) - 1; i >= 0; i-- {
+ next := finalPublish
+ mw := middlewares[i]
+ finalPublish = func(event *IceinuEvent) {
+ mw(event, next)
+ }
+ }
+ return finalPublish
+}
+
+// 包装订阅者中间件
+func (bus *EventBus) wrapSubscribeMiddlewares(handler EventHandler) EventHandler {
+ bus.lock.RLock()
+ middlewares := bus.subscribeMiddlewares
+ bus.lock.RUnlock()
+
+ // 按顺序应用中间件
+ for i := len(middlewares) - 1; i >= 0; i-- {
+ next := handler
+ mw := middlewares[i]
+ handler = func(event *IceinuEvent) {
+ mw(event, next)
+ }
+ }
+ return handler
+}
+
+func UseGlobalPublishMiddleware(middleware PublishMiddleware) {
+ Bus.UseGlobalPublishMiddleware(middleware)
+}
+
+func UseTypePublishMiddleware(eventType uint8, middleware PublishMiddleware) {
+ Bus.UseTypePublishMiddleware(eventType, middleware)
+}
+
+func UseSummaryPublishMiddleware(summary string, middleware PublishMiddleware) {
+ Bus.UseSummaryPublishMiddleware(summary, middleware)
+}
+
+func UseSubscribeMiddleware(middleware SubscribeMiddleware) {
+ Bus.UseSubscribeMiddleware(middleware)
+}
diff --git a/log_format.go b/log_format.go
new file mode 100644
index 0000000..fbf590a
--- /dev/null
+++ b/log_format.go
@@ -0,0 +1,54 @@
+package main
+
+import (
+ "fmt"
+ "github.com/KyokuKong/gradients"
+ "github.com/sirupsen/logrus"
+)
+
+// LogFormatter 可以通过修改这个结构体的Format方法来设置你想要的日志格式
+type LogFormatter struct{}
+
+func (f *LogFormatter) Format(entry *logrus.Entry) ([]byte, error) {
+ // 根据日志级别设置不同的颜色
+ var textColor string
+ var levelColor string
+ var levelText string
+ switch entry.Level {
+ case logrus.DebugLevel, logrus.TraceLevel:
+ levelColor = gradients.DarkGreen
+ textColor = gradients.DarkGreen
+ levelText = "DEBUG"
+ case logrus.InfoLevel:
+ levelColor = gradients.DarkCyan
+ textColor = gradients.White
+ levelText = "_INFO"
+ case logrus.WarnLevel:
+ levelColor = gradients.Orange
+ textColor = gradients.DarkYellow
+ levelText = "_WARN"
+ case logrus.ErrorLevel, logrus.FatalLevel, logrus.PanicLevel:
+ levelColor = gradients.Red
+ textColor = gradients.Red
+ levelText = "ERROR"
+ default:
+ levelColor = gradients.White
+ textColor = gradients.White
+ levelText = "UNKNOWN"
+ }
+
+ // 构建日志格式,可以按需修改
+ logMsg := fmt.Sprintf(
+ "%s• %s %s[%s%s%s] %s%s\n",
+ gradients.Gray,
+ entry.Time.Format("2006-01-02 15:04:05"),
+ textColor,
+ levelColor,
+ levelText,
+ textColor,
+ entry.Message,
+ gradients.ResetColor,
+ )
+
+ return []byte(logMsg), nil
+}
diff --git a/main.go b/main.go
index 1faa861..4305ef6 100644
--- a/main.go
+++ b/main.go
@@ -2,13 +2,13 @@ package main
import (
"fmt"
+ "github.com/Iceinu-Project/iceinu/ice"
"github.com/Iceinu-Project/iceinu/log"
- "github.com/KyokuKong/gradients"
- "github.com/sirupsen/logrus"
+ "time"
)
// Iceinu的程序入口
-// 可以参照文档来对这其进行修改
+// 可以参照文档来对其进行修改
func main() {
// 定义日志格式
@@ -16,53 +16,50 @@ func main() {
log.SetFormatter(formatter)
// 输出日志
log.Info("Hello, World!")
-}
+ // 创建事件总线
+ bus := ice.NewEventBus()
+
+ customPublishLogger := func(event *ice.IceinuEvent, next func(event *ice.IceinuEvent)) {
+ log.Infof("Publish event: %v", event)
+ next(event)
+ log.Infof("Event published: %v", event)
+ }
+
+ // 定义订阅者中间件:错误恢复
+ subscriberRecovery := func(event *ice.IceinuEvent, next func(event *ice.IceinuEvent)) {
+ defer func() {
+ if err := recover(); err != nil {
+ fmt.Printf("处理事件时发生错误:%v\n", err)
+ }
+ }()
+ next(event)
+ }
+
+ // 添加中间件
+ bus.UseGlobalPublishMiddleware(customPublishLogger)
+ bus.UseSubscribeMiddleware(subscriberRecovery)
-// LogFormatter 可以通过修改这个结构体的Format方法来设置你想要的日志格式
-//
-// 其本身实现了Logrus的Formatter接口
-type LogFormatter struct{}
+ // 订阅事件,获取订阅 ID
+ subID := bus.Subscribe(1, "NodeConnect", func(event *ice.IceinuEvent) {
+ fmt.Println("处理事件内容:", event.Event)
+ // 模拟错误
+ // panic("模拟的错误")
+ })
-func (f *LogFormatter) Format(entry *logrus.Entry) ([]byte, error) {
- // 根据日志级别设置不同的颜色
- var textColor string
- var levelColor string
- var levelText string
- switch entry.Level {
- case logrus.DebugLevel, logrus.TraceLevel:
- levelColor = gradients.DarkGreen
- textColor = gradients.DarkGreen
- levelText = "DEBUG"
- case logrus.InfoLevel:
- levelColor = gradients.DarkCyan
- textColor = gradients.White
- levelText = "_INFO"
- case logrus.WarnLevel:
- levelColor = gradients.Orange
- textColor = gradients.DarkYellow
- levelText = "_WARN"
- case logrus.ErrorLevel, logrus.FatalLevel, logrus.PanicLevel:
- levelColor = gradients.Red
- textColor = gradients.Red
- levelText = "ERROR"
- default:
- levelColor = gradients.White
- textColor = gradients.White
- levelText = "UNKNOWN"
+ // 发布事件
+ event := &ice.IceinuEvent{
+ Type: 1,
+ From: "node1",
+ Target: "node2",
+ Timestamp: time.Now().Unix(),
+ Summary: "NodeConnect",
+ Event: "节点连接事件",
}
+ bus.Publish(event)
- // 构建日志格式,可以按需修改
- logMsg := fmt.Sprintf(
- "%s• %s %s[%s%s%s] %s%s\n",
- gradients.Gray,
- entry.Time.Format("2006-01-02 15:04:05"),
- textColor,
- levelColor,
- levelText,
- textColor,
- entry.Message,
- gradients.ResetColor,
- )
+ // 等待异步处理完成
+ time.Sleep(1 * time.Second)
- return []byte(logMsg), nil
+ // 取消订阅
+ bus.Unsubscribe(1, "NodeConnect", subID)
}
diff --git a/models/event.go b/models/event.go
deleted file mode 100644
index acad8b0..0000000
--- a/models/event.go
+++ /dev/null
@@ -1,17 +0,0 @@
-package models
-
-type IceinuEvent struct {
- // 事件种类,用于确定其中承载的消息类型
- // 0:WebSocket心跳事件
- // 1:节点连接事件
- // 2:节点断开事件
- // 3:节点更新推送事件
- // 4:节点更新请求事件
- // 5:消息接受事件(从子节点到主节点)
- // 6:消息处理事件(从主节点到子节点)
- Type uint8 `json:"type"`
- From string `json:"from"` // 消息事件来源节点ID
- Target string `json:"target"` // 消息事件目标节点ID
- Timestamp int64 `json:"timestamp"` // 事件推送的时间戳
- Event interface{} `json:"event"` // 事件内容,用于承载不同类型的消息,由事件总线进行断言
-}
diff --git a/models/satori/README.md b/models/satori/README.md
index 02184f9..1d725c5 100644
--- a/models/satori/README.md
+++ b/models/satori/README.md
@@ -1,8 +1,8 @@
# Satori Model
-Iceinu默认的消息事件接收/发送模型,基于跨平台的Satori标准,可以用于方便的实现适配不同平台的消息适配器。
+Iceinu默认的消息事件接收/发送模型,基于跨平台的Satori标准,可以用于实现适配不同平台的消息适配器。
-虽然说是基于Satori,但是对标准的一些细节进行了修改,主要是Guild之类的元素名称太神秘了(所以我至今也不理解为什么群组要叫做协会),初次之外扩展了一下Satori的一些元素,方便使用。
+虽然说是基于Satori,但是对标准的一些细节进行了修改,主要是Guild之类的元素名称太神秘了(所以我至今也不理解为什么群组要叫做协会),除此之外扩展了一下Satori的一些元素,方便使用。
在使用Satori Model的情况下,可以直接构建Satori XHTML风格的消息进行发送,Iceinu会自动将其解析成Satori元素切片并传递到事件系统中。
diff --git a/models/satori/satori_model.go b/models/satori/satori_model.go
index 30019ed..c74b048 100644
--- a/models/satori/satori_model.go
+++ b/models/satori/satori_model.go
@@ -4,100 +4,100 @@ import "time"
// EventSatori 基于Satori标准设计的事件接收结构体,出于方便使用进行了部分修改和拓展
type EventSatori struct {
- EventId uint64 // 事件ID
- EventType string // 事件类型
- Platform string // 接收者平台名称
- SelfId string // 接收者平台账号
- Timestamp int64 // 事件推送的时间戳
- Argv *Argv // 交互指令
- Button *Button // 交互按钮
- Channel *Channel // 事件所属的频道
- Group *Group // 事件所属的群组
- Login *Login // 事件的登录信息
- Member *GroupMember // 事件的目标成员
- Message *Message // 事件的消息
- Operator *User // 事件的操作者
- Role *GroupRole // 事件的目标角色
- User *User // 事件的目标用户
+ Id uint64 `json:"id,omitempty"` // 事件ID
+ Type string `json:"type,omitempty"` // 事件类型
+ Platform string `json:"platform,omitempty"` // 接收者平台名称
+ SelfId string `json:"selfId,omitempty"` // 接收者平台账号
+ Timestamp int64 `json:"timestamp,omitempty"` // 事件推送的时间戳
+ Argv *Argv `json:"argv,omitempty"` // 交互指令
+ Button *Button `json:"button,omitempty"` // 交互按钮
+ Channel *Channel `json:"channel,omitempty"` // 事件所属的频道
+ Group *Group `json:"group,omitempty"` // 事件所属的群组
+ Login *Login `json:"login,omitempty"` // 事件的登录信息
+ Member *GroupMember `json:"member,omitempty"` // 事件的目标成员
+ Message *Message `json:"message,omitempty"` // 事件的消息
+ Operator *User `json:"operator,omitempty"` // 事件的操作者
+ Role *GroupRole `json:"role,omitempty"` // 事件的目标角色
+ User *User `json:"user,omitempty"` // 事件的目标用户
}
// Channel 频道结构体
type Channel struct {
- Id string // 频道ID
- Type uint8 // 频道类型, 0: 文本频道, 1: 私聊频道,2:分类频道,3:语音频道
- Name string // 频道名称
- ParentId string // 父频道ID
+ Id string `json:"id,omitempty"` // 频道ID
+ Type uint8 `json:"type,omitempty"` // 频道类型, 0: 文本频道, 1: 私聊频道,2:分类频道,3:语音频道
+ Name string `json:"name,omitempty"` // 频道名称
+ ParentId string `json:"parentId,omitempty"` // 父频道ID
}
// Group 群组结构体
type Group struct {
- Id string // 群组ID
- Name string // 群组名称
- Avatar string // 群组头像
- Maxcount uint32 // 群组最大人数
- MemberCount uint32 // 群组成员数量
+ Id string `json:"id,omitempty"` // 群组ID
+ Name string `json:"name,omitempty"` // 群组名称
+ Avatar string `json:"avatar,omitempty"` // 群组头像
+ Maxcount uint32 `json:"maxcount,omitempty"` // 群组最大人数
+ MemberCount uint32 `json:"memberCount,omitempty"` // 群组成员数量
}
// GroupMember 群组成员结构体
type GroupMember struct {
- User *User
- Nickname string
- Avatar string
- JoinedAt time.Time
+ User *User `json:"user,omitempty"`
+ Nickname string `json:"nickname,omitempty"`
+ Avatar string `json:"avatar,omitempty"`
+ JoinedAt time.Time `json:"joinedAt"`
}
// GroupRole 群组角色结构体
type GroupRole struct {
- Id string
- Name string
+ Id string `json:"id,omitempty"`
+ Name string `json:"name,omitempty"`
}
// Argv 交互指令结构体
type Argv struct {
- Name string
- Arguments []interface{}
- Options interface{}
+ Name string `json:"name,omitempty"`
+ Arguments []interface{} `json:"arguments,omitempty"`
+ Options interface{} `json:"options,omitempty"`
}
// Button 交互按钮结构体
type Button struct {
- Id string
+ Id string `json:"id,omitempty"`
}
// Login 登录信息结构体
type Login struct {
- User *User // 用户对象
- SelfId string // 平台账号
- Platform string // 平台名称
- Status uint8 // 登录状态,0为离线,1为在线,2为连接中,3为断开连接,4为重新连接
- Features []string // 平台特性列表
- ProxyUrls []string // 代理路由列表
+ User *User `json:"user,omitempty"` // 用户对象
+ SelfId string `json:"selfId,omitempty"` // 平台账号
+ Platform string `json:"platform,omitempty"` // 平台名称
+ Status uint8 `json:"status,omitempty"` // 登录状态,0为离线,1为在线,2为连接中,3为断开连接,4为重新连接
+ Features []string `json:"features,omitempty"` // 平台特性列表
+ ProxyUrls []string `json:"proxyUrls,omitempty"` // 代理路由列表
}
// Message 消息结构体
type Message struct {
- Id string
- Content string
- Channel *Channel
- Group *Group
- Member *GroupMember
- User *User
- CreatedAt time.Time
- UpdatedAt time.Time
- MessageElements *[]ElementSatori
+ Id string `json:"id,omitempty"`
+ Content string `json:"content,omitempty"`
+ Channel *Channel `json:"channel,omitempty"`
+ Group *Group `json:"group,omitempty"`
+ Member *GroupMember `json:"member,omitempty"`
+ User *User `json:"user,omitempty"`
+ CreatedAt time.Time `json:"createdAt"`
+ UpdatedAt time.Time `json:"updatedAt"`
+ MessageElements *[]ElementSatori `json:"messageElements,omitempty"`
}
// PagedList 分页列表结构体
type PagedList struct {
- Data interface{}
- Next string
+ Data interface{} `json:"data,omitempty"`
+ Next string `json:"next,omitempty"`
}
// User 用户结构体
type User struct {
- Id string // 用户ID
- Name string // 用户名称
- Nickname string // 用户昵称
- Avatar string // 用户头像
- IsBot bool // 是否是机器人
+ Id string `json:"id,omitempty"` // 用户ID
+ Name string `json:"name,omitempty"` // 用户名称
+ Nickname string `json:"nickname,omitempty"` // 用户昵称
+ Avatar string `json:"avatar,omitempty"` // 用户头像
+ IsBot bool `json:"isBot,omitempty"` // 是否是机器人
}
From 9eaed6b1b67ed0d4c89a8f0b9bc9acf98cb1b68b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kyoku=E2=AD=90?=
<144906395+KyokuKong@users.noreply.github.com>
Date: Tue, 24 Sep 2024 10:06:04 +0800
Subject: [PATCH 4/9] =?UTF-8?q?refact=EF=BC=9A=E7=BC=93=E5=AD=98=E7=AE=A1?=
=?UTF-8?q?=E7=90=86=E5=99=A8=E8=AE=BE=E8=AE=A1=EF=BC=8C=E9=85=8D=E7=BD=AE?=
=?UTF-8?q?=E7=AE=A1=E7=90=86=E5=99=A8=E8=AE=BE=E8=AE=A1?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
adpters/adapter.go | 2 +
cache/cache_manager.go | 105 ++++++++++++++
cache/iceinu_cache.go | 14 ++
config/iceinu_config.go | 58 ++++++++
config/manager.go | 136 ++++++++++++++++++
.../\346\225\260\346\215\256\345\272\223.md" | 7 +
go.mod | 7 +-
ice/event_bus.go | 19 +++
main.go | 56 +-------
test.go | 21 +++
10 files changed, 374 insertions(+), 51 deletions(-)
create mode 100644 cache/cache_manager.go
create mode 100644 cache/iceinu_cache.go
create mode 100644 config/iceinu_config.go
create mode 100644 config/manager.go
create mode 100644 "doc/draft/\346\225\260\346\215\256\345\272\223.md"
create mode 100644 test.go
diff --git a/adpters/adapter.go b/adpters/adapter.go
index 2ce74f6..b623ec0 100644
--- a/adpters/adapter.go
+++ b/adpters/adapter.go
@@ -1 +1,3 @@
package adpters
+
+// Iceinu的适配器接口
\ No newline at end of file
diff --git a/cache/cache_manager.go b/cache/cache_manager.go
new file mode 100644
index 0000000..cbef4dd
--- /dev/null
+++ b/cache/cache_manager.go
@@ -0,0 +1,105 @@
+package cache
+
+import (
+ "bytes"
+ "encoding/gob"
+ "github.com/Iceinu-Project/iceinu/log"
+ "github.com/coocood/freecache"
+)
+
+// IceCacheManager Iceinu的缓存管理器实例,实现了一系列的缓存管理方法,便于直接使用
+type IceCacheManager struct {
+ MaxSize uint32 // 缓存最大容量,单位为MB
+ ExpireTime uint32 // 缓存过期时间,单位为秒
+ Cache *freecache.Cache // 缓存实例
+}
+
+// NewIceCacheManager 创建新的缓存管理器实例
+func NewIceCacheManager(maxSize, expireTime uint32) *IceCacheManager {
+ log.Debugf("缓存管理器初始化完成,最大容量:%dMB,过期时间:%d秒", maxSize, expireTime)
+ return &IceCacheManager{
+ MaxSize: maxSize * 1024 * 1024,
+ ExpireTime: expireTime,
+ Cache: freecache.NewCache(int(maxSize * 1024 * 1024)),
+ }
+}
+
+// GetCache 直接获取缓存实例
+func (icm *IceCacheManager) GetCache() *freecache.Cache {
+ return icm.Cache
+}
+
+// Set 设置缓存数据
+func (icm *IceCacheManager) Set(key string, value interface{}) error {
+ // 创建一个字节缓冲区
+ var buf bytes.Buffer
+
+ // 创建一个新的编码器,并将值编码到缓冲区中
+ enc := gob.NewEncoder(&buf)
+ err := enc.Encode(value)
+ if err != nil {
+ return err
+ }
+
+ // 将序列化后的数据存储到缓存中
+ return icm.Cache.Set([]byte(key), buf.Bytes(), int(icm.ExpireTime))
+}
+
+// Get 获取缓存数据,value 必须是指针类型以便解码后进行填充
+func (icm *IceCacheManager) Get(key string, value interface{}) error {
+ // 从缓存中获取数据
+ data, err := icm.Cache.Get([]byte(key))
+ if err != nil {
+ return err
+ }
+
+ // 创建一个字节缓冲区,并使用解码器解码数据到提供的 value 中
+ buf := bytes.NewBuffer(data)
+ dec := gob.NewDecoder(buf)
+ return dec.Decode(value)
+}
+
+// Del 删除缓存数据
+func (icm *IceCacheManager) Del(key string) {
+ icm.Cache.Del([]byte(key))
+}
+
+// Clear 清空缓存
+func (icm *IceCacheManager) Clear() {
+ icm.Cache.Clear()
+}
+
+// Update 更新缓存数据
+func (icm *IceCacheManager) Update(key string, value interface{}) error {
+ // 先删除旧数据
+ icm.Del(key)
+
+ // 再设置新数据
+ return icm.Set(key, value)
+}
+
+// GetMaxSize 获取缓存最大容量
+func (icm *IceCacheManager) GetMaxSize() uint32 {
+ return icm.MaxSize
+}
+
+// GetExpireTime 获取缓存过期时间
+func (icm *IceCacheManager) GetExpireTime() uint32 {
+ return icm.ExpireTime
+}
+
+// SetWithExpire 设置缓存数据并同时指定过期时间
+func (icm *IceCacheManager) SetWithExpire(key string, value interface{}, expire int) error {
+ // 创建一个字节缓冲区
+ var buf bytes.Buffer
+
+ // 创建一个新的编码器,并将值编码到缓冲区中
+ enc := gob.NewEncoder(&buf)
+ err := enc.Encode(value)
+ if err != nil {
+ return err
+ }
+
+ // 将序列化后的数据存储到缓存中
+ return icm.Cache.Set([]byte(key), buf.Bytes(), expire)
+}
diff --git a/cache/iceinu_cache.go b/cache/iceinu_cache.go
new file mode 100644
index 0000000..1383028
--- /dev/null
+++ b/cache/iceinu_cache.go
@@ -0,0 +1,14 @@
+package cache
+
+import (
+ "github.com/Iceinu-Project/iceinu/config"
+ "github.com/Iceinu-Project/iceinu/log"
+)
+
+var IceCache *IceCacheManager
+
+// 初始化缓存管理器
+func init() {
+ log.Debugf("正在初始化内置缓存管理器...")
+ IceCache = NewIceCacheManager(config.IceConf.Cache.MaxCacheSize, config.IceConf.Cache.CacheExpire)
+}
diff --git a/config/iceinu_config.go b/config/iceinu_config.go
new file mode 100644
index 0000000..15e49a0
--- /dev/null
+++ b/config/iceinu_config.go
@@ -0,0 +1,58 @@
+package config
+
+import "github.com/Iceinu-Project/iceinu/log"
+
+var IceConf *IceinuConfig
+
+type IceinuConfig struct {
+ LogLevel string `toml:"log_level"` // 日志级别
+ Node NodeConfig `toml:"node"` // 节点配置
+ Database DatabaseConfig `toml:"database"` // 数据库配置
+ Cache CacheConfig `toml:"cache"` // 缓存配置
+}
+
+type NodeConfig struct {
+ IsEnableNode bool `toml:"is_enable_node"` // 是否启用节点连接
+ Mode string `toml:"node_mode"` // 运行模式,可选值为Dist(分布式),Static(静态集群),Dynamic(动态集群)
+ IsMaster bool `toml:"is_master"` // 是否为主节点
+ MasterURL string `toml:"master_url"` // 主节点地址
+}
+
+type DatabaseConfig struct {
+ IsEnableRemoteDatabase bool `toml:"is_enable_remote_database"` // 是否启用远程数据库
+ DatabaseType string `toml:"database_type"` // 数据库类型,可选值为MySQL,PostgreSQL
+ DatabaseURL string `toml:"database_url"` // 数据库连接地址
+}
+
+type CacheConfig struct {
+ MaxCacheSize uint32 `toml:"max_cache_size"` // 内置缓存最大容量
+ CacheExpire uint32 `toml:"cache_expire"` // 内置缓存过期时间
+}
+
+// IceConfigInit 初始化内置配置文件
+func IceConfigInit() {
+ log.Debugf("正在初始化内置配置文件...")
+ // 初始化内置配置文件
+ IceConf = &IceinuConfig{
+ LogLevel: "INFO",
+ Node: NodeConfig{
+ IsEnableNode: false,
+ Mode: "Dist",
+ IsMaster: true,
+ MasterURL: "",
+ },
+ Database: DatabaseConfig{
+ IsEnableRemoteDatabase: false,
+ DatabaseType: "PostgreSQL",
+ DatabaseURL: "",
+ },
+ Cache: CacheConfig{
+ MaxCacheSize: 100,
+ CacheExpire: 600,
+ },
+ }
+ err := ProcessConfig(IceConf, "ice_config.toml")
+ if err != nil {
+ return
+ }
+}
diff --git a/config/manager.go b/config/manager.go
new file mode 100644
index 0000000..6d6cd03
--- /dev/null
+++ b/config/manager.go
@@ -0,0 +1,136 @@
+package config
+
+import (
+ "encoding/json"
+ "github.com/Iceinu-Project/iceinu/log"
+ "os"
+
+ "github.com/pelletier/go-toml"
+)
+
+// 将结构体转换为 map
+func structToMap(v interface{}) (map[string]interface{}, error) {
+ data, err := json.Marshal(v)
+ if err != nil {
+ return nil, err
+ }
+ var m map[string]interface{}
+ err = json.Unmarshal(data, &m)
+ return m, err
+}
+
+// 读取 TOML 文件到 map
+func readTomlFileToMap(filename string) (map[string]interface{}, error) {
+ tree, err := toml.LoadFile(filename)
+ if err != nil {
+ return nil, err
+ }
+ return tree.ToMap(), nil
+}
+
+// 将 map 写入 TOML 文件
+func writeMapToTomlFile(m map[string]interface{}, filename string) error {
+ tree, err := toml.TreeFromMap(m)
+ if err != nil {
+ return err
+ }
+ tomlString := tree.String()
+ return os.WriteFile(filename, []byte(tomlString), 0644)
+}
+
+// 备份文件
+func backupFile(filename string) error {
+ input, err := os.ReadFile(filename)
+ if err != nil {
+ return err
+ }
+ backupFilename := filename + ".backup.toml"
+ return os.WriteFile(backupFilename, input, 0644)
+}
+
+// 合并两个 map,并检测是否有缺失的键
+func mergeMaps(defaultMap, fileMap map[string]interface{}) (map[string]interface{}, bool) {
+ mergedMap := make(map[string]interface{})
+ missingKeys := false
+
+ // 复制 defaultMap 的所有键值对到 mergedMap
+ for key, defVal := range defaultMap {
+ mergedMap[key] = defVal
+ }
+
+ // 用 fileMap 的值覆盖 mergedMap,并检测缺失的键
+ for key, fileVal := range fileMap {
+ if defVal, ok := defaultMap[key]; ok {
+ defSubMap, defIsMap := defVal.(map[string]interface{})
+ fileSubMap, fileIsMap := fileVal.(map[string]interface{})
+ if defIsMap && fileIsMap {
+ subMergedMap, subMissing := mergeMaps(defSubMap, fileSubMap)
+ mergedMap[key] = subMergedMap
+ if subMissing {
+ missingKeys = true
+ }
+ } else {
+ mergedMap[key] = fileVal
+ }
+ } else {
+ mergedMap[key] = fileVal
+ }
+ }
+
+ // 检查是否有缺失的键
+ for key := range defaultMap {
+ if _, ok := fileMap[key]; !ok {
+ missingKeys = true
+ break
+ }
+ }
+
+ return mergedMap, missingKeys
+}
+
+// ProcessConfig 处理配置文件,需要传入预先配置了默认值的结构体和配置文件名
+func ProcessConfig(cfg interface{}, filename string) error {
+ defaultMap, err := structToMap(cfg)
+ if err != nil {
+ return err
+ }
+
+ fileMap, err := readTomlFileToMap(filename)
+ if err != nil {
+ if os.IsNotExist(err) {
+ // 文件不存在,生成新的配置文件
+ log.Warnf("配置文件 %s 不存在,将生成新的配置文件", filename)
+ err = writeMapToTomlFile(defaultMap, filename)
+ if err != nil {
+ return err
+ }
+ } else {
+ return err
+ }
+ // 文件已生成,使用默认配置
+ return nil
+ }
+
+ mergedMap, missingKeys := mergeMaps(defaultMap, fileMap)
+ if missingKeys {
+ // 备份原始配置文件
+ log.Warnf("配置文件 %s 缺失了键,将自动备份原始配置文件并写入新的配置文件", filename)
+ err = backupFile(filename)
+ if err != nil {
+ return err
+ }
+ // 写入更新后的配置文件
+ err = writeMapToTomlFile(mergedMap, filename)
+ if err != nil {
+ return err
+ }
+ }
+
+ // 将 TOML 文件解析到配置结构体
+ data, err := os.ReadFile(filename)
+ if err != nil {
+ return err
+ }
+ err = toml.Unmarshal(data, cfg)
+ return err
+}
diff --git "a/doc/draft/\346\225\260\346\215\256\345\272\223.md" "b/doc/draft/\346\225\260\346\215\256\345\272\223.md"
new file mode 100644
index 0000000..83ac347
--- /dev/null
+++ "b/doc/draft/\346\225\260\346\215\256\345\272\223.md"
@@ -0,0 +1,7 @@
+考虑到对Bot进行二次开发时往往需要涉及到数据存储的问题,Iceinu直接提供了公用的数据库连接池,其中包含一个SQLite连接池和可选的PostgreSQL连接池。
+
+由于不想因为引入cgo而导致代码编译流程变得复杂,Iceinu使用的SQLite驱动是github.com/glebarez/sqlite,一个纯Go实现的SQLite驱动,但这可能导致无法预料的特性缺失以及相对原生SQLite的性能下降。
+
+虽然直接提供了SQLite连接但并不建议将其作为主要的数据存储,SQLite连接本身主要被框架用来存储元数据,在实际进行数据存储(尤其是启用了分布/集群模式时)应该启用Iceinu的PostgreSQL/MySQL连接作为插件的数据存储。
+
+同时Iceinu还维护了基于FreeCache的缓存数据存储池,一般有一个被用于适配器的消息数据缓存,还有一个作为框架使用的缓存。
\ No newline at end of file
diff --git a/go.mod b/go.mod
index 633aee1..3f5c26d 100644
--- a/go.mod
+++ b/go.mod
@@ -4,8 +4,13 @@ go 1.23.1
require (
github.com/KyokuKong/gradients v0.0.0-20240910005044-45c73fcaed90
+ github.com/coocood/freecache v1.2.4
github.com/google/uuid v1.6.0
+ github.com/pelletier/go-toml v1.9.5
github.com/sirupsen/logrus v1.9.3
)
-require golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
+require (
+ github.com/cespare/xxhash/v2 v2.1.2 // indirect
+ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
+)
diff --git a/ice/event_bus.go b/ice/event_bus.go
index 2049514..a14bdb4 100644
--- a/ice/event_bus.go
+++ b/ice/event_bus.go
@@ -210,18 +210,37 @@ func (bus *EventBus) wrapSubscribeMiddlewares(handler EventHandler) EventHandler
return handler
}
+// UseGlobalPublishMiddleware 添加全局发布中间件
func UseGlobalPublishMiddleware(middleware PublishMiddleware) {
Bus.UseGlobalPublishMiddleware(middleware)
}
+// UseTypePublishMiddleware 添加指定类型发布中间件
func UseTypePublishMiddleware(eventType uint8, middleware PublishMiddleware) {
Bus.UseTypePublishMiddleware(eventType, middleware)
}
+// UseSummaryPublishMiddleware 添加指定摘要发布中间件
func UseSummaryPublishMiddleware(summary string, middleware PublishMiddleware) {
Bus.UseSummaryPublishMiddleware(summary, middleware)
}
+// UseSubscribeMiddleware 添加订阅者接收事件中间件
func UseSubscribeMiddleware(middleware SubscribeMiddleware) {
Bus.UseSubscribeMiddleware(middleware)
}
+
+// Publish 用于向事件总线发布事件的快捷方式
+func Publish(event *IceinuEvent) {
+ Bus.Publish(event)
+}
+
+// Subscribe 用于从事件总线订阅事件的快捷方式
+func Subscribe(eventType uint8, summary string, handler EventHandler) string {
+ return Bus.Subscribe(eventType, summary, handler)
+}
+
+// Unsubscribe 用于从事件总线取消订阅事件的快捷方式
+func Unsubscribe(eventType uint8, summary string, subID string) {
+ Bus.Unsubscribe(eventType, summary, subID)
+}
diff --git a/main.go b/main.go
index 4305ef6..abc4723 100644
--- a/main.go
+++ b/main.go
@@ -1,10 +1,8 @@
package main
import (
- "fmt"
- "github.com/Iceinu-Project/iceinu/ice"
+ "github.com/Iceinu-Project/iceinu/config"
"github.com/Iceinu-Project/iceinu/log"
- "time"
)
// Iceinu的程序入口
@@ -14,52 +12,10 @@ func main() {
// 定义日志格式
formatter := &LogFormatter{}
log.SetFormatter(formatter)
- // 输出日志
- log.Info("Hello, World!")
- // 创建事件总线
- bus := ice.NewEventBus()
+ log.Infof("正在启动Iceinu......")
- customPublishLogger := func(event *ice.IceinuEvent, next func(event *ice.IceinuEvent)) {
- log.Infof("Publish event: %v", event)
- next(event)
- log.Infof("Event published: %v", event)
- }
-
- // 定义订阅者中间件:错误恢复
- subscriberRecovery := func(event *ice.IceinuEvent, next func(event *ice.IceinuEvent)) {
- defer func() {
- if err := recover(); err != nil {
- fmt.Printf("处理事件时发生错误:%v\n", err)
- }
- }()
- next(event)
- }
-
- // 添加中间件
- bus.UseGlobalPublishMiddleware(customPublishLogger)
- bus.UseSubscribeMiddleware(subscriberRecovery)
-
- // 订阅事件,获取订阅 ID
- subID := bus.Subscribe(1, "NodeConnect", func(event *ice.IceinuEvent) {
- fmt.Println("处理事件内容:", event.Event)
- // 模拟错误
- // panic("模拟的错误")
- })
-
- // 发布事件
- event := &ice.IceinuEvent{
- Type: 1,
- From: "node1",
- Target: "node2",
- Timestamp: time.Now().Unix(),
- Summary: "NodeConnect",
- Event: "节点连接事件",
- }
- bus.Publish(event)
-
- // 等待异步处理完成
- time.Sleep(1 * time.Second)
-
- // 取消订阅
- bus.Unsubscribe(1, "NodeConnect", subID)
+ // 初始化内置配置文件读取
+ config.IceConfigInit()
+ // 设置日志级别
+ log.SetLevel(config.IceConf.LogLevel)
}
diff --git a/test.go b/test.go
new file mode 100644
index 0000000..b51c3b2
--- /dev/null
+++ b/test.go
@@ -0,0 +1,21 @@
+package main
+
+import (
+ "github.com/Iceinu-Project/iceinu/ice"
+ "time"
+)
+
+// 这不是Iceinu的自动单元测试入口,只是一个临时的测试函数
+
+func Test() {
+ // 发布事件测试
+ // 发布事件
+ ice.Bus.Publish(&ice.IceinuEvent{
+ Type: 0,
+ From: "node0",
+ Target: "node0",
+ Timestamp: time.Now().Unix(),
+ Summary: "WebsocketHeartBeat",
+ Event: ice.WebSocketHeartbeatEvent{OK: true},
+ })
+}
From b8a1b3d787dadc9270d93d7aee33847fc65982b9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kyoku=E2=AD=90?=
<144906395+KyokuKong@users.noreply.github.com>
Date: Wed, 25 Sep 2024 14:48:56 +0800
Subject: [PATCH 5/9] =?UTF-8?q?refact=EF=BC=9A=E5=AE=8C=E5=96=84=E9=80=82?=
=?UTF-8?q?=E9=85=8D=E5=99=A8=E8=AE=BE=E8=AE=A1=EF=BC=8C=E5=BC=80=E5=A7=8B?=
=?UTF-8?q?=E5=87=86=E5=A4=87=E7=BC=96=E5=86=99LagrangeGo=E9=80=82?=
=?UTF-8?q?=E9=85=8D=E5=99=A8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
adpters/adapter.go | 38 ++++++++++++++++++-
adpters/lagrange/event_publisher.go | 22 +++++++++++
adpters/lagrange/lagrange.go | 57 +++++++++++++++++++++++++++++
adpters/lagrange/lagrange_config.go | 41 +++++++++++++++++++++
adpters/lagrange/protocol_logger.go | 50 +++++++++++++++++++++++++
cache/cache_manager.go | 2 +-
cache/iceinu_cache.go | 4 +-
config/iceinu_config.go | 3 +-
config/manager.go | 38 ++++++++++++++++++-
go.mod | 18 +++++++--
ice/Node.go | 9 +++++
log_format.go | 26 ++++++-------
main.go | 13 ++++++-
test.go | 2 +-
14 files changed, 297 insertions(+), 26 deletions(-)
create mode 100644 adpters/lagrange/event_publisher.go
create mode 100644 adpters/lagrange/lagrange.go
create mode 100644 adpters/lagrange/lagrange_config.go
create mode 100644 adpters/lagrange/protocol_logger.go
create mode 100644 ice/Node.go
diff --git a/adpters/adapter.go b/adpters/adapter.go
index b623ec0..1ce8240 100644
--- a/adpters/adapter.go
+++ b/adpters/adapter.go
@@ -1,3 +1,39 @@
package adpters
-// Iceinu的适配器接口
\ No newline at end of file
+// IceinuAdapter Iceinu的适配器接口,编写适配器时需要实现这个接口
+type IceinuAdapter interface {
+ Init() error // 初始化适配器
+ SubscribeEvents() error // 订阅事件,用于操作适配器客户端
+ Start() error // 启动适配器
+ GetAdapterInfo() *AdapterInfo // 获取适配器元信息
+ GetUserTree() *UserTree // 获取完整用户树
+}
+
+// AdapterInfo 适配器元信息
+type AdapterInfo struct {
+ Name string // 适配器名称
+ Version string // 适配器版本
+ Model string // 适配器模型
+ Platform []string // 适配器平台
+ Author []string // 适配器作者
+ License []string // 适配器许可证
+ Repo string // 适配器仓库地址
+ Introduce string // 适配器简介
+}
+
+// UserTree 用户树结构
+//
+// Iceinu由于本身设计了分布式/集群式的架构,所以需要保证各个节点不会重复处理数据,这需要维护一个用户树结构
+//
+// 简而言之,每个适配器和客户端连接时都会将客户端的频道/群组/好友信息处理成用户树结构,这个用户树结构会被上传到主节点
+//
+// 主节点会将所有适配器的用户树结构根据优先级合并成一个完整的用户树结构,广播给每个子节点,从而限制每个节点可以处理的用户范围
+//
+// 当节点创建新连接/失去可用性/更新用户数据时会重新向主节点发送用户树结构,主节点会根据用户树结构更新节点的用户范围
+type UserTree struct {
+ SelfId string // 适配器自身ID
+ Platform string // 平台
+ Users []string // 用户列表
+ Groups []string // 群组列表
+ Channels []string // 频道列表
+}
diff --git a/adpters/lagrange/event_publisher.go b/adpters/lagrange/event_publisher.go
new file mode 100644
index 0000000..80bf98e
--- /dev/null
+++ b/adpters/lagrange/event_publisher.go
@@ -0,0 +1,22 @@
+package lagrange
+
+import (
+ "github.com/Iceinu-Project/Iceinu/ice"
+ "github.com/LagrangeDev/LagrangeGo/client"
+ "github.com/LagrangeDev/LagrangeGo/message"
+ "time"
+)
+
+func EventsBinder() {
+ // 绑定私聊消息事件
+ Client.PrivateMessageEvent.Subscribe(func(client *client.QQClient, event *message.PrivateMessage) {
+ ice.Publish(&ice.IceinuEvent{
+ Type: 5,
+ From: ice.GetSelfNodeId(),
+ Target: ice.GetMasterNodeId(),
+ Timestamp: time.Now().Unix(),
+ Summary: "",
+ Event: nil,
+ })
+ })
+}
diff --git a/adpters/lagrange/lagrange.go b/adpters/lagrange/lagrange.go
new file mode 100644
index 0000000..c5143cf
--- /dev/null
+++ b/adpters/lagrange/lagrange.go
@@ -0,0 +1,57 @@
+package lagrange
+
+import (
+ "github.com/Iceinu-Project/Iceinu/adpters"
+ "github.com/LagrangeDev/LagrangeGo/client"
+ "github.com/LagrangeDev/LagrangeGo/client/auth"
+)
+
+// InfosLagrangeAdapter LagrangeGo适配器元信息
+var InfosLagrangeAdapter = adpters.AdapterInfo{
+ Name: "LagrangeGo适配器",
+ Version: "1.0.0",
+ Model: "Satori",
+ Platform: []string{"NTQQ"},
+ Author: []string{"Kyoku"},
+ License: []string{"MIT License"},
+ Repo: "https://github.com/Iceinu-Project/Iceinu",
+ Introduce: "内置LagrangeGo的NTQQ适配器,无需外置协议端程序",
+}
+
+// Client LagrangeGo客户端实例
+var Client *client.QQClient
+
+// AdapterLagrangeGo LagrangeGo适配器
+type AdapterLagrangeGo struct{}
+
+func (AdapterLagrangeGo) Init() error {
+ // 读取配置文件
+ AdapterConfigInit()
+ // 日志输出
+ appInfo := auth.AppList["linux"]["3.2.10-25765"]
+ deviceInfo := auth.NewDeviceInfo(AdapterLagrangeConf.Lagrange.Account)
+ qqClientInstance := client.NewClient(uint32(AdapterLagrangeConf.Lagrange.Account), appInfo, AdapterLagrangeConf.Lagrange.SignServer)
+ qqClientInstance.SetLogger(GetProtocolLogger())
+ qqClientInstance.UseDevice(deviceInfo)
+ //TODO implement me
+ panic("implement me")
+}
+
+func (AdapterLagrangeGo) SubscribeEvents() error {
+ //TODO implement me
+ panic("implement me")
+}
+
+func (AdapterLagrangeGo) Start() error {
+ //TODO implement me
+ panic("implement me")
+}
+
+func (AdapterLagrangeGo) GetAdapterInfo() *adpters.AdapterInfo {
+ return &InfosLagrangeAdapter
+}
+
+func (AdapterLagrangeGo) GetUserTree() *adpters.UserTree {
+ //TODO implement me
+ panic("implement me")
+}
diff --git a/adpters/lagrange/lagrange_config.go b/adpters/lagrange/lagrange_config.go
new file mode 100644
index 0000000..9c2499b
--- /dev/null
+++ b/adpters/lagrange/lagrange_config.go
@@ -0,0 +1,41 @@
+package lagrange
+
+import (
+ "github.com/Iceinu-Project/Iceinu/config"
+ "github.com/Iceinu-Project/Iceinu/log"
+)
+
+var AdapterLagrangeConf *AdapterConfig
+
+// AdapterConfig 适配器配置
+type AdapterConfig struct {
+ CacheSize int `toml:"message_cache_size"` // 消息缓存大小
+ CacheExpire int `toml:"message_cache_expire"` // 消息缓存过期时间
+ Lagrange LgrConfig `toml:"lagrange"`
+}
+
+type LgrConfig struct {
+ SignServer string `toml:"sign_server"`
+ MusicSignServer string `toml:"music_sign_server"`
+ Account int `toml:"account"`
+ Password string `toml:"password"`
+}
+
+// AdapterConfigInit 初始化适配器配置
+func AdapterConfigInit() {
+ AdapterLagrangeConf = &AdapterConfig{
+ CacheSize: 200,
+ CacheExpire: 600,
+ Lagrange: LgrConfig{
+ SignServer: "https://sign.lagrangecore.org/api/sign/25765",
+ MusicSignServer: "",
+ Account: 0,
+ Password: "",
+ },
+ }
+ err := config.ProcessConfig(AdapterLagrangeConf, "lagrange_config.toml")
+ if err != nil {
+ log.Errorf("初始化LagrangeGo适配器配置文件失败: %v", err)
+ return
+ }
+}
diff --git a/adpters/lagrange/protocol_logger.go b/adpters/lagrange/protocol_logger.go
new file mode 100644
index 0000000..9e950df
--- /dev/null
+++ b/adpters/lagrange/protocol_logger.go
@@ -0,0 +1,50 @@
+package lagrange
+
+import (
+ "fmt"
+ "github.com/Iceinu-Project/Iceinu/log"
+ "os"
+ "path"
+ "time"
+)
+
+// ProtocolLogger from https://github.com/Mrs4s/go-cqhttp/blob/a5923f179b360331786a6509eb33481e775a7bd1/cmd/gocq/main.go#L501
+type ProtocolLogger struct{}
+
+var dumpspath = "dump"
+
+const fromProtocol = "LGR | "
+
+func (p ProtocolLogger) Info(format string, arg ...any) {
+ log.Infof(fromProtocol+format, arg...)
+}
+
+func (p ProtocolLogger) Warning(format string, arg ...any) {
+ log.Warnf(fromProtocol+format, arg...)
+}
+
+func (p ProtocolLogger) Debug(format string, arg ...any) {
+ log.Debugf(fromProtocol+format, arg...)
+}
+
+func (p ProtocolLogger) Error(format string, arg ...any) {
+ log.Errorf(fromProtocol+format, arg...)
+}
+
+func (p ProtocolLogger) Dump(data []byte, format string, arg ...any) {
+ message := fmt.Sprintf(format, arg...)
+ if _, err := os.Stat(dumpspath); err != nil {
+ err = os.MkdirAll(dumpspath, 0o755)
+ if err != nil {
+ log.Errorf("出现错误 %v. 详细信息转储失败", message)
+ return
+ }
+ }
+ dumpFile := path.Join(dumpspath, fmt.Sprintf("%v.dump", time.Now().Unix()))
+ log.Errorf("出现错误 %v. 详细信息已转储至文件 %v 请连同日志提交给开发者处理", message, dumpFile)
+ _ = os.WriteFile(dumpFile, data, 0o644)
+}
+
+func GetProtocolLogger() ProtocolLogger {
+ return ProtocolLogger{}
+}
diff --git a/cache/cache_manager.go b/cache/cache_manager.go
index cbef4dd..9a456d2 100644
--- a/cache/cache_manager.go
+++ b/cache/cache_manager.go
@@ -3,7 +3,7 @@ package cache
import (
"bytes"
"encoding/gob"
- "github.com/Iceinu-Project/iceinu/log"
+ "github.com/Iceinu-Project/Iceinu/log"
"github.com/coocood/freecache"
)
diff --git a/cache/iceinu_cache.go b/cache/iceinu_cache.go
index 1383028..4e8c49b 100644
--- a/cache/iceinu_cache.go
+++ b/cache/iceinu_cache.go
@@ -1,8 +1,8 @@
package cache
import (
- "github.com/Iceinu-Project/iceinu/config"
- "github.com/Iceinu-Project/iceinu/log"
+ "github.com/Iceinu-Project/Iceinu/config"
+ "github.com/Iceinu-Project/Iceinu/log"
)
var IceCache *IceCacheManager
diff --git a/config/iceinu_config.go b/config/iceinu_config.go
index 15e49a0..ecb5034 100644
--- a/config/iceinu_config.go
+++ b/config/iceinu_config.go
@@ -1,6 +1,6 @@
package config
-import "github.com/Iceinu-Project/iceinu/log"
+import "github.com/Iceinu-Project/Iceinu/log"
var IceConf *IceinuConfig
@@ -53,6 +53,7 @@ func IceConfigInit() {
}
err := ProcessConfig(IceConf, "ice_config.toml")
if err != nil {
+ log.Errorf("初始化内置配置文件失败: %v", err)
return
}
}
diff --git a/config/manager.go b/config/manager.go
index 6d6cd03..0f58db1 100644
--- a/config/manager.go
+++ b/config/manager.go
@@ -2,8 +2,10 @@ package config
import (
"encoding/json"
- "github.com/Iceinu-Project/iceinu/log"
+ "github.com/Iceinu-Project/Iceinu/log"
"os"
+ "reflect"
+ "strings"
"github.com/pelletier/go-toml"
)
@@ -19,6 +21,34 @@ func structToMap(v interface{}) (map[string]interface{}, error) {
return m, err
}
+// structToMapWithTags converts a struct to a map with tag keys, handling nested structs
+func structToMapWithTags(data interface{}) (map[string]interface{}, error) {
+ result := make(map[string]interface{})
+ val := reflect.ValueOf(data).Elem()
+ typ := val.Type()
+
+ for i := 0; i < val.NumField(); i++ {
+ field := val.Field(i)
+ fieldType := typ.Field(i)
+ tag := fieldType.Tag.Get("toml")
+ if tag == "" {
+ tag = strings.ToLower(fieldType.Name)
+ }
+
+ if field.Kind() == reflect.Struct {
+ nestedMap, err := structToMapWithTags(field.Addr().Interface())
+ if err != nil {
+ return nil, err
+ }
+ result[tag] = nestedMap
+ } else {
+ result[tag] = field.Interface()
+ }
+ }
+
+ return result, nil
+}
+
// 读取 TOML 文件到 map
func readTomlFileToMap(filename string) (map[string]interface{}, error) {
tree, err := toml.LoadFile(filename)
@@ -90,7 +120,7 @@ func mergeMaps(defaultMap, fileMap map[string]interface{}) (map[string]interface
// ProcessConfig 处理配置文件,需要传入预先配置了默认值的结构体和配置文件名
func ProcessConfig(cfg interface{}, filename string) error {
- defaultMap, err := structToMap(cfg)
+ defaultMap, err := structToMapWithTags(cfg)
if err != nil {
return err
}
@@ -131,6 +161,10 @@ func ProcessConfig(cfg interface{}, filename string) error {
if err != nil {
return err
}
+ // 打印读取到的 TOML 数据
+ // println("读取到的 TOML 数据:", string(data))
+
err = toml.Unmarshal(data, cfg)
+
return err
}
diff --git a/go.mod b/go.mod
index 3f5c26d..d983ac5 100644
--- a/go.mod
+++ b/go.mod
@@ -1,9 +1,10 @@
-module github.com/Iceinu-Project/iceinu
+module github.com/Iceinu-Project/Iceinu
go 1.23.1
require (
- github.com/KyokuKong/gradients v0.0.0-20240910005044-45c73fcaed90
+ github.com/Iceinu-Project/IceGradient v1.0.3
+ github.com/LagrangeDev/LagrangeGo v0.0.0-20240924074914-bdd9093379b1
github.com/coocood/freecache v1.2.4
github.com/google/uuid v1.6.0
github.com/pelletier/go-toml v1.9.5
@@ -11,6 +12,17 @@ require (
)
require (
+ github.com/RomiChan/protobuf v0.1.1-0.20230204044148-2ed269a2e54d // indirect
+ github.com/RomiChan/syncx v0.0.0-20240418144900-b7402ffdebc7 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
- golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
+ github.com/fumiama/gofastTEA v0.0.10 // indirect
+ github.com/fumiama/imgsz v0.0.4 // indirect
+ github.com/pkg/errors v0.9.1 // indirect
+ github.com/tidwall/gjson v1.17.1 // indirect
+ github.com/tidwall/match v1.1.1 // indirect
+ github.com/tidwall/pretty v1.2.0 // indirect
+ golang.org/x/image v0.18.0 // indirect
+ golang.org/x/net v0.25.0 // indirect
+ golang.org/x/sync v0.7.0 // indirect
+ golang.org/x/sys v0.20.0 // indirect
)
diff --git a/ice/Node.go b/ice/Node.go
new file mode 100644
index 0000000..e759f02
--- /dev/null
+++ b/ice/Node.go
@@ -0,0 +1,9 @@
+package ice
+
+func GetSelfNodeId() string {
+ return "0"
+}
+
+func GetMasterNodeId() string {
+ return "0"
+}
diff --git a/log_format.go b/log_format.go
index fbf590a..b5890d3 100644
--- a/log_format.go
+++ b/log_format.go
@@ -2,7 +2,7 @@ package main
import (
"fmt"
- "github.com/KyokuKong/gradients"
+ "github.com/Iceinu-Project/IceGradient"
"github.com/sirupsen/logrus"
)
@@ -16,38 +16,38 @@ func (f *LogFormatter) Format(entry *logrus.Entry) ([]byte, error) {
var levelText string
switch entry.Level {
case logrus.DebugLevel, logrus.TraceLevel:
- levelColor = gradients.DarkGreen
- textColor = gradients.DarkGreen
+ levelColor = gradient.DarkGreen
+ textColor = gradient.DarkGreen
levelText = "DEBUG"
case logrus.InfoLevel:
- levelColor = gradients.DarkCyan
- textColor = gradients.White
+ levelColor = gradient.DarkCyan
+ textColor = gradient.Reset
levelText = "_INFO"
case logrus.WarnLevel:
- levelColor = gradients.Orange
- textColor = gradients.DarkYellow
+ levelColor = gradient.Orange
+ textColor = gradient.DarkOrange
levelText = "_WARN"
case logrus.ErrorLevel, logrus.FatalLevel, logrus.PanicLevel:
- levelColor = gradients.Red
- textColor = gradients.Red
+ levelColor = gradient.Red
+ textColor = gradient.Red
levelText = "ERROR"
default:
- levelColor = gradients.White
- textColor = gradients.White
+ levelColor = gradient.White
+ textColor = gradient.White
levelText = "UNKNOWN"
}
// 构建日志格式,可以按需修改
logMsg := fmt.Sprintf(
"%s• %s %s[%s%s%s] %s%s\n",
- gradients.Gray,
+ gradient.Gray,
entry.Time.Format("2006-01-02 15:04:05"),
textColor,
levelColor,
levelText,
textColor,
entry.Message,
- gradients.ResetColor,
+ gradient.Reset,
)
return []byte(logMsg), nil
diff --git a/main.go b/main.go
index abc4723..50d223e 100644
--- a/main.go
+++ b/main.go
@@ -1,8 +1,9 @@
package main
import (
- "github.com/Iceinu-Project/iceinu/config"
- "github.com/Iceinu-Project/iceinu/log"
+ "github.com/Iceinu-Project/IceGradient"
+ "github.com/Iceinu-Project/Iceinu/config"
+ "github.com/Iceinu-Project/Iceinu/log"
)
// Iceinu的程序入口
@@ -18,4 +19,12 @@ func main() {
config.IceConfigInit()
// 设置日志级别
log.SetLevel(config.IceConf.LogLevel)
+ log.Debugf("调试模式已启用")
+ // 输出欢迎日志
+ log.Infof("欢迎使用🧊" + gradient.Bold +
+ gradient.GradientText("氷犬 Iceinu Bot", "#00d2ff", "#3a7bd5") + gradient.DarkGray + " | " +
+ gradient.RGBToANSI(255, 255, 255) +
+ gradient.GradientBackgroundText(" 通用的模块化 Go 聊天机器人框架 ", "#00d2ff", "#3a7bd5") +
+ gradient.Reset)
+ log.Infof("当前版本: " + gradient.Cyan + "β0.1.3")
}
diff --git a/test.go b/test.go
index b51c3b2..acb953d 100644
--- a/test.go
+++ b/test.go
@@ -1,7 +1,7 @@
package main
import (
- "github.com/Iceinu-Project/iceinu/ice"
+ "github.com/Iceinu-Project/Iceinu/ice"
"time"
)
From d0086b3ce3c969ad026cdf035281d758912f9140 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kyoku=E2=AD=90?=
<144906395+KyokuKong@users.noreply.github.com>
Date: Thu, 26 Sep 2024 00:39:04 +0800
Subject: [PATCH 6/9] =?UTF-8?q?refact=EF=BC=9A=E8=87=AA=E5=8A=A8=E7=94=9F?=
=?UTF-8?q?=E6=88=90=E8=8A=82=E7=82=B9=E4=BF=A1=E6=81=AF=EF=BC=8C=E6=95=B0?=
=?UTF-8?q?=E6=8D=AE=E5=BA=93=E8=BF=9E=E6=8E=A5=E6=B1=A0=E9=85=8D=E7=BD=AE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
{adpters => adapters}/adapter.go | 2 +-
adapters/bot.go | 1 +
adapters/lagrange/README.md | 3 +
.../lagrange/event_publisher.go | 0
{adpters => adapters}/lagrange/lagrange.go | 8 +--
.../lagrange/lagrange_config.go | 0
.../lagrange/protocol_logger.go | 0
adapters/onebot/README.md | 1 +
adapters/satori/README.md | 1 +
adpters/bot.go | 1 -
config/iceinu_config.go | 6 ++
go.mod | 20 +++++-
ice/Node.go | 9 ---
ice/database_manager.go | 61 ++++++++++++++++++
ice/init.go | 1 +
ice/node.go | 18 ++++++
ice/tables.go | 26 ++++++++
iceinu.db | Bin 0 -> 24576 bytes
log/logger.go | 4 ++
log_format.go | 8 +--
main.go | 3 +
21 files changed, 152 insertions(+), 21 deletions(-)
rename {adpters => adapters}/adapter.go (99%)
create mode 100644 adapters/bot.go
create mode 100644 adapters/lagrange/README.md
rename {adpters => adapters}/lagrange/event_publisher.go (100%)
rename {adpters => adapters}/lagrange/lagrange.go (86%)
rename {adpters => adapters}/lagrange/lagrange_config.go (100%)
rename {adpters => adapters}/lagrange/protocol_logger.go (100%)
create mode 100644 adapters/onebot/README.md
create mode 100644 adapters/satori/README.md
delete mode 100644 adpters/bot.go
delete mode 100644 ice/Node.go
create mode 100644 ice/database_manager.go
create mode 100644 ice/init.go
create mode 100644 ice/node.go
create mode 100644 ice/tables.go
create mode 100644 iceinu.db
diff --git a/adpters/adapter.go b/adapters/adapter.go
similarity index 99%
rename from adpters/adapter.go
rename to adapters/adapter.go
index 1ce8240..f2a3d94 100644
--- a/adpters/adapter.go
+++ b/adapters/adapter.go
@@ -1,4 +1,4 @@
-package adpters
+package adapters
// IceinuAdapter Iceinu的适配器接口,编写适配器时需要实现这个接口
type IceinuAdapter interface {
diff --git a/adapters/bot.go b/adapters/bot.go
new file mode 100644
index 0000000..5f9bc45
--- /dev/null
+++ b/adapters/bot.go
@@ -0,0 +1 @@
+package adapters
diff --git a/adapters/lagrange/README.md b/adapters/lagrange/README.md
new file mode 100644
index 0000000..84768ef
--- /dev/null
+++ b/adapters/lagrange/README.md
@@ -0,0 +1,3 @@
+# LagrangeGo适配器
+
+Iceinu内置的NTQQ适配器,详细文档参见:https://iceinu-project.github.io/adapters/lagrange_go.html
\ No newline at end of file
diff --git a/adpters/lagrange/event_publisher.go b/adapters/lagrange/event_publisher.go
similarity index 100%
rename from adpters/lagrange/event_publisher.go
rename to adapters/lagrange/event_publisher.go
diff --git a/adpters/lagrange/lagrange.go b/adapters/lagrange/lagrange.go
similarity index 86%
rename from adpters/lagrange/lagrange.go
rename to adapters/lagrange/lagrange.go
index c5143cf..563a811 100644
--- a/adpters/lagrange/lagrange.go
+++ b/adapters/lagrange/lagrange.go
@@ -1,13 +1,13 @@
package lagrange
import (
- "github.com/Iceinu-Project/Iceinu/adpters"
+ "github.com/Iceinu-Project/Iceinu/adapters"
"github.com/LagrangeDev/LagrangeGo/client"
"github.com/LagrangeDev/LagrangeGo/client/auth"
)
// InfosLagrangeAdapter LagrangeGo适配器元信息
-var InfosLagrangeAdapter = adpters.AdapterInfo{
+var InfosLagrangeAdapter = adapters.AdapterInfo{
Name: "LagrangeGo适配器",
Version: "1.0.0",
Model: "Satori",
@@ -47,11 +47,11 @@ func (AdapterLagrangeGo) Start() error {
panic("implement me")
}
-func (AdapterLagrangeGo) GetAdapterInfo() *adpters.AdapterInfo {
+func (AdapterLagrangeGo) GetAdapterInfo() *adapters.AdapterInfo {
return &InfosLagrangeAdapter
}
-func (AdapterLagrangeGo) GetUserTree() *adpters.UserTree {
+func (AdapterLagrangeGo) GetUserTree() *adapters.UserTree {
//TODO implement me
panic("implement me")
}
diff --git a/adpters/lagrange/lagrange_config.go b/adapters/lagrange/lagrange_config.go
similarity index 100%
rename from adpters/lagrange/lagrange_config.go
rename to adapters/lagrange/lagrange_config.go
diff --git a/adpters/lagrange/protocol_logger.go b/adapters/lagrange/protocol_logger.go
similarity index 100%
rename from adpters/lagrange/protocol_logger.go
rename to adapters/lagrange/protocol_logger.go
diff --git a/adapters/onebot/README.md b/adapters/onebot/README.md
new file mode 100644
index 0000000..e6612f8
--- /dev/null
+++ b/adapters/onebot/README.md
@@ -0,0 +1 @@
+饼先画上再说,暂时先主要支持LagrangeGo适配器和Satori适配器的接入,后续再逐步完善其他适配器的接入。
diff --git a/adapters/satori/README.md b/adapters/satori/README.md
new file mode 100644
index 0000000..e6612f8
--- /dev/null
+++ b/adapters/satori/README.md
@@ -0,0 +1 @@
+饼先画上再说,暂时先主要支持LagrangeGo适配器和Satori适配器的接入,后续再逐步完善其他适配器的接入。
diff --git a/adpters/bot.go b/adpters/bot.go
deleted file mode 100644
index 2ce74f6..0000000
--- a/adpters/bot.go
+++ /dev/null
@@ -1 +0,0 @@
-package adpters
diff --git a/config/iceinu_config.go b/config/iceinu_config.go
index ecb5034..12aa85f 100644
--- a/config/iceinu_config.go
+++ b/config/iceinu_config.go
@@ -19,6 +19,9 @@ type NodeConfig struct {
}
type DatabaseConfig struct {
+ MaxIdleConns int `toml:"max_idle_conns"` // 数据库连接池最大空闲连接数
+ MaxOpenConns int `toml:"max_open_conns"` // 数据库连接池最大连接数
+ ConnMaxLifetime int `toml:"conn_max_lifetime"` // 数据库连接最大生命周期,单位为分钟
IsEnableRemoteDatabase bool `toml:"is_enable_remote_database"` // 是否启用远程数据库
DatabaseType string `toml:"database_type"` // 数据库类型,可选值为MySQL,PostgreSQL
DatabaseURL string `toml:"database_url"` // 数据库连接地址
@@ -42,6 +45,9 @@ func IceConfigInit() {
MasterURL: "",
},
Database: DatabaseConfig{
+ MaxIdleConns: 10,
+ MaxOpenConns: 100,
+ ConnMaxLifetime: 60,
IsEnableRemoteDatabase: false,
DatabaseType: "PostgreSQL",
DatabaseURL: "",
diff --git a/go.mod b/go.mod
index d983ac5..f3d7d28 100644
--- a/go.mod
+++ b/go.mod
@@ -6,23 +6,39 @@ require (
github.com/Iceinu-Project/IceGradient v1.0.3
github.com/LagrangeDev/LagrangeGo v0.0.0-20240924074914-bdd9093379b1
github.com/coocood/freecache v1.2.4
+ github.com/glebarez/sqlite v1.11.0
github.com/google/uuid v1.6.0
github.com/pelletier/go-toml v1.9.5
github.com/sirupsen/logrus v1.9.3
+ gorm.io/driver/sqlite v1.5.6
+ gorm.io/gorm v1.25.12
)
require (
+ github.com/Iceinu-Project/IceLogrusEnhance v1.0.1 // indirect
github.com/RomiChan/protobuf v0.1.1-0.20230204044148-2ed269a2e54d // indirect
github.com/RomiChan/syncx v0.0.0-20240418144900-b7402ffdebc7 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
+ github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fumiama/gofastTEA v0.0.10 // indirect
github.com/fumiama/imgsz v0.0.4 // indirect
+ github.com/glebarez/go-sqlite v1.21.2 // indirect
+ github.com/jinzhu/inflection v1.0.0 // indirect
+ github.com/jinzhu/now v1.1.5 // indirect
+ github.com/mattn/go-isatty v0.0.20 // indirect
+ github.com/mattn/go-sqlite3 v1.14.23 // indirect
github.com/pkg/errors v0.9.1 // indirect
+ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/tidwall/gjson v1.17.1 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
golang.org/x/image v0.18.0 // indirect
golang.org/x/net v0.25.0 // indirect
- golang.org/x/sync v0.7.0 // indirect
- golang.org/x/sys v0.20.0 // indirect
+ golang.org/x/sync v0.8.0 // indirect
+ golang.org/x/sys v0.25.0 // indirect
+ golang.org/x/text v0.18.0 // indirect
+ modernc.org/libc v1.22.5 // indirect
+ modernc.org/mathutil v1.5.0 // indirect
+ modernc.org/memory v1.5.0 // indirect
+ modernc.org/sqlite v1.23.1 // indirect
)
diff --git a/ice/Node.go b/ice/Node.go
deleted file mode 100644
index e759f02..0000000
--- a/ice/Node.go
+++ /dev/null
@@ -1,9 +0,0 @@
-package ice
-
-func GetSelfNodeId() string {
- return "0"
-}
-
-func GetMasterNodeId() string {
- return "0"
-}
diff --git a/ice/database_manager.go b/ice/database_manager.go
new file mode 100644
index 0000000..d88a2f3
--- /dev/null
+++ b/ice/database_manager.go
@@ -0,0 +1,61 @@
+package ice
+
+import (
+ icelogrus "github.com/Iceinu-Project/IceLogrusEnhance"
+ "github.com/Iceinu-Project/Iceinu/config"
+ "github.com/Iceinu-Project/Iceinu/log"
+ "github.com/glebarez/sqlite"
+ "gorm.io/gorm"
+ "gorm.io/gorm/logger"
+ "time"
+)
+
+var LocalDatabase *gorm.DB // 本地数据库连接
+var RemoteDatabase *gorm.DB // 远程数据库连接
+
+func InitLocalDatabase() {
+ // 尝试和本地数据库建立连接
+ gormLogger := icelogrus.NewGormLogrusLogger(log.GetLogger())
+ gormLogger.LogMode(logger.Info)
+ db, err := gorm.Open(sqlite.Open("iceinu.db"), &gorm.Config{
+ Logger: gormLogger,
+ })
+ if err != nil {
+ log.Panic("与本地SQLite数据库建立连接失败: ", err, "请检查文件系统权限!")
+ }
+ db.Session(&gorm.Session{Logger: gormLogger})
+ // 自动迁移数据库结构
+ err = db.AutoMigrate(&IceinuNodeData{}, &IceinuPluginList{})
+ if err != nil {
+ log.Panic("自动迁移数据库结构失败: ", err)
+ }
+ // 查询是否为第一次进行初始化节点数据
+ var count int64
+ db.Model(&IceinuNodeData{}).Count(&count)
+ if count == 0 {
+ log.Infof("检测到数据库中没有节点数据,正在初始化节点数据...")
+ // 如果是第一次初始化,插入一个节点数据
+ NodeId = GenerateNodeId()
+ db.Create(&IceinuNodeData{
+ NodeId: NodeId,
+ AdapterModel: "satori",
+ })
+ } else {
+ // 否则读取节点数据
+ var nodeData IceinuNodeData
+ db.First(&nodeData)
+ // 更新全局NodeId
+ NodeId = nodeData.NodeId
+ }
+ // 设置数据库连接池参数
+ sqlDB, err := db.DB()
+ if err != nil {
+ log.Panic("获取数据库连接池失败: ", err)
+ }
+ sqlDB.SetMaxIdleConns(config.IceConf.Database.MaxIdleConns)
+ sqlDB.SetMaxOpenConns(config.IceConf.Database.MaxOpenConns)
+ sqlDB.SetConnMaxLifetime(time.Duration(config.IceConf.Database.ConnMaxLifetime) * time.Minute)
+
+ // 设置本地数据库连接
+ LocalDatabase = db
+}
diff --git a/ice/init.go b/ice/init.go
new file mode 100644
index 0000000..f1c4f73
--- /dev/null
+++ b/ice/init.go
@@ -0,0 +1 @@
+package ice
diff --git a/ice/node.go b/ice/node.go
new file mode 100644
index 0000000..bf99c36
--- /dev/null
+++ b/ice/node.go
@@ -0,0 +1,18 @@
+package ice
+
+import "github.com/google/uuid"
+
+var NodeId string
+
+func GetSelfNodeId() string {
+ return "0"
+}
+
+func GetMasterNodeId() string {
+ return "0"
+}
+
+// GenerateNodeId 生成一个新的节点ID,节点ID是一个字符串形式的UUID
+func GenerateNodeId() string {
+ return uuid.NewString()
+}
diff --git a/ice/tables.go b/ice/tables.go
new file mode 100644
index 0000000..4b5a266
--- /dev/null
+++ b/ice/tables.go
@@ -0,0 +1,26 @@
+package ice
+
+import (
+ "gorm.io/gorm"
+ "time"
+)
+
+// IceinuNodeData 氷犬的节点元信息数据表,框架启动时自动初始化
+type IceinuNodeData struct {
+ gorm.Model
+ NodeId string // 节点ID,一般是UUID,每一个节点只会有一个UUID,除非数据被重置
+ AdapterModel string // 适配器模型
+}
+
+// IceinuPluginList 氷犬的插件信息数据表,用于存储插件的启用状态,并在各个节点之间同步
+type IceinuPluginList struct {
+ PluginId string `gorm:"primaryKey"` // 插件ID,一般是名字
+ IsEnabled bool // 是否启用
+ UpdateTime time.Time // 操作时间
+ IsWhiteList bool // 是否是白名单模式,这个模式会反转禁用逻辑
+ BanUsers string // 对哪些用户禁用该插件的触发器
+ BanGroups string // 对哪些群禁用该插件的触发器
+ BanChannels string // 对哪些频道禁用该插件的触发器
+ BanNodes string // 对哪些节点禁用该插件
+ IsDetectUpdate bool // 是否检测更新
+}
diff --git a/iceinu.db b/iceinu.db
new file mode 100644
index 0000000000000000000000000000000000000000..47a67404d44ce354f3c17d4c348ee449f7f46105
GIT binary patch
literal 24576
zcmeI(Jx|*}7zgk(38YkzRO%3QvpNxhk@H4KmNsoYAV{DzNR2w!hkd|FjuY&&N=NA2
zZ^Z}b=O{}*K!@JtJyDcSWkCOvlh}{#d-*-jVkPN+7L
zDPL>yHEIdDqhh0`jdqrNCdux_ug!FYtZv+s%-P-Sk%6M6ZG6?++{q7?VcZB^1$|aB%%eO)%T}Xy=CdG`nw}bcNSdX
z=v&UP6Ye>>U^imN;gQV(HHwS`afgN6-ErccM+LJ5?=VMKyK1k0?bj>$bk(23)aBI
zT3ySwiyGm9$8Mr)Urmq3qT4^V7bw{I1wr*#_)KNwHl0Xi4-M@$-|v8XH={n(Ol@gF
z{ZS{iOFh~XEVMf^HU6OXa+@-V
zTIL6l8yW;4009U<00Izz00bZa0SG_<0*@hZt))n#LC#(k%tFa9t43i@H_ck1QY+_o
z_sga7er4O-ubJk*&YLu!ukN{~Q7L*wqh#hwMwOQ2+g`OSyD7NxJ))u;^1<`}1Iau*
z#sV=01Rwwb2tWV=5P$##AOHafKmY>&S>UCXFxKaPuJBwAs%8EVxuHP-0uX=z1Rwwb
z2tWV=5P$##An*hQ5(%=dzW
Date: Thu, 26 Sep 2024 00:40:09 +0800
Subject: [PATCH 7/9] Delete iceinu.db
---
iceinu.db | Bin 24576 -> 0 bytes
1 file changed, 0 insertions(+), 0 deletions(-)
delete mode 100644 iceinu.db
diff --git a/iceinu.db b/iceinu.db
deleted file mode 100644
index 47a67404d44ce354f3c17d4c348ee449f7f46105..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 24576
zcmeI(Jx|*}7zgk(38YkzRO%3QvpNxhk@H4KmNsoYAV{DzNR2w!hkd|FjuY&&N=NA2
zZ^Z}b=O{}*K!@JtJyDcSWkCOvlh}{#d-*-jVkPN+7L
zDPL>yHEIdDqhh0`jdqrNCdux_ug!FYtZv+s%-P-Sk%6M6ZG6?++{q7?VcZB^1$|aB%%eO)%T}Xy=CdG`nw}bcNSdX
z=v&UP6Ye>>U^imN;gQV(HHwS`afgN6-ErccM+LJ5?=VMKyK1k0?bj>$bk(23)aBI
zT3ySwiyGm9$8Mr)Urmq3qT4^V7bw{I1wr*#_)KNwHl0Xi4-M@$-|v8XH={n(Ol@gF
z{ZS{iOFh~XEVMf^HU6OXa+@-V
zTIL6l8yW;4009U<00Izz00bZa0SG_<0*@hZt))n#LC#(k%tFa9t43i@H_ck1QY+_o
z_sga7er4O-ubJk*&YLu!ukN{~Q7L*wqh#hwMwOQ2+g`OSyD7NxJ))u;^1<`}1Iau*
z#sV=01Rwwb2tWV=5P$##AOHafKmY>&S>UCXFxKaPuJBwAs%8EVxuHP-0uX=z1Rwwb
z2tWV=5P$##An*hQ5(%=dzW
Date: Fri, 27 Sep 2024 01:41:24 +0800
Subject: [PATCH 8/9] =?UTF-8?q?refact=EF=BC=9A=E5=87=86=E5=A4=87=E5=BC=80?=
=?UTF-8?q?=E5=A7=8B=E5=AE=8C=E5=96=84=E9=80=82=E9=85=8D=E5=99=A8=E8=AE=BE?=
=?UTF-8?q?=E8=AE=A1?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.gitignore | 1 +
adapters/lagrange/event_publisher.go | 3 +-
adapters/lagrange/lagrange.go | 79 +++++++++++++++++++++++++---
ice/database_manager.go | 6 +--
ice/node.go | 28 ++++++++--
main.go | 7 ++-
middlewares.go | 6 +++
7 files changed, 115 insertions(+), 15 deletions(-)
create mode 100644 middlewares.go
diff --git a/.gitignore b/.gitignore
index 7397e03..6e5e379 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,3 +30,4 @@ sig.bin
qrcode.png
signature.bin
*.toml
+*.db
diff --git a/adapters/lagrange/event_publisher.go b/adapters/lagrange/event_publisher.go
index 80bf98e..48fada5 100644
--- a/adapters/lagrange/event_publisher.go
+++ b/adapters/lagrange/event_publisher.go
@@ -2,6 +2,7 @@ package lagrange
import (
"github.com/Iceinu-Project/Iceinu/ice"
+ "github.com/Iceinu-Project/Iceinu/models/satori"
"github.com/LagrangeDev/LagrangeGo/client"
"github.com/LagrangeDev/LagrangeGo/message"
"time"
@@ -16,7 +17,7 @@ func EventsBinder() {
Target: ice.GetMasterNodeId(),
Timestamp: time.Now().Unix(),
Summary: "",
- Event: nil,
+ Event: &satori.EventSatori{},
})
})
}
diff --git a/adapters/lagrange/lagrange.go b/adapters/lagrange/lagrange.go
index 563a811..97ef4c8 100644
--- a/adapters/lagrange/lagrange.go
+++ b/adapters/lagrange/lagrange.go
@@ -2,8 +2,11 @@ package lagrange
import (
"github.com/Iceinu-Project/Iceinu/adapters"
+ "github.com/Iceinu-Project/Iceinu/log"
"github.com/LagrangeDev/LagrangeGo/client"
"github.com/LagrangeDev/LagrangeGo/client/auth"
+ "github.com/sirupsen/logrus"
+ "os"
)
// InfosLagrangeAdapter LagrangeGo适配器元信息
@@ -33,18 +36,56 @@ func (AdapterLagrangeGo) Init() error {
qqClientInstance := client.NewClient(uint32(AdapterLagrangeConf.Lagrange.Account), appInfo, AdapterLagrangeConf.Lagrange.SignServer)
qqClientInstance.SetLogger(GetProtocolLogger())
qqClientInstance.UseDevice(deviceInfo)
- //TODO implement me
- panic("implement me")
+
+ // 尝试读取签名文件
+ data, err := os.ReadFile("signature.bin")
+ if err != nil {
+ logrus.Warnln("读取签名文件时发生错误:", err)
+ } else {
+ sig, err := auth.UnmarshalSigInfo(data, true)
+ if err != nil {
+ logrus.Warnln("加载签名文件时发生错误:", err)
+ } else {
+ qqClientInstance.UseSig(sig)
+ }
+ }
+ // 保存Client实例
+ Client = qqClientInstance
+ return nil
}
func (AdapterLagrangeGo) SubscribeEvents() error {
- //TODO implement me
- panic("implement me")
+ EventsBinder()
+ return nil
}
func (AdapterLagrangeGo) Start() error {
- //TODO implement me
- panic("implement me")
+ // 在函数结束时释放Client并尝试保存签名
+ defer Client.Release()
+ defer SaveSignature()
+ // 登录
+ err := Login()
+ if err != nil {
+ return err
+ }
+ // 尝试刷新client的所有缓存
+ err = Client.RefreshFriendCache()
+ if err != nil {
+ return err
+ }
+ err = Client.RefreshAllGroupsInfo()
+ if err != nil {
+ return err
+ }
+ err = Client.RefreshAllGroupMembersCache()
+ if err != nil {
+ return err
+ }
+ err = Client.RefreshAllRkeyInfoCache()
+ if err != nil {
+ return err
+ }
+ return nil
}
func (AdapterLagrangeGo) GetAdapterInfo() *adapters.AdapterInfo {
@@ -55,3 +96,29 @@ func (AdapterLagrangeGo) GetUserTree() *adapters.UserTree {
//TODO implement me
panic("implement me")
}
+
+// Login 登录
+func Login() error {
+ // 声明 err 变量并进行错误处理
+ err := Client.Login("", "qrcode.png")
+ if err != nil {
+ log.Error("登录时发生错误:", err)
+ return err
+ }
+ return nil
+}
+
+// SaveSignature 保存sign信息
+func SaveSignature() {
+ data, err := Client.Sig().Marshal()
+ if err != nil {
+ log.Error("生成签名文件时发生错误err:", err)
+ return
+ }
+ err = os.WriteFile("signature.bin", data, 0644)
+ if err != nil {
+ log.Error("写入签名文件时发生错误 err:", err)
+ return
+ }
+ log.Info("签名已被写入签名文件")
+}
diff --git a/ice/database_manager.go b/ice/database_manager.go
index d88a2f3..b58e27a 100644
--- a/ice/database_manager.go
+++ b/ice/database_manager.go
@@ -35,9 +35,9 @@ func InitLocalDatabase() {
if count == 0 {
log.Infof("检测到数据库中没有节点数据,正在初始化节点数据...")
// 如果是第一次初始化,插入一个节点数据
- NodeId = GenerateNodeId()
+ SelfNodeId = GenerateNodeId()
db.Create(&IceinuNodeData{
- NodeId: NodeId,
+ NodeId: SelfNodeId,
AdapterModel: "satori",
})
} else {
@@ -45,7 +45,7 @@ func InitLocalDatabase() {
var nodeData IceinuNodeData
db.First(&nodeData)
// 更新全局NodeId
- NodeId = nodeData.NodeId
+ SetSelfNodeId(nodeData.NodeId)
}
// 设置数据库连接池参数
sqlDB, err := db.DB()
diff --git a/ice/node.go b/ice/node.go
index bf99c36..482b016 100644
--- a/ice/node.go
+++ b/ice/node.go
@@ -1,15 +1,35 @@
package ice
-import "github.com/google/uuid"
+import (
+ "github.com/Iceinu-Project/Iceinu/config"
+ "github.com/google/uuid"
+)
-var NodeId string
+var SelfNodeId string
+var MasterNodeId string
func GetSelfNodeId() string {
- return "0"
+ return SelfNodeId
}
func GetMasterNodeId() string {
- return "0"
+ if !config.IceConf.Node.IsEnableNode {
+ return GetSelfNodeId()
+ }
+ if config.IceConf.Node.IsMaster {
+ return GetSelfNodeId()
+ }
+ return MasterNodeId
+}
+
+// SetMasterNodeId 设置主节点ID
+func SetMasterNodeId(masterNodeId string) {
+ MasterNodeId = masterNodeId
+}
+
+// SetSelfNodeId 设置自身节点ID
+func SetSelfNodeId(selfNodeId string) {
+ SelfNodeId = selfNodeId
}
// GenerateNodeId 生成一个新的节点ID,节点ID是一个字符串形式的UUID
diff --git a/main.go b/main.go
index 972a330..c6ac7cc 100644
--- a/main.go
+++ b/main.go
@@ -27,7 +27,12 @@ func main() {
gradient.RGBToANSI(255, 255, 255) +
gradient.GradientBackgroundText(" 通用的模块化 Go 聊天机器人框架 ", "#00d2ff", "#3a7bd5") +
gradient.Reset)
- log.Infof("当前版本: " + gradient.Cyan + "β0.1.3")
+ log.Infof("当前版本: " + gradient.Cyan + "β0.1.4")
// 初始化数据库连接
ice.InitLocalDatabase()
+ // 如果启用集群/分布式模式,则尝试和主节点建立连接
+ // 输出节点Id和集群模式
+ log.Info("当前节点模式: " + gradient.DarkGray + config.IceConf.Node.Mode)
+ log.Info("Node Id: " + gradient.DarkGray + ice.GetSelfNodeId())
+ log.Info("MasterNode Id: " + gradient.DarkGray + ice.GetMasterNodeId())
}
diff --git a/middlewares.go b/middlewares.go
new file mode 100644
index 0000000..4939abf
--- /dev/null
+++ b/middlewares.go
@@ -0,0 +1,6 @@
+package main
+
+// SetCustomMiddleWare 设置自定义中间件
+func SetCustomMiddleWare() {
+
+}
From 3eedc1d6233564a522a14587e82b6314c12c6b67 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kyoku=E2=AD=90?=
<144906395+KyokuKong@users.noreply.github.com>
Date: Sat, 28 Sep 2024 19:55:32 +0800
Subject: [PATCH 9/9] =?UTF-8?q?refact=EF=BC=9A=E5=9F=BA=E6=9C=AC=E9=87=8D?=
=?UTF-8?q?=E6=9E=84=E4=BA=86=E9=80=82=E9=85=8D=E5=99=A8=E5=92=8C=E4=BA=8B?=
=?UTF-8?q?=E4=BB=B6=E6=80=BB=E7=BA=BF=E7=9A=84=E8=AE=BE=E8=AE=A1=EF=BC=8C?=
=?UTF-8?q?=E5=87=86=E5=A4=87=E5=90=88=E5=B9=B6?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
adapters/lagrange/event_publisher.go | 137 ++++++++++-
adapters/lagrange/lagrange.go | 64 +++--
adapters/lagrange/lagrange_config.go | 4 +-
adapters/lagrange/protocol_logger.go | 3 +-
adapters/lagrange/subscribe_manager.go | 312 +++++++++++++++++++++++++
adapters/lagrange/tools.go | 174 ++++++++++++++
cache/iceinu_cache.go | 14 --
ice/event.go | 89 ++++++-
ice/event_maker.go | 200 ++++++++++++++++
ice/plugin_manager.go | 20 ++
log_format.go | 2 +-
main.go | 26 ++-
middlewares.go | 35 +++
models/satori/satori_elements.go | 58 +++++
models/satori/satori_model.go | 14 +-
models/satori/tools.go | 125 +++++++++-
16 files changed, 1201 insertions(+), 76 deletions(-)
create mode 100644 adapters/lagrange/subscribe_manager.go
create mode 100644 adapters/lagrange/tools.go
delete mode 100644 cache/iceinu_cache.go
create mode 100644 ice/event_maker.go
create mode 100644 ice/plugin_manager.go
diff --git a/adapters/lagrange/event_publisher.go b/adapters/lagrange/event_publisher.go
index 48fada5..678fa04 100644
--- a/adapters/lagrange/event_publisher.go
+++ b/adapters/lagrange/event_publisher.go
@@ -5,19 +5,144 @@ import (
"github.com/Iceinu-Project/Iceinu/models/satori"
"github.com/LagrangeDev/LagrangeGo/client"
"github.com/LagrangeDev/LagrangeGo/message"
+ "strconv"
"time"
)
-func EventsBinder() {
- // 绑定私聊消息事件
- Client.PrivateMessageEvent.Subscribe(func(client *client.QQClient, event *message.PrivateMessage) {
+func BindEvents() {
+ // 注册私聊事件
+ Manager.RegisterPrivateMessageHandler(func(client *client.QQClient, event *message.PrivateMessage) {
+ // 尝试从适配器的缓存中获取自身信息
+ selfInfo := GetSelfInfoInCache(client)
+ // 尝试从适配器的缓存中获取好友信息
+ friendInfo := GetFriendsDataInCache(client)
ice.Publish(&ice.IceinuEvent{
- Type: 5,
+ Type: 10,
From: ice.GetSelfNodeId(),
Target: ice.GetMasterNodeId(),
Timestamp: time.Now().Unix(),
- Summary: "",
- Event: &satori.EventSatori{},
+ Summary: "PrivateMessageEvent",
+ Event: &satori.EventSatori{
+ Id: uint64(event.Id),
+ Type: "PrivateMessageEvent",
+ Platform: "QQNT",
+ SelfId: strconv.Itoa(int(client.Uin)),
+ Timestamp: int64(event.Time),
+ Argv: nil,
+ Button: nil,
+ Channel: &satori.Channel{
+ Id: strconv.Itoa(int(event.Sender.Uin)),
+ Type: 1,
+ Name: event.Sender.Uid,
+ ParentId: "",
+ },
+ Group: &satori.Group{
+ Id: "",
+ Name: "",
+ Avatar: "",
+ Maxcount: 0,
+ MemberCount: 0,
+ },
+ Login: nil,
+ Member: nil,
+ Message: &satori.Message{
+ Id: strconv.Itoa(int(event.InternalId)),
+ Content: event.ToString(),
+ Channel: nil,
+ Group: nil,
+ Member: nil,
+ User: nil,
+ CreatedAt: int64(event.Time),
+ UpdatedAt: int64(event.Time),
+ MessageElements: ToSatoriElements(event.Elements),
+ },
+ Operator: &satori.User{
+ Id: strconv.Itoa(int(event.Sender.Uin)),
+ Name: event.Sender.CardName,
+ Nickname: event.Sender.Nickname,
+ Avatar: friendInfo[event.Sender.Uin].Avatar,
+ IsBot: false,
+ },
+ Role: nil,
+ User: &satori.User{
+ Id: strconv.Itoa(int(event.Self)),
+ Name: client.NickName(),
+ Nickname: client.NickName(),
+ Avatar: selfInfo.Avatar,
+ IsBot: false,
+ },
+ },
+ })
+ // 将LagrangeGo的消息存入消息缓存
+ err := Cache.Set(strconv.Itoa(int(event.InternalId)), event)
+ if err != nil {
+ return
+ }
+ })
+ // 注册群聊事件
+ Manager.RegisterGroupMessageHandler(func(client *client.QQClient, event *message.GroupMessage) {
+ // 尝试从LagrangeGo的内置缓存中获取群信息
+ groupInfo := client.GetCachedGroupInfo(event.GroupUin)
+ // 尝试从适配器的缓存中获取自身信息
+ selfInfo := GetSelfInfoInCache(client)
+ // 尝试从适配器的缓存中获取群成员映射
+ groupMemberData := GetGroupMembersDataInCache(client, event.GroupUin)
+ ice.Publish(&ice.IceinuEvent{
+ Type: 10,
+ From: ice.GetSelfNodeId(),
+ Target: ice.GetMasterNodeId(),
+ Timestamp: time.Now().Unix(),
+ Summary: "GroupMessageEvent",
+ Event: &satori.EventSatori{
+ Id: uint64(event.Id),
+ Type: "GroupMessageEvent",
+ Platform: "QQNT",
+ SelfId: strconv.Itoa(int(client.Uin)),
+ Timestamp: int64(event.Time),
+ Argv: nil,
+ Button: nil,
+ Channel: &satori.Channel{
+ Id: strconv.Itoa(int(event.GroupUin)),
+ Type: 0,
+ Name: event.GroupName,
+ ParentId: "",
+ },
+ Group: &satori.Group{
+ Id: strconv.Itoa(int(event.GroupUin)),
+ Name: event.GroupName,
+ Avatar: groupInfo.Avatar,
+ Maxcount: groupInfo.MaxMember,
+ MemberCount: groupInfo.MemberCount,
+ },
+ Login: nil,
+ Member: nil,
+ Message: &satori.Message{
+ Id: strconv.Itoa(int(event.InternalId)),
+ Content: event.ToString(),
+ Channel: nil,
+ Group: nil,
+ Member: nil,
+ User: nil,
+ CreatedAt: int64(event.Time),
+ UpdatedAt: int64(event.Time),
+ MessageElements: ToSatoriElements(event.Elements),
+ },
+ Operator: &satori.User{
+ Id: strconv.Itoa(int(event.Sender.Uin)),
+ Name: event.Sender.CardName,
+ Nickname: event.Sender.Nickname,
+ Avatar: groupMemberData[event.Sender.Uin].Avatar,
+ IsBot: false,
+ },
+ Role: nil,
+ User: &satori.User{
+ Id: strconv.Itoa(int(client.Uin)),
+ Name: client.NickName(),
+ Nickname: client.NickName(),
+ Avatar: selfInfo.Avatar,
+ IsBot: false,
+ },
+ },
})
})
}
diff --git a/adapters/lagrange/lagrange.go b/adapters/lagrange/lagrange.go
index 97ef4c8..5ba9795 100644
--- a/adapters/lagrange/lagrange.go
+++ b/adapters/lagrange/lagrange.go
@@ -2,11 +2,15 @@ package lagrange
import (
"github.com/Iceinu-Project/Iceinu/adapters"
+ "github.com/Iceinu-Project/Iceinu/cache"
+ "github.com/Iceinu-Project/Iceinu/ice"
"github.com/Iceinu-Project/Iceinu/log"
"github.com/LagrangeDev/LagrangeGo/client"
"github.com/LagrangeDev/LagrangeGo/client/auth"
- "github.com/sirupsen/logrus"
"os"
+ "os/signal"
+ "strconv"
+ "syscall"
)
// InfosLagrangeAdapter LagrangeGo适配器元信息
@@ -24,12 +28,18 @@ var InfosLagrangeAdapter = adapters.AdapterInfo{
// Client LagrangeGo客户端实例
var Client *client.QQClient
+// Cache 消息缓存
+var Cache *cache.IceCacheManager
+
// AdapterLagrangeGo LagrangeGo适配器
type AdapterLagrangeGo struct{}
-func (AdapterLagrangeGo) Init() error {
+func (a *AdapterLagrangeGo) Init() error {
// 读取配置文件
AdapterConfigInit()
+ // 初始化消息缓存
+ log.Debug("正在初始化LagrangeGo的消息缓存...")
+ Cache = cache.NewIceCacheManager(AdapterLagrangeConf.CacheSize, AdapterLagrangeConf.CacheExpire)
// 日志输出
appInfo := auth.AppList["linux"]["3.2.10-25765"]
deviceInfo := auth.NewDeviceInfo(AdapterLagrangeConf.Lagrange.Account)
@@ -40,11 +50,11 @@ func (AdapterLagrangeGo) Init() error {
// 尝试读取签名文件
data, err := os.ReadFile("signature.bin")
if err != nil {
- logrus.Warnln("读取签名文件时发生错误:", err)
+ log.Warn("读取签名文件时发生错误:", err)
} else {
sig, err := auth.UnmarshalSigInfo(data, true)
if err != nil {
- logrus.Warnln("加载签名文件时发生错误:", err)
+ log.Warn("加载签名文件时发生错误:", err)
} else {
qqClientInstance.UseSig(sig)
}
@@ -54,45 +64,45 @@ func (AdapterLagrangeGo) Init() error {
return nil
}
-func (AdapterLagrangeGo) SubscribeEvents() error {
- EventsBinder()
+func (a *AdapterLagrangeGo) SubscribeEvents() error {
+ BindEvents()
return nil
}
-func (AdapterLagrangeGo) Start() error {
+func (a *AdapterLagrangeGo) Start() error {
// 在函数结束时释放Client并尝试保存签名
defer Client.Release()
defer SaveSignature()
- // 登录
- err := Login()
- if err != nil {
- return err
- }
- // 尝试刷新client的所有缓存
- err = Client.RefreshFriendCache()
- if err != nil {
- return err
- }
- err = Client.RefreshAllGroupsInfo()
+ // 事件订阅
+ err := a.SubscribeEvents()
if err != nil {
return err
}
- err = Client.RefreshAllGroupMembersCache()
+ SetAllSubscribes()
+ // 登录
+ err = Login()
if err != nil {
return err
}
- err = Client.RefreshAllRkeyInfoCache()
- if err != nil {
- return err
+ // 推送适配器连接事件
+ ice.MakeAdapterConnectEvent(InfosLagrangeAdapter.Name, InfosLagrangeAdapter.Model, strconv.Itoa(int(Client.Uin)), Client.NickName())
+
+ // 主协程关闭通道
+ mc := make(chan os.Signal, 2)
+ signal.Notify(mc, os.Interrupt, syscall.SIGTERM)
+ for {
+ switch <-mc {
+ case os.Interrupt, syscall.SIGTERM:
+ return nil
+ }
}
- return nil
}
-func (AdapterLagrangeGo) GetAdapterInfo() *adapters.AdapterInfo {
+func (a *AdapterLagrangeGo) GetAdapterInfo() *adapters.AdapterInfo {
return &InfosLagrangeAdapter
}
-func (AdapterLagrangeGo) GetUserTree() *adapters.UserTree {
+func (a *AdapterLagrangeGo) GetUserTree() *adapters.UserTree {
//TODO implement me
panic("implement me")
}
@@ -122,3 +132,7 @@ func SaveSignature() {
}
log.Info("签名已被写入签名文件")
}
+
+func GetAdapter() adapters.IceinuAdapter {
+ return &AdapterLagrangeGo{}
+}
diff --git a/adapters/lagrange/lagrange_config.go b/adapters/lagrange/lagrange_config.go
index 9c2499b..d832f26 100644
--- a/adapters/lagrange/lagrange_config.go
+++ b/adapters/lagrange/lagrange_config.go
@@ -9,8 +9,8 @@ var AdapterLagrangeConf *AdapterConfig
// AdapterConfig 适配器配置
type AdapterConfig struct {
- CacheSize int `toml:"message_cache_size"` // 消息缓存大小
- CacheExpire int `toml:"message_cache_expire"` // 消息缓存过期时间
+ CacheSize uint32 `toml:"message_cache_size"` // 消息缓存大小
+ CacheExpire uint32 `toml:"message_cache_expire"` // 消息缓存过期时间
Lagrange LgrConfig `toml:"lagrange"`
}
diff --git a/adapters/lagrange/protocol_logger.go b/adapters/lagrange/protocol_logger.go
index 9e950df..e0f6cd0 100644
--- a/adapters/lagrange/protocol_logger.go
+++ b/adapters/lagrange/protocol_logger.go
@@ -2,6 +2,7 @@ package lagrange
import (
"fmt"
+ gradient "github.com/Iceinu-Project/IceGradient"
"github.com/Iceinu-Project/Iceinu/log"
"os"
"path"
@@ -13,7 +14,7 @@ type ProtocolLogger struct{}
var dumpspath = "dump"
-const fromProtocol = "LGR | "
+const fromProtocol = "LGR | " + gradient.DarkGray
func (p ProtocolLogger) Info(format string, arg ...any) {
log.Infof(fromProtocol+format, arg...)
diff --git a/adapters/lagrange/subscribe_manager.go b/adapters/lagrange/subscribe_manager.go
new file mode 100644
index 0000000..2436768
--- /dev/null
+++ b/adapters/lagrange/subscribe_manager.go
@@ -0,0 +1,312 @@
+package lagrange
+
+import (
+ "github.com/LagrangeDev/LagrangeGo/client"
+ "github.com/LagrangeDev/LagrangeGo/client/event"
+ "github.com/LagrangeDev/LagrangeGo/message"
+)
+
+// 定义各个消息类型的处理函数
+
+type PrivateMessageHandler func(client *client.QQClient, message *message.PrivateMessage)
+type GroupMessageHandler func(client *client.QQClient, message *message.GroupMessage)
+type TempMessageHandler func(client *client.QQClient, message *message.TempMessage)
+
+type SelfPrivateMessageHandler func(client *client.QQClient, message *message.PrivateMessage)
+type SelfGroupMessageHandler func(client *client.QQClient, message *message.GroupMessage)
+type SelfTempMessageHandler func(client *client.QQClient, message *message.TempMessage)
+
+// 定义各个事件类型的处理函数
+
+type GroupJoinEventHandler func(client *client.QQClient, event *event.GroupMemberIncrease) // bot进群
+type GroupLeaveEventHandler func(client *client.QQClient, event *event.GroupMemberDecrease) // bot退群
+
+type GroupInvitedEventHandler func(client *client.QQClient, event *event.GroupInvite) // bot被邀请进群
+type GroupMemberJoinRequestEventHandler func(client *client.QQClient, event *event.GroupMemberJoinRequest) // 加群邀请
+type GroupMemberJoinEventHandler func(client *client.QQClient, event *event.GroupMemberIncrease) // 成员加群
+type GroupMemberLeaveEventHandler func(client *client.QQClient, event *event.GroupMemberDecrease) // 成员退群
+type GroupMuteEventHandler func(client *client.QQClient, event *event.GroupMute) // 禁言事件
+type GroupRecallEventHandler func(client *client.QQClient, event *event.GroupRecall)
+type GroupMemberPermissionChangedEventHandler func(client *client.QQClient, event *event.GroupMemberPermissionChanged)
+type GroupNameUpdatedEventHandler func(client *client.QQClient, event *event.GroupNameUpdated) // 群更名
+type MemberSpecialTitleUpdatedEventHandler func(client *client.QQClient, event *event.MemberSpecialTitleUpdated) // 群成员特殊头衔更新
+type NewFriendRequestHandler func(client *client.QQClient, event *event.NewFriendRequest) // 新朋友请求
+type FriendRecallEventHandler func(client *client.QQClient, event *event.FriendRecall) // 好友消息撤回
+type RenameEventHandler func(client *client.QQClient, event *event.Rename) // 好友昵称更改
+type FriendNotifyEventHandler func(client *client.QQClient, event event.INotifyEvent) // 好友通知
+type GroupNotifyEventHandler func(client *client.QQClient, event event.INotifyEvent) // 群通知
+
+// SubscribeManager 订阅管理器实现,用于向LagrangeGo注册各类消息/事件处理函数
+type SubscribeManager struct {
+ privateMessageHandlers []PrivateMessageHandler
+ groupMessageHandlers []GroupMessageHandler
+ tempMessageHandlers []TempMessageHandler
+ selfPrivateMessageHandlers []SelfPrivateMessageHandler
+ selfGroupMessageHandlers []SelfGroupMessageHandler
+ selfTempMessageHandlers []SelfTempMessageHandler
+ groupJoinEventHandlers []GroupJoinEventHandler
+ groupLeaveEventHandlers []GroupLeaveEventHandler
+ groupInviteEventHandlers []GroupInvitedEventHandler
+ groupMemberJoinRequestEventHandlers []GroupMemberJoinRequestEventHandler
+ groupMemberJoinEventHandlers []GroupMemberJoinEventHandler
+ groupMemberLeaveEventHandlers []GroupMemberLeaveEventHandler
+ groupMuteEventHandlers []GroupMuteEventHandler
+ groupRecallEventHandlers []GroupRecallEventHandler
+ groupMemberPermissionChangedEventHandlers []GroupMemberPermissionChangedEventHandler
+ groupNameUpdatedEventHandlers []GroupNameUpdatedEventHandler
+ memberSpecialTitleUpdatedEventHandlers []MemberSpecialTitleUpdatedEventHandler
+ newFriendRequestHandlers []NewFriendRequestHandler
+ friendRecallEventHandlers []FriendRecallEventHandler
+ renameEventHandlers []RenameEventHandler
+ friendNotifyEventHandlers []FriendNotifyEventHandler
+ groupNotifyEventHandlers []GroupNotifyEventHandler
+}
+
+// Manager 全局 SubscribeManager 实例
+var Manager = &SubscribeManager{}
+
+// RegisterPrivateMessageHandler 注册私聊消息处理函数
+func (sm *SubscribeManager) RegisterPrivateMessageHandler(handler PrivateMessageHandler) {
+ sm.privateMessageHandlers = append(sm.privateMessageHandlers, handler)
+}
+
+// RegisterGroupMessageHandler 注册群消息处理函数
+func (sm *SubscribeManager) RegisterGroupMessageHandler(handler GroupMessageHandler) {
+ sm.groupMessageHandlers = append(sm.groupMessageHandlers, handler)
+}
+
+// RegisterTempMessageHandler 注册临时消息处理函数
+func (sm *SubscribeManager) RegisterTempMessageHandler(handler TempMessageHandler) {
+ sm.tempMessageHandlers = append(sm.tempMessageHandlers, handler)
+}
+
+// RegisterSelfPrivateMessageHandler 注册自己的私聊消息处理函数
+func (sm *SubscribeManager) RegisterSelfPrivateMessageHandler(handler SelfPrivateMessageHandler) {
+ sm.selfPrivateMessageHandlers = append(sm.selfPrivateMessageHandlers, handler)
+}
+
+// RegisterSelfGroupMessageHandler 注册自己的群消息处理函数
+func (sm *SubscribeManager) RegisterSelfGroupMessageHandler(handler SelfGroupMessageHandler) {
+ sm.selfGroupMessageHandlers = append(sm.selfGroupMessageHandlers, handler)
+}
+
+// RegisterSelfTempMessageHandler 注册自己的临时消息处理函数
+func (sm *SubscribeManager) RegisterSelfTempMessageHandler(handler SelfTempMessageHandler) {
+ sm.selfTempMessageHandlers = append(sm.selfTempMessageHandlers, handler)
+}
+
+// RegisterGroupJoinEventHandler 注册群成员加入事件处理函数
+func (sm *SubscribeManager) RegisterGroupJoinEventHandler(handler GroupJoinEventHandler) {
+ sm.groupJoinEventHandlers = append(sm.groupJoinEventHandlers, handler)
+}
+
+// RegisterGroupLeaveEventHandler 注册群成员离开事件处理函数
+func (sm *SubscribeManager) RegisterGroupLeaveEventHandler(handler GroupLeaveEventHandler) {
+ sm.groupLeaveEventHandlers = append(sm.groupLeaveEventHandlers, handler)
+}
+
+// RegisterGroupInviteEventHandler 注册群邀请事件处理函数
+func (sm *SubscribeManager) RegisterGroupInviteEventHandler(handler GroupInvitedEventHandler) {
+ sm.groupInviteEventHandlers = append(sm.groupInviteEventHandlers, handler)
+
+}
+
+// RegisterGroupMemberJoinRequestEventHandler 注册群成员加群邀请事件处理函数
+func (sm *SubscribeManager) RegisterGroupMemberJoinRequestEventHandler(handler GroupMemberJoinRequestEventHandler) {
+ sm.groupMemberJoinRequestEventHandlers = append(sm.groupMemberJoinRequestEventHandlers, handler)
+}
+
+// RegisterGroupMemberJoinEventHandler 注册群成员加入事件处理函数
+func (sm *SubscribeManager) RegisterGroupMemberJoinEventHandler(handler GroupMemberJoinEventHandler) {
+ sm.groupMemberJoinEventHandlers = append(sm.groupMemberJoinEventHandlers, handler)
+}
+
+// RegisterGroupMemberLeaveEventHandler 注册群成员离开事件处理函数
+func (sm *SubscribeManager) RegisterGroupMemberLeaveEventHandler(handler GroupMemberLeaveEventHandler) {
+ sm.groupMemberLeaveEventHandlers = append(sm.groupMemberLeaveEventHandlers, handler)
+}
+
+// RegisterGroupMuteEventHandler 注册群成员禁言事件处理函数
+func (sm *SubscribeManager) RegisterGroupMuteEventHandler(handler GroupMuteEventHandler) {
+ sm.groupMuteEventHandlers = append(sm.groupMuteEventHandlers, handler)
+}
+
+// RegisterGroupRecallEventHandler 注册群消息撤回事件处理函数
+func (sm *SubscribeManager) RegisterGroupRecallEventHandler(handler GroupRecallEventHandler) {
+ sm.groupRecallEventHandlers = append(sm.groupRecallEventHandlers, handler)
+}
+
+// RegisterGroupMemberPermissionChangedEventHandler 注册群成员权限变更事件处理函数
+func (sm *SubscribeManager) RegisterGroupMemberPermissionChangedEventHandler(handler GroupMemberPermissionChangedEventHandler) {
+ sm.groupMemberPermissionChangedEventHandlers = append(sm.groupMemberPermissionChangedEventHandlers, handler)
+}
+
+// RegisterGroupNameUpdatedEventHandler 注册群名称变更事件处理函数
+func (sm *SubscribeManager) RegisterGroupNameUpdatedEventHandler(handler GroupNameUpdatedEventHandler) {
+ sm.groupNameUpdatedEventHandlers = append(sm.groupNameUpdatedEventHandlers, handler)
+}
+
+// RegisterMemberSpecialTitleUpdatedEventHandler 注册群成员特殊头衔变更事件处理函数
+func (sm *SubscribeManager) RegisterMemberSpecialTitleUpdatedEventHandler(handler MemberSpecialTitleUpdatedEventHandler) {
+ sm.memberSpecialTitleUpdatedEventHandlers = append(sm.memberSpecialTitleUpdatedEventHandlers, handler)
+}
+
+// RegisterNewFriendRequestHandler 注册新朋友请求处理函数
+func (sm *SubscribeManager) RegisterNewFriendRequestHandler(handler NewFriendRequestHandler) {
+ sm.newFriendRequestHandlers = append(sm.newFriendRequestHandlers, handler)
+}
+
+// RegisterFriendRecallEventHandler 注册好友消息撤回事件处理函数
+func (sm *SubscribeManager) RegisterFriendRecallEventHandler(handler FriendRecallEventHandler) {
+ sm.friendRecallEventHandlers = append(sm.friendRecallEventHandlers, handler)
+}
+
+// RegisterRenameEventHandler 注册好友昵称更改事件处理函数
+func (sm *SubscribeManager) RegisterRenameEventHandler(handler RenameEventHandler) {
+ sm.renameEventHandlers = append(sm.renameEventHandlers, handler)
+}
+
+// RegisterFriendNotifyEventHandler 注册好友通知事件处理函数
+func (sm *SubscribeManager) RegisterFriendNotifyEventHandler(handler FriendNotifyEventHandler) {
+ sm.friendNotifyEventHandlers = append(sm.friendNotifyEventHandlers, handler)
+}
+
+// RegisterGroupNotifyEventHandler 注册群通知事件处理函数
+func (sm *SubscribeManager) RegisterGroupNotifyEventHandler(handler GroupNotifyEventHandler) {
+ sm.groupNotifyEventHandlers = append(sm.groupNotifyEventHandlers, handler)
+}
+
+// SetAllSubscribes 设置所有订阅处理
+func SetAllSubscribes() {
+ Client.PrivateMessageEvent.Subscribe(func(client *client.QQClient, event *message.PrivateMessage) {
+ for _, handler := range Manager.privateMessageHandlers {
+ handler(client, event)
+ }
+ })
+
+ Client.GroupMessageEvent.Subscribe(func(client *client.QQClient, event *message.GroupMessage) {
+ for _, handler := range Manager.groupMessageHandlers {
+ handler(client, event)
+ }
+ })
+
+ Client.TempMessageEvent.Subscribe(func(client *client.QQClient, event *message.TempMessage) {
+ for _, handler := range Manager.tempMessageHandlers {
+ handler(client, event)
+ }
+ })
+
+ Client.SelfPrivateMessageEvent.Subscribe(func(client *client.QQClient, event *message.PrivateMessage) {
+ for _, handler := range Manager.selfPrivateMessageHandlers {
+ handler(client, event)
+ }
+ })
+
+ Client.SelfGroupMessageEvent.Subscribe(func(client *client.QQClient, event *message.GroupMessage) {
+ for _, handler := range Manager.selfGroupMessageHandlers {
+ handler(client, event)
+ }
+ })
+
+ Client.SelfTempMessageEvent.Subscribe(func(client *client.QQClient, event *message.TempMessage) {
+ for _, handler := range Manager.selfTempMessageHandlers {
+ handler(client, event)
+ }
+ })
+
+ Client.GroupJoinEvent.Subscribe(func(client *client.QQClient, event *event.GroupMemberIncrease) {
+ for _, handler := range Manager.groupJoinEventHandlers {
+ handler(client, event)
+ }
+ })
+
+ Client.GroupLeaveEvent.Subscribe(func(client *client.QQClient, event *event.GroupMemberDecrease) {
+ for _, handler := range Manager.groupLeaveEventHandlers {
+ handler(client, event)
+ }
+ })
+
+ Client.GroupInvitedEvent.Subscribe(func(client *client.QQClient, event *event.GroupInvite) {
+ for _, handler := range Manager.groupInviteEventHandlers {
+ handler(client, event)
+ }
+ })
+
+ Client.GroupMemberJoinRequestEvent.Subscribe(func(client *client.QQClient, event *event.GroupMemberJoinRequest) {
+ for _, handler := range Manager.groupMemberJoinRequestEventHandlers {
+ handler(client, event)
+ }
+ })
+
+ Client.GroupMemberJoinEvent.Subscribe(func(client *client.QQClient, event *event.GroupMemberIncrease) {
+ for _, handler := range Manager.groupMemberJoinEventHandlers {
+ handler(client, event)
+ }
+ })
+
+ Client.GroupMemberLeaveEvent.Subscribe(func(client *client.QQClient, event *event.GroupMemberDecrease) {
+ for _, handler := range Manager.groupMemberLeaveEventHandlers {
+ handler(client, event)
+ }
+ })
+
+ Client.GroupMuteEvent.Subscribe(func(client *client.QQClient, event *event.GroupMute) {
+ for _, handler := range Manager.groupMuteEventHandlers {
+ handler(client, event)
+ }
+ })
+
+ Client.GroupRecallEvent.Subscribe(func(client *client.QQClient, event *event.GroupRecall) {
+ for _, handler := range Manager.groupRecallEventHandlers {
+ handler(client, event)
+ }
+ })
+
+ Client.GroupMemberPermissionChangedEvent.Subscribe(func(client *client.QQClient, event *event.GroupMemberPermissionChanged) {
+ for _, handler := range Manager.groupMemberPermissionChangedEventHandlers {
+ handler(client, event)
+ }
+ })
+
+ Client.GroupNameUpdatedEvent.Subscribe(func(client *client.QQClient, event *event.GroupNameUpdated) {
+ for _, handler := range Manager.groupNameUpdatedEventHandlers {
+ handler(client, event)
+ }
+ })
+
+ Client.MemberSpecialTitleUpdatedEvent.Subscribe(func(client *client.QQClient, event *event.MemberSpecialTitleUpdated) {
+ for _, handler := range Manager.memberSpecialTitleUpdatedEventHandlers {
+ handler(client, event)
+ }
+ })
+
+ Client.NewFriendRequestEvent.Subscribe(func(client *client.QQClient, event *event.NewFriendRequest) {
+ for _, handler := range Manager.newFriendRequestHandlers {
+ handler(client, event)
+ }
+ })
+
+ Client.FriendRecallEvent.Subscribe(func(client *client.QQClient, event *event.FriendRecall) {
+ for _, handler := range Manager.friendRecallEventHandlers {
+ handler(client, event)
+ }
+ })
+
+ Client.RenameEvent.Subscribe(func(client *client.QQClient, event *event.Rename) {
+ for _, handler := range Manager.renameEventHandlers {
+ handler(client, event)
+ }
+ })
+
+ Client.FriendNotifyEvent.Subscribe(func(client *client.QQClient, event event.INotifyEvent) {
+ for _, handler := range Manager.friendNotifyEventHandlers {
+ handler(client, event)
+ }
+ })
+
+ Client.GroupNotifyEvent.Subscribe(func(client *client.QQClient, event event.INotifyEvent) {
+ for _, handler := range Manager.groupNotifyEventHandlers {
+ handler(client, event)
+ }
+ })
+}
diff --git a/adapters/lagrange/tools.go b/adapters/lagrange/tools.go
new file mode 100644
index 0000000..ada0b32
--- /dev/null
+++ b/adapters/lagrange/tools.go
@@ -0,0 +1,174 @@
+package lagrange
+
+import (
+ "fmt"
+ "github.com/Iceinu-Project/Iceinu/log"
+ "github.com/Iceinu-Project/Iceinu/models/satori"
+ "github.com/LagrangeDev/LagrangeGo/client"
+ "github.com/LagrangeDev/LagrangeGo/client/entity"
+ "github.com/LagrangeDev/LagrangeGo/message"
+ "strconv"
+ "strings"
+)
+
+// ToSatoriElements 将LagrangeGo的消息元素切片转换为Satori的消息元素切片
+func ToSatoriElements(elements []message.IMessageElement) *[]satori.ElementSatori {
+ // 创建存储Satori消息切片的变量
+ var result []satori.ElementSatori
+ // 遍历传入的元素
+ for _, ele := range elements {
+ // 通过消息元素的Type方法来确定消息类型
+ switch ele.Type() {
+ // 将元素依次对应转换并传入
+ case message.Text:
+ ele := ele.(*message.TextElement)
+ // 检测文本中是否包含换行符
+ if strings.Contains(ele.Content, "\n") {
+ // 如果包含换行符,进行拆分和处理
+ textParts := strings.Split(ele.Content, "\n")
+ for i, part := range textParts {
+ // 将每段文本添加到 result
+ result = append(result, &satori.TextElement{Text: part})
+ // 如果不是最后一段文本,则插入 BrElement
+ if i < len(textParts)-1 {
+ result = append(result, &satori.BrElement{})
+ }
+ }
+ } else {
+ // 如果不包含换行符,直接添加文本元素
+ result = append(result, &satori.TextElement{Text: ele.Content})
+ }
+ case message.At:
+ ele := ele.(*message.AtElement)
+ result = append(result, &satori.AtElement{
+ Id: strconv.Itoa(int(ele.TargetUin)),
+ Name: ele.Display,
+ Role: "",
+ Type: strconv.Itoa(int(ele.Type())),
+ })
+ case message.Face:
+ ele := ele.(*message.FaceElement)
+ result = append(result, &satori.FaceElement{
+ Id: ele.FaceID,
+ })
+ case message.Voice:
+ ele := ele.(*message.VoiceElement)
+ result = append(result, &satori.AudioElement{
+ Src: ele.Url,
+ Title: ele.Name,
+ Duration: ele.Size,
+ Poster: "",
+ Cache: false,
+ Timeout: 0,
+ })
+ case message.Image:
+ ele := ele.(*message.ImageElement)
+ result = append(result, &satori.ImgElement{
+ Src: ele.Url,
+ Width: ele.Width,
+ Height: ele.Height,
+ Title: ele.ImageId,
+ Cache: false,
+ Timeout: 0,
+ })
+ case message.File:
+ ele := ele.(*message.FileElement)
+ result = append(result, &satori.FileElement{
+ Src: ele.FileUrl,
+ Title: ele.FileName,
+ Poster: "",
+ Cache: false,
+ Timeout: 0,
+ })
+ case message.Reply:
+ ele := ele.(*message.ReplyElement)
+ result = append(result, &satori.QuoteElement{
+ Id: strconv.Itoa(int(ele.SenderUin)),
+ Name: ele.SenderUid,
+ ChannelId: strconv.Itoa(int(ele.GroupUin)),
+ GroupId: strconv.Itoa(int(ele.GroupUin)),
+ Timestamp: int64(ele.Time),
+ Elements: ToSatoriElements(ele.Elements),
+ })
+ case message.Forward:
+ ele := ele.(*message.ForwardMessage)
+ result = append(result, &satori.MessageElement{
+ Forward: true,
+ Elements: UnzipNodes(ele.Nodes),
+ })
+
+ default:
+ result = append(result, &satori.UnsupportedElement{Type: strconv.Itoa(int(ele.Type()))})
+
+ }
+ }
+ return &result
+}
+
+func UnzipNodes(n []*message.ForwardNode) *[]satori.ElementSatori {
+ var result []satori.ElementSatori
+ for _, node := range n {
+ result = append(result, &satori.NodeElement{
+ GroupId: node.GroupId,
+ SenderId: node.SenderId,
+ SenderName: node.SenderName,
+ Time: node.Time,
+ Message: ToSatoriElements(node.Message),
+ })
+ }
+ return &result
+}
+
+// GetGroupMembersDataInCache 从缓存中获取群成员映射,如果缓存中没有则拉取并存入缓存
+func GetGroupMembersDataInCache(client *client.QQClient, groupId uint32) map[uint32]*entity.GroupMember {
+ // 尝试在内置缓存中查找群成员映射
+ var groupMemberMap map[uint32]*entity.GroupMember
+ err := Cache.Get(fmt.Sprintf("group_member_data_%d", groupId), &groupMemberMap)
+ if err != nil {
+ // 如果缓存中没有群成员映射,则创建一个
+ groupMembersData, err := client.GetGroupMembersData(groupId)
+ if err != nil {
+ log.Warn("无法获取群成员列表数据:", err)
+ }
+ err = Cache.Set(fmt.Sprintf("group_member_data_%d", groupId), groupMembersData)
+ if err != nil {
+ log.Warn("无法缓存群成员映射数据:", err)
+ }
+ groupMemberMap = groupMembersData
+ log.Debugf("%v群成员缓存数据更新完成,共%d个成员", groupId, len(groupMemberMap))
+ }
+ return groupMemberMap
+}
+
+// GetFriendsDataInCache 从缓存中获取好友映射,如果缓存中没有则拉取并存入缓存
+func GetFriendsDataInCache(client *client.QQClient) map[uint32]*entity.Friend {
+ // 尝试在内置缓存中查找好友映射
+ var friendMap map[uint32]*entity.Friend
+ err := Cache.Get("friend_data", &friendMap)
+ if err != nil {
+ // 如果缓存中没有好友映射,则创建一个
+ friendData, err := client.GetFriendsData()
+ if err != nil {
+ log.Warn("无法获取好友列表数据:", err)
+ }
+ err = Cache.Set("friend_data", friendData)
+ if err != nil {
+ log.Warn("无法缓存好友映射数据:", err)
+ }
+ friendMap = friendData
+ log.Debugf("好友缓存数据更新完成,共%d个好友", len(friendMap))
+ }
+ return friendMap
+}
+
+// GetSelfInfoInCache 从缓存中获取自身信息,如果缓存中没有则拉取并存入缓存
+func GetSelfInfoInCache(client *client.QQClient) *entity.Friend {
+ // 尝试在内置缓存中查找自身信息
+ friendData := GetFriendsDataInCache(client)
+ selfInfo, ok := friendData[client.Uin]
+ if !ok {
+ log.Warn("无法获取自身信息")
+ return nil
+ }
+ return selfInfo
+}
diff --git a/cache/iceinu_cache.go b/cache/iceinu_cache.go
deleted file mode 100644
index 4e8c49b..0000000
--- a/cache/iceinu_cache.go
+++ /dev/null
@@ -1,14 +0,0 @@
-package cache
-
-import (
- "github.com/Iceinu-Project/Iceinu/config"
- "github.com/Iceinu-Project/Iceinu/log"
-)
-
-var IceCache *IceCacheManager
-
-// 初始化缓存管理器
-func init() {
- log.Debugf("正在初始化内置缓存管理器...")
- IceCache = NewIceCacheManager(config.IceConf.Cache.MaxCacheSize, config.IceConf.Cache.CacheExpire)
-}
diff --git a/ice/event.go b/ice/event.go
index bf91068..e256065 100644
--- a/ice/event.go
+++ b/ice/event.go
@@ -1,5 +1,7 @@
package ice
+import "github.com/Iceinu-Project/Iceinu/adapters"
+
// IceinuEvent Iceinu全局事件结构体
//
// 默认的全局事件总线结构体,在直接使用Iceinu框架进行二次开发时使用这个事件结构
@@ -9,17 +11,31 @@ package ice
// 事件种类:
// 0:WebSocket心跳事件
//
-// 1:节点连接事件
+// 1:节点请求建立WebSocket连接事件
+//
+// 2:节点成功连接事件
+//
+// 3:节点断开连接事件
+//
+// 4:节点推送数据事件(主节点到子节点)
+//
+// 5:节点用户推送事件(子节点到主节点)
+//
+// 6:节点请求数据事件(子节点到主节点)
+//
+// 7:节点请求数据事件(主节点到子节点)
//
-// 2:节点断开事件
+// 8:适配器连接事件
//
-// 3:节点更新推送事件
+// 9:适配器断开连接事件
//
-// 4:节点更新请求事件
+// 10:消息接收事件
//
-// 5:消息接受事件(从子节点到主节点)
+// 11:消息发送事件
//
-// 6:消息处理事件(从主节点到子节点)
+// 12:节点失活事件(子节点到主节点)
+//
+// 13:节点重新激活事件(子节点到主节点)
type IceinuEvent struct {
Type uint8 `json:"type"`
From string `json:"from"` // 消息事件来源节点ID
@@ -29,18 +45,71 @@ type IceinuEvent struct {
Event interface{} `json:"event"` // 事件内容,用于承载不同类型的消息,使用时需要进行断言
}
+// WebSocketHeartbeatEvent 0:WebSocket心跳事件结构体
type WebSocketHeartbeatEvent struct {
OK bool `json:"ok"`
}
-type NodeConnectEvent struct {
+// NodeConnectRequestEvent 1:节点请求建立WebSocket连接事件结构体
+type NodeConnectRequestEvent struct {
+ Mode string `json:"mode"` // 组网模式
+ AdapterModel string `json:"adapter_model"` // 适配器模型
+ PluginVerifier string `json:"plugin_verifier"` // 插件校验值
+}
+
+// NodeConnectedEvent 2:节点成功连接事件结构体
+type NodeConnectedEvent struct {
+ OK bool `json:"ok"`
+}
+
+// NodeDisconnectedEvent 3:节点断开连接事件结构体
+type NodeDisconnectedEvent struct {
+ OK bool `json:"ok"`
}
-type NodeDisconnectEvent struct {
+// NodePushDataEvent 4:节点推送数据事件(主节点到子节点)结构体
+type NodePushDataEvent struct {
+ Data interface{} `json:"data"` // 推送的数据
}
-type NodeUpdatePushEvent struct {
+// NodeUserPushEvent 5:节点用户推送事件(子节点到主节点)结构体
+type NodeUserPushEvent struct {
+ UserTree adapters.UserTree `json:"user_tree"` // 用户树
}
-type NodeUpdateRequestEvent struct {
+// NodeRequestDataEvent 6:节点请求数据事件(子节点到主节点)结构体
+type NodeRequestDataEvent struct {
+ DataType string `json:"data_type"` // 请求的数据类型
+ Key string `json:"key"` // 请求的数据键
+ Query string `json:"query"` // 请求的查询内容
+}
+
+// RequestNodeDataEvent 7:节点请求数据事件(主节点到子节点)结构体
+type RequestNodeDataEvent struct {
+ DataType string `json:"data_type"` // 请求的数据类型
+ Key string `json:"key"` // 请求的数据键
+ Query string `json:"query"` // 请求的查询内容
+}
+
+// AdapterConnectEvent 8:适配器连接事件结构体
+type AdapterConnectEvent struct {
+ AdapterType string `json:"adapter_type"` // 适配器类型
+ AdapterModel string `json:"adapter_model"` // 适配器模型
+ UserId string `json:"user_id"` // 用户ID
+ UserName string `json:"user_name"` // 用户名称
+}
+
+// AdapterDisconnectEvent 9:适配器断开连接事件结构体
+type AdapterDisconnectEvent struct {
+ OK bool `json:"ok"`
+}
+
+// NodeDeactiveEvent 12:节点失活事件(子节点到主节点)结构体
+type NodeDeactiveEvent struct {
+ OK bool `json:"ok"`
+}
+
+// NodeReactiveEvent 13:节点重新激活事件(子节点到主节点)结构体
+type NodeReactiveEvent struct {
+ OK bool `json:"ok"`
}
diff --git a/ice/event_maker.go b/ice/event_maker.go
new file mode 100644
index 0000000..5420baa
--- /dev/null
+++ b/ice/event_maker.go
@@ -0,0 +1,200 @@
+package ice
+
+import "time"
+
+// MakeWebsocketHeartbeatEvent 创建一个WebSocket心跳事件
+func MakeWebsocketHeartbeatEvent() *IceinuEvent {
+ e := &IceinuEvent{
+ Type: 0,
+ From: GetSelfNodeId(),
+ Target: GetMasterNodeId(),
+ Timestamp: time.Now().Unix(),
+ Summary: "WebSocketHeartbeatEvent",
+ Event: &WebSocketHeartbeatEvent{
+ OK: true,
+ },
+ }
+ Publish(e)
+ return e
+}
+
+// MakeNodeConnectRequestEvent 创建一个节点请求建立WebSocket连接事件
+func MakeNodeConnectRequestEvent(mode string, adapterModel string, pluginVerifier string) *IceinuEvent {
+ e := &IceinuEvent{
+ Type: 1,
+ From: GetSelfNodeId(),
+ Target: GetMasterNodeId(),
+ Timestamp: time.Now().Unix(),
+ Summary: "NodeConnectRequestEvent",
+ Event: &NodeConnectRequestEvent{
+ Mode: mode,
+ AdapterModel: adapterModel,
+ PluginVerifier: pluginVerifier,
+ },
+ }
+ Publish(e)
+ return e
+}
+
+// MakeNodeConnectedEvent 创建一个节点成功连接事件
+func MakeNodeConnectedEvent() *IceinuEvent {
+ e := &IceinuEvent{
+ Type: 2,
+ From: GetSelfNodeId(),
+ Target: GetMasterNodeId(),
+ Timestamp: time.Now().Unix(),
+ Summary: "NodeConnectedEvent",
+ Event: &NodeConnectedEvent{
+ OK: true,
+ },
+ }
+ Publish(e)
+ return e
+}
+
+// MakeNodeDisconnectedEvent 创建一个节点断开连接事件
+func MakeNodeDisconnectedEvent() *IceinuEvent {
+ e := &IceinuEvent{
+ Type: 3,
+ From: GetSelfNodeId(),
+ Target: GetMasterNodeId(),
+ Timestamp: time.Now().Unix(),
+ Summary: "NodeDisconnectedEvent",
+ Event: &NodeDisconnectedEvent{
+ OK: true,
+ },
+ }
+ Publish(e)
+ return e
+}
+
+// MakeNodePushDataEvent 创建一个节点推送数据事件
+func MakeNodePushDataEvent(data interface{}) *IceinuEvent {
+ e := &IceinuEvent{
+ Type: 4,
+ From: GetSelfNodeId(),
+ Target: GetMasterNodeId(),
+ Timestamp: time.Now().Unix(),
+ Summary: "NodePushDataEvent",
+ Event: data,
+ }
+ Publish(e)
+ return e
+}
+
+// MakeNodeUserPushEvent 创建一个节点用户推送事件
+func MakeNodeUserPushEvent(data interface{}) *IceinuEvent {
+ e := &IceinuEvent{
+ Type: 5,
+ From: GetSelfNodeId(),
+ Target: GetMasterNodeId(),
+ Timestamp: time.Now().Unix(),
+ Summary: "NodeUserPushEvent",
+ Event: data,
+ }
+ Publish(e)
+ return e
+}
+
+// MakeNodeRequestDataEvent 创建一个节点请求数据事件
+func MakeNodeRequestDataEvent(dataType string, key string, query string) *IceinuEvent {
+ e := &IceinuEvent{
+ Type: 6,
+ From: GetSelfNodeId(),
+ Target: GetMasterNodeId(),
+ Timestamp: time.Now().Unix(),
+ Summary: "NodeRequestDataEvent",
+ Event: &NodeRequestDataEvent{
+ DataType: dataType,
+ Key: key,
+ Query: query,
+ },
+ }
+ Publish(e)
+ return e
+}
+
+// MakeRequestNodeDataEvent 创建一个请求节点数据事件
+func MakeRequestNodeDataEvent(dataType string, key string, query string) *IceinuEvent {
+ e := &IceinuEvent{
+ Type: 7,
+ From: GetSelfNodeId(),
+ Target: GetMasterNodeId(),
+ Timestamp: time.Now().Unix(),
+ Summary: "RequestNodeDataEvent",
+ Event: &RequestNodeDataEvent{
+ DataType: dataType,
+ Key: key,
+ Query: query,
+ },
+ }
+ Publish(e)
+ return e
+}
+
+// MakeNodeDeactiveEvent 创建一个节点失活事件
+func MakeNodeDeactiveEvent() *IceinuEvent {
+ e := &IceinuEvent{
+ Type: 12,
+ From: GetSelfNodeId(),
+ Target: GetMasterNodeId(),
+ Timestamp: time.Now().Unix(),
+ Summary: "NodeActiveEvent",
+ Event: &NodeDeactiveEvent{
+ OK: true,
+ },
+ }
+ Publish(e)
+ return e
+}
+
+// MakeNodeReactiveEvent 创建一个节点重新激活事件
+func MakeNodeReactiveEvent() *IceinuEvent {
+ e := &IceinuEvent{
+ Type: 13,
+ From: GetSelfNodeId(),
+ Target: GetMasterNodeId(),
+ Timestamp: time.Now().Unix(),
+ Summary: "NodeActiveEvent",
+ Event: &NodeReactiveEvent{
+ OK: true,
+ },
+ }
+ Publish(e)
+ return e
+}
+
+// MakeAdapterConnectEvent 创建一个适配器连接事件
+func MakeAdapterConnectEvent(adapterType string, model string, userId string, userName string) *IceinuEvent {
+ e := &IceinuEvent{
+ Type: 8,
+ From: GetSelfNodeId(),
+ Target: GetMasterNodeId(),
+ Timestamp: time.Now().Unix(),
+ Summary: "AdapterConnectEvent",
+ Event: &AdapterConnectEvent{
+ AdapterType: adapterType,
+ AdapterModel: model,
+ UserId: userId,
+ UserName: userName,
+ },
+ }
+ Publish(e)
+ return e
+}
+
+// MakeAdapterDisconnectEvent 创建一个适配器断开连接事件
+func MakeAdapterDisconnectEvent() *IceinuEvent {
+ e := &IceinuEvent{
+ Type: 9,
+ From: GetSelfNodeId(),
+ Target: GetMasterNodeId(),
+ Timestamp: time.Now().Unix(),
+ Summary: "AdapterDisconnectEvent",
+ Event: &AdapterDisconnectEvent{
+ OK: true,
+ },
+ }
+ Publish(e)
+ return e
+}
diff --git a/ice/plugin_manager.go b/ice/plugin_manager.go
new file mode 100644
index 0000000..dabb44e
--- /dev/null
+++ b/ice/plugin_manager.go
@@ -0,0 +1,20 @@
+package ice
+
+var PluginVerifier string
+
+// GetPluginVerifier 获取插件的校验值,用于确认各个实例之间的插件版本是否一致
+//
+// 校验是可选的,在氷犬的消息事件系统中实例间的插件是否相同实际上并不影响组网,但是如果插件不同可能会导致一些意想不到的问题
+func GetPluginVerifier(isVerify bool) string {
+ if !isVerify {
+ PluginVerifier = "pass"
+ } else {
+ VerifyPlugin()
+ }
+ return PluginVerifier
+}
+
+// VerifyPlugin 校验插件是否一致,暂时是空函数
+func VerifyPlugin() {
+
+}
diff --git a/log_format.go b/log_format.go
index 8642106..e655eba 100644
--- a/log_format.go
+++ b/log_format.go
@@ -16,7 +16,7 @@ func (f *LogFormatter) Format(entry *logrus.Entry) ([]byte, error) {
var levelText string
switch entry.Level {
case logrus.DebugLevel, logrus.TraceLevel:
- levelColor = gradient.DarkGreen
+ levelColor = gradient.LightGreen
textColor = gradient.DarkGreen
levelText = "🚧DEBUG"
case logrus.InfoLevel:
diff --git a/main.go b/main.go
index c6ac7cc..59ba194 100644
--- a/main.go
+++ b/main.go
@@ -2,6 +2,8 @@ package main
import (
"github.com/Iceinu-Project/IceGradient"
+ "github.com/Iceinu-Project/Iceinu/adapters"
+ "github.com/Iceinu-Project/Iceinu/adapters/lagrange"
"github.com/Iceinu-Project/Iceinu/config"
"github.com/Iceinu-Project/Iceinu/ice"
"github.com/Iceinu-Project/Iceinu/log"
@@ -18,21 +20,31 @@ func main() {
// 初始化内置配置文件读取
config.IceConfigInit()
- // 设置日志级别
- log.SetLevel(config.IceConf.LogLevel)
- log.Debugf("调试模式已启用")
// 输出欢迎日志
log.Infof("欢迎使用🧊" + gradient.Bold +
gradient.GradientText("氷犬 Iceinu Bot", "#00d2ff", "#3a7bd5") + gradient.DarkGray + " | " +
gradient.RGBToANSI(255, 255, 255) +
gradient.GradientBackgroundText(" 通用的模块化 Go 聊天机器人框架 ", "#00d2ff", "#3a7bd5") +
gradient.Reset)
- log.Infof("当前版本: " + gradient.Cyan + "β0.1.4")
+ log.Infof("当前版本: " + gradient.Cyan + "Re:β 0.2.4")
// 初始化数据库连接
ice.InitLocalDatabase()
+ // 设置日志级别
+ log.SetLevel(config.IceConf.LogLevel)
+ log.Debugf("调试模式已启用")
+ // 设置自定义中间件
+ SetCustomMiddleWare()
// 如果启用集群/分布式模式,则尝试和主节点建立连接
// 输出节点Id和集群模式
- log.Info("当前节点模式: " + gradient.DarkGray + config.IceConf.Node.Mode)
- log.Info("Node Id: " + gradient.DarkGray + ice.GetSelfNodeId())
- log.Info("MasterNode Id: " + gradient.DarkGray + ice.GetMasterNodeId())
+ log.Info("当前节点模式: " + gradient.Cyan + config.IceConf.Node.Mode)
+ log.Info("Node Id: " + gradient.Cyan + ice.GetSelfNodeId())
+ log.Info("MasterNode Id: " + gradient.Cyan + ice.GetMasterNodeId())
+ // 启动适配器
+ var adapter adapters.IceinuAdapter
+ adapter = lagrange.GetAdapter()
+ err := adapter.Init()
+ if err != nil {
+ return
+ }
+ adapter.Start()
}
diff --git a/middlewares.go b/middlewares.go
index 4939abf..79e43b2 100644
--- a/middlewares.go
+++ b/middlewares.go
@@ -1,6 +1,41 @@
package main
+import (
+ "github.com/Iceinu-Project/Iceinu/ice"
+ "github.com/Iceinu-Project/Iceinu/log"
+ "github.com/Iceinu-Project/Iceinu/models/satori"
+)
+
// SetCustomMiddleWare 设置自定义中间件
func SetCustomMiddleWare() {
+ // 这个函数中定义了一系列事件总线的中间件,你可以直接参考这些中间件以及氷犬的在线文档来编写中间件
+
+ ice.UseGlobalPublishMiddleware(func(event *ice.IceinuEvent, next func(event *ice.IceinuEvent)) {
+ // 默认的事件监听中间件示例,它会在任意事件发布时运行
+ log.Debugf("接收到来自节点 %s 的 %s 事件", event.From, event.Summary)
+ next(event)
+ })
+
+ ice.UseTypePublishMiddleware(8, func(event *ice.IceinuEvent, next func(event *ice.IceinuEvent)) {
+ // 适配器连接事件监听中间件,它会在适配器连接事件发布时运行
+ // 提取事件里的参数
+ adapterConnectEvent := event.Event.(*ice.AdapterConnectEvent)
+ log.Infof("来自节点 %s 的%s连接成功,机器人Id %s,机器人名称 %s", event.From, adapterConnectEvent.AdapterType, adapterConnectEvent.UserId, adapterConnectEvent.UserName)
+ next(event)
+ })
+
+ ice.UseSummaryPublishMiddleware("PrivateMessageEvent", func(event *ice.IceinuEvent, next func(event *ice.IceinuEvent)) {
+ // Satori私聊消息事件监听中间件,它会在私聊消息事件发布时运行
+ privateMessageEvent := event.Event.(*satori.EventSatori)
+ log.Infof("[%s][%s][私聊]%s@%s:%s", privateMessageEvent.Platform, privateMessageEvent.User.Name, privateMessageEvent.Operator.Id, privateMessageEvent.Operator.Nickname, satori.ElementsToSatori(*privateMessageEvent.Message.MessageElements))
+ next(event)
+ })
+
+ ice.UseSummaryPublishMiddleware("GroupMessageEvent", func(event *ice.IceinuEvent, next func(event *ice.IceinuEvent)) {
+ // Satori群聊消息事件监听中间件,它会在群聊消息事件发布时运行
+ groupMessageEvent := event.Event.(*satori.EventSatori)
+ log.Infof("[%s][%s][群聊][%s]%s@%s:%s", groupMessageEvent.Platform, groupMessageEvent.User.Name, groupMessageEvent.Group.Name, groupMessageEvent.Operator.Id, groupMessageEvent.Operator.Nickname, satori.ElementsToSatori(*groupMessageEvent.Message.MessageElements))
+ next(event)
+ })
}
diff --git a/models/satori/satori_elements.go b/models/satori/satori_elements.go
index 7bab417..cb875ae 100644
--- a/models/satori/satori_elements.go
+++ b/models/satori/satori_elements.go
@@ -1,10 +1,14 @@
package satori
+// 参考:https://satori.js.org/zh-CN/protocol/elements.html
+// 在Satori的标准元素基础上扩展了一部分元素,方便直接使用
+
// ElementSatori Satori标准事件元素接口
type ElementSatori interface {
GetType() string
}
+// TextElement 文本消息元素
type TextElement struct {
Text string
}
@@ -13,6 +17,7 @@ func (t *TextElement) GetType() string {
return "text"
}
+// AtElement 提及用户消息元素
type AtElement struct {
Id string
Name string
@@ -24,6 +29,7 @@ func (a *AtElement) GetType() string {
return "at"
}
+// SharpElement 提及频道消息元素
type SharpElement struct {
Id string
Name string
@@ -33,6 +39,7 @@ func (s *SharpElement) GetType() string {
return "sharp"
}
+// AElement 超链接消息元素
type AElement struct {
Href string
}
@@ -41,6 +48,7 @@ func (a *AElement) GetType() string {
return "a"
}
+// ImgElement 图片消息元素
type ImgElement struct {
Src string
Title string
@@ -54,6 +62,7 @@ func (i *ImgElement) GetType() string {
return "img"
}
+// AudioElement 音频消息元素
type AudioElement struct {
Src string
Title string
@@ -67,6 +76,7 @@ func (a *AudioElement) GetType() string {
return "audio"
}
+// VideoElement 视频消息元素
type VideoElement struct {
Src string
Title string
@@ -82,6 +92,7 @@ func (v *VideoElement) GetType() string {
return "video"
}
+// FileElement 文件消息元素
type FileElement struct {
Src string
Title string
@@ -94,6 +105,7 @@ func (f *FileElement) GetType() string {
return "file"
}
+// StrongElement 加粗消息元素
type StrongElement struct {
Elements *[]ElementSatori
}
@@ -102,6 +114,7 @@ func (s *StrongElement) GetType() string {
return "strong"
}
+// EmElement 斜体消息元素
type EmElement struct {
Elements *[]ElementSatori
}
@@ -110,6 +123,7 @@ func (e *EmElement) GetType() string {
return "em"
}
+// InsElement 下划线消息元素
type InsElement struct {
Elements *[]ElementSatori
}
@@ -118,6 +132,7 @@ func (i *InsElement) GetType() string {
return "ins"
}
+// DelElement 删除线消息元素
type DelElement struct {
Elements *[]ElementSatori
}
@@ -126,6 +141,7 @@ func (d *DelElement) GetType() string {
return "del"
}
+// SpoilerElement 剧透消息元素
type SpoilerElement struct {
Elements *[]ElementSatori
}
@@ -134,6 +150,7 @@ func (s *SpoilerElement) GetType() string {
return "spoiler"
}
+// CodeElement 代码消息元素
type CodeElement struct {
Elements *[]ElementSatori
}
@@ -142,6 +159,7 @@ func (c *CodeElement) GetType() string {
return "code"
}
+// SupElement 上标消息元素
type SupElement struct {
Elements *[]ElementSatori
}
@@ -150,6 +168,7 @@ func (s *SupElement) GetType() string {
return "sup"
}
+// SubElement 下标消息元素
type SubElement struct {
Elements *[]ElementSatori
}
@@ -158,6 +177,7 @@ func (s *SubElement) GetType() string {
return "sub"
}
+// BrElement 换行消息元素
type BrElement struct {
}
@@ -165,6 +185,7 @@ func (b *BrElement) GetType() string {
return "br"
}
+// HrElement 分割线消息元素
type HrElement struct {
}
@@ -172,6 +193,7 @@ func (h *HrElement) GetType() string {
return "hr"
}
+// PElement 段落消息元素
type PElement struct {
Elements *[]ElementSatori
}
@@ -180,6 +202,7 @@ func (p *PElement) GetType() string {
return "p"
}
+// MessageElement 消息元素
type MessageElement struct {
Id string
Forward bool
@@ -190,6 +213,7 @@ func (m *MessageElement) GetType() string {
return "message"
}
+// QuoteElement 引用消息元素
type QuoteElement struct {
Id string
Name string
@@ -203,6 +227,7 @@ func (q *QuoteElement) GetType() string {
return "quote"
}
+// AuthorElement 作者消息元素
type AuthorElement struct {
Id string
Name string
@@ -213,6 +238,7 @@ func (a *AuthorElement) GetType() string {
return "author"
}
+// ButtonElement 按钮消息元素
type ButtonElement struct {
Id string
Type string
@@ -224,3 +250,35 @@ type ButtonElement struct {
func (b *ButtonElement) GetType() string {
return "button"
}
+
+// FaceElement 表情消息元素
+type FaceElement struct {
+ Id uint16
+ IsLargeFace bool
+}
+
+func (f *FaceElement) GetType() string {
+ return "face"
+}
+
+// NodeElement 节点消息元素
+type NodeElement struct {
+ GroupId int64
+ SenderId int64
+ SenderName string
+ Time int32
+ Message *[]ElementSatori
+}
+
+func (n *NodeElement) GetType() string {
+ return "node"
+}
+
+// UnsupportedElement 未支持的消息元素
+type UnsupportedElement struct {
+ Type string
+}
+
+func (u *UnsupportedElement) GetType() string {
+ return "unsupported"
+}
diff --git a/models/satori/satori_model.go b/models/satori/satori_model.go
index c74b048..c421290 100644
--- a/models/satori/satori_model.go
+++ b/models/satori/satori_model.go
@@ -1,7 +1,5 @@
package satori
-import "time"
-
// EventSatori 基于Satori标准设计的事件接收结构体,出于方便使用进行了部分修改和拓展
type EventSatori struct {
Id uint64 `json:"id,omitempty"` // 事件ID
@@ -40,10 +38,10 @@ type Group struct {
// GroupMember 群组成员结构体
type GroupMember struct {
- User *User `json:"user,omitempty"`
- Nickname string `json:"nickname,omitempty"`
- Avatar string `json:"avatar,omitempty"`
- JoinedAt time.Time `json:"joinedAt"`
+ User *User `json:"user,omitempty"`
+ Nickname string `json:"nickname,omitempty"`
+ Avatar string `json:"avatar,omitempty"`
+ JoinedAt int64 `json:"joinedAt"`
}
// GroupRole 群组角色结构体
@@ -82,8 +80,8 @@ type Message struct {
Group *Group `json:"group,omitempty"`
Member *GroupMember `json:"member,omitempty"`
User *User `json:"user,omitempty"`
- CreatedAt time.Time `json:"createdAt"`
- UpdatedAt time.Time `json:"updatedAt"`
+ CreatedAt int64 `json:"createdAt"`
+ UpdatedAt int64 `json:"updatedAt"`
MessageElements *[]ElementSatori `json:"messageElements,omitempty"`
}
diff --git a/models/satori/tools.go b/models/satori/tools.go
index 194b9c5..7a06dfb 100644
--- a/models/satori/tools.go
+++ b/models/satori/tools.go
@@ -1,11 +1,132 @@
package satori
+import (
+ "strconv"
+ "strings"
+)
+
// ParseSatori 从Satori标准XHTML消息中解析出Satori标准事件元素
func ParseSatori(content string) *[]ElementSatori {
return nil
}
// ElementsToSatori 将Satori标准事件元素转换为Satori标准XHTML消息字符串
-func ElementsToSatori(elements *[]ElementSatori) string {
- return ""
+func ElementsToSatori(elements []ElementSatori) string {
+ var sb strings.Builder
+
+ for _, element := range elements {
+ switch e := element.(type) {
+ case *TextElement:
+ sb.WriteString(escapeXML(e.Text))
+ case *AtElement:
+ sb.WriteString(``)
+ case *SharpElement:
+ sb.WriteString(``)
+ case *AElement:
+ sb.WriteString(``)
+ case *ImgElement:
+ sb.WriteString(``)
+ case *AudioElement:
+ sb.WriteString(``)
+ case *VideoElement:
+ sb.WriteString(``)
+ case *FileElement:
+ sb.WriteString(``)
+ case *StrongElement:
+ sb.WriteString(``)
+ sb.WriteString(ElementsToSatori(*e.Elements))
+ sb.WriteString(``)
+ case *EmElement:
+ sb.WriteString(``)
+ sb.WriteString(ElementsToSatori(*e.Elements))
+ sb.WriteString(``)
+ case *InsElement:
+ sb.WriteString(``)
+ sb.WriteString(ElementsToSatori(*e.Elements))
+ sb.WriteString(``)
+ case *DelElement:
+ sb.WriteString(``)
+ sb.WriteString(ElementsToSatori(*e.Elements))
+ sb.WriteString(``)
+ case *SpoilerElement:
+ sb.WriteString(``)
+ sb.WriteString(ElementsToSatori(*e.Elements))
+ sb.WriteString(``)
+ case *CodeElement:
+ sb.WriteString(``)
+ sb.WriteString(ElementsToSatori(*e.Elements))
+ sb.WriteString(`
`)
+ case *SupElement:
+ sb.WriteString(``)
+ sb.WriteString(ElementsToSatori(*e.Elements))
+ sb.WriteString(``)
+ case *SubElement:
+ sb.WriteString(``)
+ sb.WriteString(ElementsToSatori(*e.Elements))
+ sb.WriteString(``)
+ case *BrElement:
+ sb.WriteString(`
`)
+ case *HrElement:
+ sb.WriteString(`
`)
+ case *PElement:
+ sb.WriteString(``)
+ sb.WriteString(ElementsToSatori(*e.Elements))
+ sb.WriteString(`
`)
+ case *MessageElement:
+ sb.WriteString(``)
+ sb.WriteString(ElementsToSatori(*e.Elements))
+ sb.WriteString(``)
+ case *QuoteElement:
+ sb.WriteString(``)
+ sb.WriteString(ElementsToSatori(*e.Elements))
+ sb.WriteString(`
`)
+ case *AuthorElement:
+ sb.WriteString(``)
+ case *ButtonElement:
+ sb.WriteString(``)
+ case *FaceElement:
+ sb.WriteString(``)
+ case *NodeElement:
+ sb.WriteString(``)
+ sb.WriteString(ElementsToSatori(*e.Message))
+ sb.WriteString(``)
+ case *UnsupportedElement:
+ sb.WriteString(``)
+ }
+ }
+
+ return sb.String()
+}
+
+// escapeXML 转义特殊字符
+func escapeXML(s string) string {
+ return strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(s, "&", "&"), "\"", """), "<", "<"), ">", ">")
+}
+
+// boolToString 将布尔值转换为字符串
+func boolToString(b bool) string {
+ if b {
+ return "true"
+ }
+ return "false"
+}
+
+// uint32ToString 将 uint32 转换为字符串
+func uint32ToString(u uint32) string {
+ return strconv.FormatUint(uint64(u), 10)
+}
+
+// int64ToString 将 int64 转换为字符串
+func int64ToString(i int64) string {
+ return strconv.FormatInt(i, 10)
+}
+
+// int32ToString 将 int32 转换为字符串
+func int32ToString(i int32) string {
+ return strconv.FormatInt(int64(i), 10)
+}
+
+// uint16ToString 将 uint16 转换为字符串
+func uint16ToString(u uint16) string {
+ return strconv.FormatUint(uint64(u), 10)
}