From 9e47650032328fbd1215d5630406f90dfee3042a Mon Sep 17 00:00:00 2001 From: Sheyiyuan <2125107118@qq.com> Date: Tue, 22 Apr 2025 12:28:44 +0800 Subject: [PATCH] =?UTF-8?q?add:=E5=AE=8C=E5=96=84=E6=8C=87=E4=BB=A4?= =?UTF-8?q?=E5=A4=84=E7=90=86=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/api_wsd.go | 207 +++++++++++++++++++++ core/{api.go => api_wsp.go} | 355 +++++------------------------------ core/api_wst.go | 143 ++++++++++++++ core/app_admin.go | 353 +++++++++++++++++++---------------- core/app_core.go | 51 ++--- core/events_handler.go | 145 ++++++--------- core/selector_doc.md | 48 +++++ core/web_socket.go | 4 +- wba/app.go | 50 ++++- wba/selector.go | 360 ++++++++++++++++++++++++++++++++++++ wba/wind_standard_tools.go | 79 ++++---- 11 files changed, 1163 insertions(+), 632 deletions(-) create mode 100644 core/api_wsd.go rename core/{api.go => api_wsp.go} (69%) create mode 100644 core/api_wst.go create mode 100644 core/selector_doc.md create mode 100644 wba/selector.go diff --git a/core/api_wsd.go b/core/api_wsd.go new file mode 100644 index 0000000..7a5826e --- /dev/null +++ b/core/api_wsd.go @@ -0,0 +1,207 @@ +package core + +import ( + "ProjectWIND/database" + "ProjectWIND/wba" + "fmt" +) + +//database模块 +// //数据库部分允许字符串变量的读写操作,允许读取配置项操作 + +type databaseInfo struct{} + +func (dbi *databaseInfo) varSet(app wba.AppInfo, datamap string, unit string, id string, key string, value string) { + database.Set(app.AppKey.Name, datamap, unit, id, key, value) +} + +func (dbi *databaseInfo) SetUserVariable(app wba.AppInfo, msg wba.MessageEventInfo, key string, value string) { + id := fmt.Sprintf("%d", msg.UserId) + dbi.varSet(app, app.AppKey.Name, "user", id, key, value) +} + +func (dbi *databaseInfo) SetGroupVariable(app wba.AppInfo, msg wba.MessageEventInfo, key string, value string) { + var id string + if msg.MessageType == "group" { + id = "group_" + fmt.Sprintf("%d", msg.GroupId) + } + if msg.MessageType == "private" { + id = "user_" + fmt.Sprintf("%d", msg.UserId) + } + dbi.varSet(app, app.AppKey.Name, "group", id, key, value) +} + +func (dbi *databaseInfo) SetOutUserVariable(app wba.AppInfo, datamap string, msg wba.MessageEventInfo, key string, value string) { + id := fmt.Sprintf("%d", msg.UserId) + dbi.varSet(app, datamap, "user", id, key, value) +} + +func (dbi *databaseInfo) SetOutGroupVariable(app wba.AppInfo, datamap string, msg wba.MessageEventInfo, key string, value string) { + var id string + if msg.MessageType == "group" { + id = "group_" + fmt.Sprintf("%d", msg.GroupId) + } + if msg.MessageType == "private" { + id = "user_" + fmt.Sprintf("%d", msg.UserId) + } + dbi.varSet(app, datamap, "group", id, key, value) +} + +func (dbi *databaseInfo) UnsafelySetUserVariable(app wba.AppInfo, id string, key string, value string) { + dbi.varSet(app, app.AppKey.Name, "user", id, key, value) +} + +func (dbi *databaseInfo) UnsafelySetGroupVariable(app wba.AppInfo, id string, key string, value string) { + dbi.varSet(app, app.AppKey.Name, "group", id, key, value) +} + +func (dbi *databaseInfo) UnsafelySetGlobalVariable(app wba.AppInfo, id string, key string, value string) { + dbi.varSet(app, app.AppKey.Name, "global", id, key, value) +} + +func (dbi *databaseInfo) UnsafelySetOutUserVariable(app wba.AppInfo, datamap string, id string, key string, value string) { + dbi.varSet(app, datamap, "user", id, key, value) +} + +func (dbi *databaseInfo) UnsafelySetOutGroupVariable(app wba.AppInfo, datamap string, id string, key string, value string) { + dbi.varSet(app, datamap, "group", id, key, value) +} + +func (dbi *databaseInfo) UnsafelySetOutGlobalVariable(app wba.AppInfo, datamap string, id string, key string, value string) { + dbi.varSet(app, datamap, "global", id, key, value) +} + +func (dbi *databaseInfo) varGet(app wba.AppInfo, datamap string, unit string, id string, key string) (string, bool) { + res, ok := database.Get(app.AppKey.Name, datamap, unit, id, key, false) + if !ok { + return "", false + } + resStr, ok := res.(string) + if !ok { + return "", false + } + return resStr, true +} + +func (dbi *databaseInfo) GetUserVariable(app wba.AppInfo, msg wba.MessageEventInfo, key string) (string, bool) { + id := fmt.Sprintf("%d", msg.UserId) + return dbi.varGet(app, app.AppKey.Name, "user", id, key) +} + +func (dbi *databaseInfo) GetGroupVariable(app wba.AppInfo, msg wba.MessageEventInfo, key string) (string, bool) { + var id string + if msg.MessageType == "group" { + id = "group_" + fmt.Sprintf("%d", msg.GroupId) + } + if msg.MessageType == "private" { + id = "user_" + fmt.Sprintf("%d", msg.UserId) + } + return dbi.varGet(app, app.AppKey.Name, "group", id, key) +} + +func (dbi *databaseInfo) GetOutUserVariable(app wba.AppInfo, datamap string, msg wba.MessageEventInfo, key string) (string, bool) { + id := fmt.Sprintf("%d", msg.UserId) + return dbi.varGet(app, datamap, "user", id, key) +} + +func (dbi *databaseInfo) GetOutGroupVariable(app wba.AppInfo, datamap string, msg wba.MessageEventInfo, key string) (string, bool) { + var id string + if msg.MessageType == "group" { + id = "group_" + fmt.Sprintf("%d", msg.GroupId) + } + if msg.MessageType == "private" { + id = "user_" + fmt.Sprintf("%d", msg.UserId) + } + return dbi.varGet(app, datamap, "group", id, key) +} + +func (dbi *databaseInfo) UnsafelyGetUserVariable(app wba.AppInfo, id string, key string) (string, bool) { + return dbi.varGet(app, app.AppKey.Name, "user", id, key) +} + +func (dbi *databaseInfo) UnsafelyGetGroupVariable(app wba.AppInfo, id string, key string) (string, bool) { + return dbi.varGet(app, app.AppKey.Name, "group", id, key) +} + +func (dbi *databaseInfo) UnsafelyGetGlobalVariable(app wba.AppInfo, id string, key string) (string, bool) { + return dbi.varGet(app, app.AppKey.Name, "global", id, key) +} + +func (dbi *databaseInfo) UnsafelyGetOutUserVariable(app wba.AppInfo, datamap string, id string, key string) (string, bool) { + return dbi.varGet(app, datamap, "user", id, key) +} + +func (dbi *databaseInfo) UnsafelyGetOutGroupVariable(app wba.AppInfo, datamap string, id string, key string) (string, bool) { + return dbi.varGet(app, datamap, "group", id, key) +} + +func (dbi *databaseInfo) UnsafelyGetOutGlobalVariable(app wba.AppInfo, datamap string, id string, key string) (string, bool) { + return dbi.varGet(app, datamap, "global", id, key) +} + +func (dbi *databaseInfo) GetIntConfig(app wba.AppInfo, datamap string, key string) (int64, bool) { + res, ok := database.Get(app.AppKey.Name, datamap, "config", "number", key, true) + if !ok { + return 0, false + } + resInt, ok := res.(int64) + if !ok { + return 0, false + } + return resInt, true +} + +func (dbi *databaseInfo) GetStringConfig(app wba.AppInfo, datamap string, key string) (string, bool) { + res, ok := database.Get(app.AppKey.Name, datamap, "config", "string", key, true) + if !ok { + return "", false + } + resStr, ok := res.(string) + if !ok { + return "", false + } + return resStr, true +} + +func (dbi *databaseInfo) GetFloatConfig(app wba.AppInfo, datamap string, key string) (float64, bool) { + res, ok := database.Get(app.AppKey.Name, datamap, "config", "float", key, true) + if !ok { + return 0, false + } + resFloat, ok := res.(float64) + if !ok { + return 0, false + } + return resFloat, true +} + +func (dbi *databaseInfo) GetIntSliceConfig(app wba.AppInfo, datamap string, key string) ([]int64, bool) { + res, ok := database.Get(app.AppKey.Name, datamap, "config", "number_slice", key, true) + if !ok { + return nil, false + } + resSlice, ok := res.([]int64) + if !ok { + return nil, false + } + return resSlice, true +} + +func (dbi *databaseInfo) GetStringSliceConfig(app wba.AppInfo, datamap string, key string) ([]string, bool) { + res, ok := database.Get(app.AppKey.Name, datamap, "config", "string_slice", key, true) + if !ok { + return nil, false + } + resSlice, ok := res.([]string) + if !ok { + return nil, false + } + return resSlice, true +} + +func (dbi *databaseInfo) UnsafelyCreatePublicDatamap(app wba.AppInfo, datamapId string) { + appName := app.AppKey.Name + database.CreatePublicDatamap(appName, datamapId) +} + +var DatabaseApi databaseInfo diff --git a/core/api.go b/core/api_wsp.go similarity index 69% rename from core/api.go rename to core/api_wsp.go index 6b8d415..5d3051f 100644 --- a/core/api.go +++ b/core/api_wsp.go @@ -2,15 +2,12 @@ package core import ( "ProjectWIND/LOG" - "ProjectWIND/database" "ProjectWIND/wba" "crypto/rand" - "encoding/json" "fmt" - "strings" ) -type apiInfo struct{} +type protocolAPI struct{} //一、Protocol模块 @@ -41,7 +38,7 @@ type apiInfo struct{} // - message: 要发送的消息内容,类型为字符串 // // - autoEscape: 是否自动转义(解析消息内容中的CQ码),可选值为 true 和 false -func (a *apiInfo) UnsafelySendMsg(messageType string, groupId int64, userId int64, message string, autoEscape bool) { +func (p *protocolAPI) UnsafelySendMsg(messageType string, groupId int64, userId int64, message string, autoEscape bool) { // 构建发送消息的JSON数据 var messageData wba.APIRequestInfo messageData.Action = "send_msg" @@ -84,7 +81,7 @@ func (a *apiInfo) UnsafelySendMsg(messageType string, groupId int64, userId int6 // - message: 要发送的消息内容,类型为字符串 // // - autoEscape: 是否自动转义(解析消息内容中的CQ码),可选值为 true 和 false -func (a *apiInfo) UnsafelySendPrivateMsg(userId int64, message string, autoEscape bool) { +func (p *protocolAPI) UnsafelySendPrivateMsg(userId int64, message string, autoEscape bool) { // 构建发送消息的JSON数据 var messageData wba.APIRequestInfo messageData.Action = "send_private_msg" @@ -112,7 +109,7 @@ func (a *apiInfo) UnsafelySendPrivateMsg(userId int64, message string, autoEscap // - message: 要发送的消息内容,类型为字符串 // // - autoEscape: 是否自动转义(解析消息内容中的CQ码),可选值为 true 和 false -func (a *apiInfo) UnsafelySendGroupMsg(groupId int64, message string, autoEscape bool) { +func (p *protocolAPI) UnsafelySendGroupMsg(groupId int64, message string, autoEscape bool) { // 构建发送消息的JSON数据 var messageData wba.APIRequestInfo messageData.Action = "send_group_msg" @@ -138,7 +135,7 @@ func (a *apiInfo) UnsafelySendGroupMsg(groupId int64, message string, autoEscape // - message: 要发送的消息内容,类型为字符串 // // - autoEscape: 是否自动转义(解析消息内容中的CQ码),可选值为 true 和 false -func (a *apiInfo) SendMsg(msg wba.MessageEventInfo, message string, autoEscape bool) { +func (p *protocolAPI) SendMsg(msg wba.MessageEventInfo, message string, autoEscape bool) { // 构建发送消息的JSON数据 var messageData wba.APIRequestInfo @@ -182,7 +179,7 @@ func (a *apiInfo) SendMsg(msg wba.MessageEventInfo, message string, autoEscape b // - message: 要发送的消息内容,类型为字符串 // // - autoEscape: 是否自动转义,可选值为 true 和 false -func (a *apiInfo) SendPrivateMsg(msg wba.MessageEventInfo, message string, autoEscape bool) { +func (p *protocolAPI) SendPrivateMsg(msg wba.MessageEventInfo, message string, autoEscape bool) { // 构建发送消息的JSON数据 var messageData wba.APIRequestInfo messageData.Action = "send_private_msg" @@ -208,7 +205,7 @@ func (a *apiInfo) SendPrivateMsg(msg wba.MessageEventInfo, message string, autoE // - message: 要发送的消息内容,类型为字符串 // // - autoEscape: 是否自动转义,可选值为 true 和 false -func (a *apiInfo) SendGroupMsg(msg wba.MessageEventInfo, message string, autoEscape bool) { +func (p *protocolAPI) SendGroupMsg(msg wba.MessageEventInfo, message string, autoEscape bool) { // 构建发送消息的JSON数据 var messageData wba.APIRequestInfo messageData.Action = "send_group_msg" @@ -232,7 +229,7 @@ func (a *apiInfo) SendGroupMsg(msg wba.MessageEventInfo, message string, autoEsc // 参数: // // - messageId: 要撤回的消息ID -func (a *apiInfo) UnsafelyDeleteMsg(messageId int32) { +func (p *protocolAPI) UnsafelyDeleteMsg(messageId int32) { // 构建删除消息的JSON数据 var messageData wba.APIRequestInfo messageData.Action = "delete_msg" @@ -251,7 +248,7 @@ func (a *apiInfo) UnsafelyDeleteMsg(messageId int32) { // 参数: // // - msg: 原始消息事件信息 -func (a *apiInfo) DeleteMsg(msg wba.MessageEventInfo) { +func (p *protocolAPI) DeleteMsg(msg wba.MessageEventInfo) { // 构建删除消息的JSON数据 var messageData wba.APIRequestInfo messageData.Action = "delete_msg" @@ -272,7 +269,7 @@ func (a *apiInfo) DeleteMsg(msg wba.MessageEventInfo) { // - userId: 要赞的用户ID // // - times: 赞的次数 -func (a *apiInfo) SendLike(userId int64, times int) { +func (p *protocolAPI) SendLike(userId int64, times int) { // 构建发送赞的JSON数据 var messageData wba.APIRequestInfo messageData.Action = "send_like" @@ -296,7 +293,7 @@ func (a *apiInfo) SendLike(userId int64, times int) { // - userId: 用户ID // // - rejectAddRequest: 是否拒绝该用户的后续加群请求,可选值为 true 和 false -func (a *apiInfo) SetGroupKick(groupId int64, userId int64, rejectAddRequest bool) { +func (p *protocolAPI) SetGroupKick(groupId int64, userId int64, rejectAddRequest bool) { var messageData wba.APIRequestInfo messageData.Action = "set_group_kick" messageData.Params.GroupId = groupId @@ -320,7 +317,7 @@ func (a *apiInfo) SetGroupKick(groupId int64, userId int64, rejectAddRequest boo // - userId: 用户ID // // - duration: 禁言时长,单位为秒,0表示取消禁言 -func (a *apiInfo) SetGroupBan(groupId int64, userId int64, duration int32) { +func (p *protocolAPI) SetGroupBan(groupId int64, userId int64, duration int32) { var messageData wba.APIRequestInfo messageData.Action = "set_group_ban" messageData.Params.GroupId = groupId @@ -342,7 +339,7 @@ func (a *apiInfo) SetGroupBan(groupId int64, userId int64, duration int32) { // - groupId: 群号 // // - enable: 是否启用全员禁言,可选值为 true 和 false -func (a *apiInfo) SetGroupWholeBan(groupId int64, enable bool) { +func (p *protocolAPI) SetGroupWholeBan(groupId int64, enable bool) { var messageData wba.APIRequestInfo messageData.Action = "set_group_whole_ban" messageData.Params.GroupId = groupId @@ -365,7 +362,7 @@ func (a *apiInfo) SetGroupWholeBan(groupId int64, enable bool) { // - userId: 用户ID // // - enable: 是否设置为管理员,可选值为 true 和 false -func (a *apiInfo) SetGroupAdmin(groupId int64, userId int64, enable bool) { +func (p *protocolAPI) SetGroupAdmin(groupId int64, userId int64, enable bool) { var messageData wba.APIRequestInfo messageData.Action = "set_group_admin" messageData.Params.GroupId = groupId @@ -389,7 +386,7 @@ func (a *apiInfo) SetGroupAdmin(groupId int64, userId int64, enable bool) { // - userId: 用户ID // // - card: 新的群名片 -func (a *apiInfo) SetGroupCard(groupId int64, userId int64, card string) { +func (p *protocolAPI) SetGroupCard(groupId int64, userId int64, card string) { var messageData wba.APIRequestInfo messageData.Action = "set_group_card" messageData.Params.GroupId = groupId @@ -411,7 +408,7 @@ func (a *apiInfo) SetGroupCard(groupId int64, userId int64, card string) { // - groupId: 群号 // // - groupName: 新的群名称 -func (a *apiInfo) SetGroupName(groupId int64, groupName string) { +func (p *protocolAPI) SetGroupName(groupId int64, groupName string) { var messageData wba.APIRequestInfo messageData.Action = "set_group_name" messageData.Params.GroupId = groupId @@ -432,7 +429,7 @@ func (a *apiInfo) SetGroupName(groupId int64, groupName string) { // - groupId: 群号 // // - isDismiss: 是否解散群聊,仅当退出的是群主时有效,可选值为 true 和 false -func (a *apiInfo) SetGroupLeave(groupId int64, isDismiss bool) { +func (p *protocolAPI) SetGroupLeave(groupId int64, isDismiss bool) { var messageData wba.APIRequestInfo messageData.Action = "set_group_leave" messageData.Params.GroupId = groupId @@ -455,7 +452,7 @@ func (a *apiInfo) SetGroupLeave(groupId int64, isDismiss bool) { // - userId: 用户ID // // - specialTitle: 新的专属头衔 -func (a *apiInfo) SetGroupSpecialTitle(groupId int64, userId int64, specialTitle string) { +func (p *protocolAPI) SetGroupSpecialTitle(groupId int64, userId int64, specialTitle string) { var messageData wba.APIRequestInfo messageData.Action = "set_group_special_title" messageData.Params.GroupId = groupId @@ -480,7 +477,7 @@ func (a *apiInfo) SetGroupSpecialTitle(groupId int64, userId int64, specialTitle // - approve: 是否同意请求,可选值为 true 和 false // // - remark: 设置好友的备注信息 -func (a *apiInfo) SetFriendAddRequest(flag string, approve bool, remark string) { +func (p *protocolAPI) SetFriendAddRequest(flag string, approve bool, remark string) { var messageData wba.APIRequestInfo messageData.Action = "set_friend_add_request" messageData.Params.Flag = flag @@ -506,7 +503,7 @@ func (a *apiInfo) SetFriendAddRequest(flag string, approve bool, remark string) // - approve: 是否同意请求,可选值为 true 和 false // // - reason: 拒绝请求的原因,仅当 approve 为 false 时有效 -func (a *apiInfo) SetGroupAddRequest(flag string, subType string, approve bool, reason string) { +func (p *protocolAPI) SetGroupAddRequest(flag string, subType string, approve bool, reason string) { var messageData wba.APIRequestInfo messageData.Action = "set_group_add_request" messageData.Params.Flag = flag @@ -523,7 +520,7 @@ func (a *apiInfo) SetGroupAddRequest(flag string, subType string, approve bool, } // SetRestart 重启 -func (a *apiInfo) SetRestart(delay int32) { +func (p *protocolAPI) SetRestart(delay int32) { var messageData wba.APIRequestInfo messageData.Action = "set_restart" messageData.Params.Delay = delay @@ -537,7 +534,7 @@ func (a *apiInfo) SetRestart(delay int32) { } // CleanCache 清理缓存 -func (a *apiInfo) CleanCache() { +func (p *protocolAPI) CleanCache() { var messageData wba.APIRequestInfo messageData.Action = "clean_cache" _, err := wsAPI(messageData) @@ -552,7 +549,7 @@ func (a *apiInfo) CleanCache() { // 2.有响应API,需添加echo字段,统一返回响应结构体 // GetLoginInfo 获取登录信息 -func (a *apiInfo) GetLoginInfo() (Response wba.APIResponseInfo) { +func (p *protocolAPI) GetLoginInfo() (Response wba.APIResponseInfo) { LOG.Info("获取登录信息(GetLoginInfo)") var messageData wba.APIRequestInfo var err error @@ -571,7 +568,7 @@ func (a *apiInfo) GetLoginInfo() (Response wba.APIResponseInfo) { } // GetVersionInfo 获取协议信息 -func (a *apiInfo) GetVersionInfo() (Response wba.APIResponseInfo) { +func (p *protocolAPI) GetVersionInfo() (Response wba.APIResponseInfo) { LOG.Info("获取协议信息(GetVersionInfo)") var messageData wba.APIRequestInfo var err error @@ -590,7 +587,7 @@ func (a *apiInfo) GetVersionInfo() (Response wba.APIResponseInfo) { } // GetMsg 获取消息 -func (a *apiInfo) GetMsg(messageId int32) (Response wba.APIResponseInfo) { +func (p *protocolAPI) GetMsg(messageId int32) (Response wba.APIResponseInfo) { LOG.Info("获取消息(GetMsg)") var messageData wba.APIRequestInfo var err error @@ -610,7 +607,7 @@ func (a *apiInfo) GetMsg(messageId int32) (Response wba.APIResponseInfo) { } // GetForwardMsg 获取合并转发消息 -func (a *apiInfo) GetForwardMsg(id string) (Response wba.APIResponseInfo) { +func (p *protocolAPI) GetForwardMsg(id string) (Response wba.APIResponseInfo) { LOG.Info("获取合并转发消息(GetForwardMsg)") var messageData wba.APIRequestInfo var err error @@ -630,7 +627,7 @@ func (a *apiInfo) GetForwardMsg(id string) (Response wba.APIResponseInfo) { } // GetStrangerInfo 获取陌生人信息 -func (a *apiInfo) GetStrangerInfo(userId int64, noCache bool) (Response wba.APIResponseInfo) { +func (p *protocolAPI) GetStrangerInfo(userId int64, noCache bool) (Response wba.APIResponseInfo) { LOG.Info("获取陌生人信息(GetStrangerInfo)") var messageData wba.APIRequestInfo var err error @@ -651,7 +648,7 @@ func (a *apiInfo) GetStrangerInfo(userId int64, noCache bool) (Response wba.APIR } // GetFriendList 获取好友列表 -func (a *apiInfo) GetFriendList() (Response wba.APIResponseInfo) { +func (p *protocolAPI) GetFriendList() (Response wba.APIResponseInfo) { LOG.Info("获取好友列表(GetFriendList)") var messageData wba.APIRequestInfo var err error @@ -670,7 +667,7 @@ func (a *apiInfo) GetFriendList() (Response wba.APIResponseInfo) { } // GetGroupList 获取群列表 -func (a *apiInfo) GetGroupList() (Response wba.APIResponseInfo) { +func (p *protocolAPI) GetGroupList() (Response wba.APIResponseInfo) { LOG.Info("获取群列表(GetGroupList)") var messageData wba.APIRequestInfo var err error @@ -689,7 +686,7 @@ func (a *apiInfo) GetGroupList() (Response wba.APIResponseInfo) { } // GetGroupInfo 获取群信息 -func (a *apiInfo) GetGroupInfo(groupId int64, noCache bool) (Response wba.APIResponseInfo) { +func (p *protocolAPI) GetGroupInfo(groupId int64, noCache bool) (Response wba.APIResponseInfo) { LOG.Info("获取群信息(GetGroupInfo)") var messageData wba.APIRequestInfo var err error @@ -710,7 +707,7 @@ func (a *apiInfo) GetGroupInfo(groupId int64, noCache bool) (Response wba.APIRes } // GetGroupMemberInfo 获取群成员信息 -func (a *apiInfo) GetGroupMemberInfo(groupId int64, userId int64, noCache bool) (Response wba.APIResponseInfo) { +func (p *protocolAPI) GetGroupMemberInfo(groupId int64, userId int64, noCache bool) (Response wba.APIResponseInfo) { LOG.Info("获取群成员信息(GetGroupMemberInfo)") var messageData wba.APIRequestInfo var err error @@ -732,7 +729,7 @@ func (a *apiInfo) GetGroupMemberInfo(groupId int64, userId int64, noCache bool) } // GetGroupMemberList 获取群成员列表 -func (a *apiInfo) GetGroupMemberList(groupId int64) (Response wba.APIResponseInfo) { +func (p *protocolAPI) GetGroupMemberList(groupId int64) (Response wba.APIResponseInfo) { LOG.Info("获取群成员列表(GetGroupMemberList)") var messageData wba.APIRequestInfo var err error @@ -752,7 +749,7 @@ func (a *apiInfo) GetGroupMemberList(groupId int64) (Response wba.APIResponseInf } // GetGroupHonorInfo 获取群荣誉信息 -func (a *apiInfo) GetGroupHonorInfo(groupId int64, Type string) (Response wba.APIResponseInfo) { +func (p *protocolAPI) GetGroupHonorInfo(groupId int64, Type string) (Response wba.APIResponseInfo) { LOG.Info("获取群荣誉信息(GetGroupHonorInfo)") var messageData wba.APIRequestInfo var err error @@ -773,7 +770,7 @@ func (a *apiInfo) GetGroupHonorInfo(groupId int64, Type string) (Response wba.AP } // GetCookies 获取Cookies -func (a *apiInfo) GetCookies(domain string) (Response wba.APIResponseInfo) { +func (p *protocolAPI) GetCookies(domain string) (Response wba.APIResponseInfo) { LOG.Info("获取Cookies(GetCookies)") var messageData wba.APIRequestInfo var err error @@ -793,7 +790,7 @@ func (a *apiInfo) GetCookies(domain string) (Response wba.APIResponseInfo) { } // GetCSRFToken 获取CSRF Token -func (a *apiInfo) GetCSRFToken() (Response wba.APIResponseInfo) { +func (p *protocolAPI) GetCSRFToken() (Response wba.APIResponseInfo) { LOG.Info("获取CSRF Token(GetCSRFToken)") var messageData wba.APIRequestInfo var err error @@ -812,7 +809,7 @@ func (a *apiInfo) GetCSRFToken() (Response wba.APIResponseInfo) { } // GetCredentials 获取登录令牌 -func (a *apiInfo) GetCredentials(domain string) (Response wba.APIResponseInfo) { +func (p *protocolAPI) GetCredentials(domain string) (Response wba.APIResponseInfo) { LOG.Info("获取登录令牌(GetCredentials)") var messageData wba.APIRequestInfo var err error @@ -832,7 +829,7 @@ func (a *apiInfo) GetCredentials(domain string) (Response wba.APIResponseInfo) { } // GetRecord 获取语音 -func (a *apiInfo) GetRecord(file string, outFormat string) (Response wba.APIResponseInfo) { +func (p *protocolAPI) GetRecord(file string, outFormat string) (Response wba.APIResponseInfo) { LOG.Info("获取语音(GetRecord)") var messageData wba.APIRequestInfo var err error @@ -853,7 +850,7 @@ func (a *apiInfo) GetRecord(file string, outFormat string) (Response wba.APIResp } // GetImage 获取图片 -func (a *apiInfo) GetImage(file string) (Response wba.APIResponseInfo) { +func (p *protocolAPI) GetImage(file string) (Response wba.APIResponseInfo) { LOG.Info("获取图片(GetImage)") var messageData wba.APIRequestInfo var err error @@ -873,7 +870,7 @@ func (a *apiInfo) GetImage(file string) (Response wba.APIResponseInfo) { } // CanSendImage 检查是否可以发送图片 -func (a *apiInfo) CanSendImage() (Response wba.APIResponseInfo) { +func (p *protocolAPI) CanSendImage() (Response wba.APIResponseInfo) { LOG.Info("检查是否可以发送图片(CanSendImage)") var messageData wba.APIRequestInfo var err error @@ -892,7 +889,7 @@ func (a *apiInfo) CanSendImage() (Response wba.APIResponseInfo) { } // CanSendRecord 检查是否可以发送语音 -func (a *apiInfo) CanSendRecord() (Response wba.APIResponseInfo) { +func (p *protocolAPI) CanSendRecord() (Response wba.APIResponseInfo) { LOG.Info("检查是否可以发送语音(CanSendRecord)") var messageData wba.APIRequestInfo var err error @@ -911,7 +908,7 @@ func (a *apiInfo) CanSendRecord() (Response wba.APIResponseInfo) { } // GetStatus 获取状态 -func (a *apiInfo) GetStatus() (Response wba.APIResponseInfo) { +func (p *protocolAPI) GetStatus() (Response wba.APIResponseInfo) { LOG.Info("获取状态(GetStatus)") var messageData wba.APIRequestInfo var err error @@ -929,273 +926,6 @@ func (a *apiInfo) GetStatus() (Response wba.APIResponseInfo) { return Response } -//二、LOG模块 - -/* -关于LOG模块的说明 - -1.日志模块级别分为TRACE、DEBUG、INFO、NOTICE、WARN、ERROR五个级别,默认级别为INFO。 - -2.日志模块提供LogWith方法,可以自定义日志级别。 - -3.日志模块提供Log方法,默认日志级别为INFO。 -*/ - -// LogWith 打印日志(带级别) -// -// 参数: -// - level: 日志级别,支持"TRACE"、"DEBUG"、"INFO"、"NOTICE"、 "WARN"、"ERROR" -// - content: 日志内容 -// - args: 可选参数,用于格式化日志内容 -// -// 返回值: -// - 无 -func (a *apiInfo) LogWith(level string, content string, args ...interface{}) { - level = strings.ToLower(level) - switch level { - case "trace": - LOG.Trace(content, args...) - return - case "debug": - LOG.Debug(content, args...) - return - case "notice": - LOG.Notice(content, args...) - return - case "warn": - LOG.Warn(content, args...) - return - case "error": - LOG.Error(content, args...) - return - default: - LOG.Info(content, args...) - return - } -} - -// Log 打印日志 -// -// 参数: -// - content: 日志内容 -// - args: 可选参数,用于格式化日志内容 -func (a *apiInfo) Log(content string, args ...interface{}) { - LOG.Info(content, args...) -} - -// MsgUnmarshal 解析消息 -// -// 参数: -// - messageJSON: 从数据库中获取的JSON序列化后的消息字符串 -// -// 返回值: -// - wba.MessageEventInfo: 解析后的消息事件信息,解析失败时返回空结构体 -func (a *apiInfo) MsgUnmarshal(messageJSON string) (msg wba.MessageEventInfo) { - err := json.Unmarshal([]byte(messageJSON), &msg) - if err != nil { - return wba.MessageEventInfo{} - } - return msg -} - -//database模块 -// //数据库部分允许字符串变量的读写操作,允许读取配置项操作 - -type databaseInfo struct{} - -func (dbi *databaseInfo) varSet(app wba.AppInfo, datamap string, unit string, id string, key string, value string) { - database.Set(app.AppKey.Name, datamap, unit, id, key, value) -} - -func (dbi *databaseInfo) SetUserVariable(app wba.AppInfo, msg wba.MessageEventInfo, key string, value string) { - id := fmt.Sprintf("%d", msg.UserId) - dbi.varSet(app, app.AppKey.Name, "user", id, key, value) -} - -func (dbi *databaseInfo) SetGroupVariable(app wba.AppInfo, msg wba.MessageEventInfo, key string, value string) { - var id string - if msg.MessageType == "group" { - id = "group_" + fmt.Sprintf("%d", msg.GroupId) - } - if msg.MessageType == "private" { - id = "user_" + fmt.Sprintf("%d", msg.UserId) - } - dbi.varSet(app, app.AppKey.Name, "group", id, key, value) -} - -func (dbi *databaseInfo) SetOutUserVariable(app wba.AppInfo, datamap string, msg wba.MessageEventInfo, key string, value string) { - id := fmt.Sprintf("%d", msg.UserId) - dbi.varSet(app, datamap, "user", id, key, value) -} - -func (dbi *databaseInfo) SetOutGroupVariable(app wba.AppInfo, datamap string, msg wba.MessageEventInfo, key string, value string) { - var id string - if msg.MessageType == "group" { - id = "group_" + fmt.Sprintf("%d", msg.GroupId) - } - if msg.MessageType == "private" { - id = "user_" + fmt.Sprintf("%d", msg.UserId) - } - dbi.varSet(app, datamap, "group", id, key, value) -} - -func (dbi *databaseInfo) UnsafelySetUserVariable(app wba.AppInfo, id string, key string, value string) { - dbi.varSet(app, app.AppKey.Name, "user", id, key, value) -} - -func (dbi *databaseInfo) UnsafelySetGroupVariable(app wba.AppInfo, id string, key string, value string) { - dbi.varSet(app, app.AppKey.Name, "group", id, key, value) -} - -func (dbi *databaseInfo) UnsafelySetGlobalVariable(app wba.AppInfo, id string, key string, value string) { - dbi.varSet(app, app.AppKey.Name, "global", id, key, value) -} - -func (dbi *databaseInfo) UnsafelySetOutUserVariable(app wba.AppInfo, datamap string, id string, key string, value string) { - dbi.varSet(app, datamap, "user", id, key, value) -} - -func (dbi *databaseInfo) UnsafelySetOutGroupVariable(app wba.AppInfo, datamap string, id string, key string, value string) { - dbi.varSet(app, datamap, "group", id, key, value) -} - -func (dbi *databaseInfo) UnsafelySetOutGlobalVariable(app wba.AppInfo, datamap string, id string, key string, value string) { - dbi.varSet(app, datamap, "global", id, key, value) -} - -func (dbi *databaseInfo) varGet(app wba.AppInfo, datamap string, unit string, id string, key string) (string, bool) { - res, ok := database.Get(app.AppKey.Name, datamap, unit, id, key, false) - if !ok { - return "", false - } - resStr, ok := res.(string) - if !ok { - return "", false - } - return resStr, true -} - -func (dbi *databaseInfo) GetUserVariable(app wba.AppInfo, msg wba.MessageEventInfo, key string) (string, bool) { - id := fmt.Sprintf("%d", msg.UserId) - return dbi.varGet(app, app.AppKey.Name, "user", id, key) -} - -func (dbi *databaseInfo) GetGroupVariable(app wba.AppInfo, msg wba.MessageEventInfo, key string) (string, bool) { - var id string - if msg.MessageType == "group" { - id = "group_" + fmt.Sprintf("%d", msg.GroupId) - } - if msg.MessageType == "private" { - id = "user_" + fmt.Sprintf("%d", msg.UserId) - } - return dbi.varGet(app, app.AppKey.Name, "group", id, key) -} - -func (dbi *databaseInfo) GetOutUserVariable(app wba.AppInfo, datamap string, msg wba.MessageEventInfo, key string) (string, bool) { - id := fmt.Sprintf("%d", msg.UserId) - return dbi.varGet(app, datamap, "user", id, key) -} - -func (dbi *databaseInfo) GetOutGroupVariable(app wba.AppInfo, datamap string, msg wba.MessageEventInfo, key string) (string, bool) { - var id string - if msg.MessageType == "group" { - id = "group_" + fmt.Sprintf("%d", msg.GroupId) - } - if msg.MessageType == "private" { - id = "user_" + fmt.Sprintf("%d", msg.UserId) - } - return dbi.varGet(app, datamap, "group", id, key) -} - -func (dbi *databaseInfo) UnsafelyGetUserVariable(app wba.AppInfo, id string, key string) (string, bool) { - return dbi.varGet(app, app.AppKey.Name, "user", id, key) -} - -func (dbi *databaseInfo) UnsafelyGetGroupVariable(app wba.AppInfo, id string, key string) (string, bool) { - return dbi.varGet(app, app.AppKey.Name, "group", id, key) -} - -func (dbi *databaseInfo) UnsafelyGetGlobalVariable(app wba.AppInfo, id string, key string) (string, bool) { - return dbi.varGet(app, app.AppKey.Name, "global", id, key) -} - -func (dbi *databaseInfo) UnsafelyGetOutUserVariable(app wba.AppInfo, datamap string, id string, key string) (string, bool) { - return dbi.varGet(app, datamap, "user", id, key) -} - -func (dbi *databaseInfo) UnsafelyGetOutGroupVariable(app wba.AppInfo, datamap string, id string, key string) (string, bool) { - return dbi.varGet(app, datamap, "group", id, key) -} - -func (dbi *databaseInfo) UnsafelyGetOutGlobalVariable(app wba.AppInfo, datamap string, id string, key string) (string, bool) { - return dbi.varGet(app, datamap, "global", id, key) -} - -func (dbi *databaseInfo) GetIntConfig(app wba.AppInfo, datamap string, key string) (int64, bool) { - res, ok := database.Get(app.AppKey.Name, datamap, "config", "number", key, true) - if !ok { - return 0, false - } - resInt, ok := res.(int64) - if !ok { - return 0, false - } - return resInt, true -} - -func (dbi *databaseInfo) GetStringConfig(app wba.AppInfo, datamap string, key string) (string, bool) { - res, ok := database.Get(app.AppKey.Name, datamap, "config", "string", key, true) - if !ok { - return "", false - } - resStr, ok := res.(string) - if !ok { - return "", false - } - return resStr, true -} - -func (dbi *databaseInfo) GetFloatConfig(app wba.AppInfo, datamap string, key string) (float64, bool) { - res, ok := database.Get(app.AppKey.Name, datamap, "config", "float", key, true) - if !ok { - return 0, false - } - resFloat, ok := res.(float64) - if !ok { - return 0, false - } - return resFloat, true -} - -func (dbi *databaseInfo) GetIntSliceConfig(app wba.AppInfo, datamap string, key string) ([]int64, bool) { - res, ok := database.Get(app.AppKey.Name, datamap, "config", "number_slice", key, true) - if !ok { - return nil, false - } - resSlice, ok := res.([]int64) - if !ok { - return nil, false - } - return resSlice, true -} - -func (dbi *databaseInfo) GetStringSliceConfig(app wba.AppInfo, datamap string, key string) ([]string, bool) { - res, ok := database.Get(app.AppKey.Name, datamap, "config", "string_slice", key, true) - if !ok { - return nil, false - } - resSlice, ok := res.([]string) - if !ok { - return nil, false - } - return resSlice, true -} - -func (dbi *databaseInfo) UnsafelyCreatePublicDatamap(app wba.AppInfo, datamapId string) { - appName := app.AppKey.Name - database.CreatePublicDatamap(appName, datamapId) -} - // 文件管理模块 //TODO: 文件管理模块待实现 @@ -1204,8 +934,7 @@ func (dbi *databaseInfo) UnsafelyCreatePublicDatamap(app wba.AppInfo, datamapId //核心信息调用模块 -var AppApi apiInfo -var DatabaseApi databaseInfo +var ProtocolApi protocolAPI func GenerateUUID() (string, error) { uuid := make([]byte, 16) diff --git a/core/api_wst.go b/core/api_wst.go new file mode 100644 index 0000000..bf8271c --- /dev/null +++ b/core/api_wst.go @@ -0,0 +1,143 @@ +package core + +import ( + "ProjectWIND/LOG" + "ProjectWIND/wba" + "encoding/json" + "strconv" + "strings" +) + +type toolsAPI struct{} + +//二、LOG模块 + +/* +关于LOG模块的说明 + +1.日志模块级别分为TRACE、DEBUG、INFO、NOTICE、WARN、ERROR五个级别,默认级别为INFO。 + +2.日志模块提供LogWith方法,可以自定义日志级别。 + +3.日志模块提供Log方法,默认日志级别为INFO。 +*/ + +// LogWith 打印日志(带级别) +// +// 参数: +// - level: 日志级别,支持"TRACE"、"DEBUG"、"INFO"、"NOTICE"、 "WARN"、"ERROR" +// - content: 日志内容 +// - args: 可选参数,用于格式化日志内容 +// +// 返回值: +// - 无 +func (t *toolsAPI) LogWith(level string, content string, args ...interface{}) { + level = strings.ToLower(level) + switch level { + case "trace": + LOG.Trace(content, args...) + return + case "debug": + LOG.Debug(content, args...) + return + case "notice": + LOG.Notice(content, args...) + return + case "warn": + LOG.Warn(content, args...) + return + case "error": + LOG.Error(content, args...) + return + default: + LOG.Info(content, args...) + return + } +} + +// Log 打印日志 +// +// 参数: +// - content: 日志内容 +// - args: 可选参数,用于格式化日志内容 +func (t *toolsAPI) Log(content string, args ...interface{}) { + LOG.Info(content, args...) +} + +// MsgUnmarshal 解析消息 +// +// 参数: +// - messageJSON: 从数据库中获取的JSON序列化后的消息字符串 +// +// 返回值: +// - wba.MessageEventInfo: 解析后的消息事件信息,解析失败时返回空结构体 +func (t *toolsAPI) MsgUnmarshal(messageJSON string) (msg wba.MessageEventInfo) { + err := json.Unmarshal([]byte(messageJSON), &msg) + if err != nil { + return wba.MessageEventInfo{} + } + return msg +} + +func (t *toolsAPI) SessionLabelAnalysis(sessionLabel wba.SessionLabel) wba.SessionInfo { + platform := strings.Split(sessionLabel, ":")[0] + sessionTypeAndId := strings.Split(sessionLabel, ":")[1] + sessionType := strings.Split(sessionTypeAndId, "-")[0] + sessionId := strings.Split(sessionTypeAndId, "-")[1] + Id, err := strconv.ParseInt(sessionId, 10, 64) + if err != nil { + return wba.SessionInfo{} + } + return wba.SessionInfo{ + Platform: platform, + SessionType: sessionType, + SessionId: Id, + } +} + +func (t *toolsAPI) VersionCompare(version1, version2 wba.VersionLabel) int { + version1Info := VersionLabelAnalysis(version1) + version2Info := VersionLabelAnalysis(version2) + if version1Info.BigVersion < version2Info.BigVersion { + return -1 + } else if version1Info.BigVersion > version2Info.BigVersion { + return 1 + } else { + if version1Info.SmallVersion < version2Info.SmallVersion { + return -1 + } else if version1Info.SmallVersion > version2Info.SmallVersion { + return 1 + } else { + if version1Info.FixVersion < version2Info.FixVersion { + return -1 + } else if version1Info.FixVersion > version2Info.FixVersion { + return 1 + } else { + return 0 + } + } + } +} + +func VersionLabelAnalysis(versionLabel wba.VersionLabel) wba.VersionInfo { + version := strings.Split(versionLabel, ".") + bigVersion, err := strconv.ParseUint(version[0], 10, 8) + if err != nil { + return wba.VersionInfo{} + } + smallVersion, err := strconv.ParseUint(version[1], 10, 8) + if err != nil { + return wba.VersionInfo{} + } + fixVersion, err := strconv.ParseUint(version[2], 10, 8) + if err != nil { + return wba.VersionInfo{} + } + return wba.VersionInfo{ + BigVersion: uint8(bigVersion), + SmallVersion: uint8(smallVersion), + FixVersion: uint8(fixVersion), + } +} + +var ToolsApi toolsAPI diff --git a/core/app_admin.go b/core/app_admin.go index 95228dc..8e6e6c3 100644 --- a/core/app_admin.go +++ b/core/app_admin.go @@ -3,21 +3,45 @@ package core import ( "ProjectWIND/LOG" "ProjectWIND/wba" + "github.com/dop251/goja" "os" "path/filepath" + "reflect" "strings" - - "github.com/dop251/goja" ) -var CmdMap = make([]map[string]wba.Cmd, 4) +type CamelCaseFieldNameMapper struct{} + +func (CamelCaseFieldNameMapper) FieldName(_ reflect.Type, f reflect.StructField) string { + name := f.Name + if len(name) == 0 { + return name + } + // 首字母小写 + return strings.ToLower(name[:1]) + name[1:] +} + +func (CamelCaseFieldNameMapper) MethodName(_ reflect.Type, m reflect.Method) string { + name := m.Name + if len(name) == 0 { + return name + } + // 首字母小写 + return strings.ToLower(name[:1]) + name[1:] +} + +var GlobalCmdAgentSelector = wba.NewCmdAgentSelector() +var CmdMap = make(map[wba.AppKey]wba.CmdList) var AppMap = make(map[wba.AppKey]wba.AppInfo) +var ScheduledTaskMap = make(map[wba.AppKey]map[string]wba.ScheduledTaskInfo) // ReloadApps 重新加载应用 func ReloadApps() (total int, success int) { // 清空AppMap和CmdMap - CmdMap = make([]map[string]wba.Cmd, 4) + CmdMap = make(map[wba.AppKey]wba.CmdList) AppMap = make(map[wba.AppKey]wba.AppInfo) + ScheduledTaskMap = make(map[wba.AppKey]map[string]wba.ScheduledTaskInfo) + GlobalCmdAgentSelector = wba.NewCmdAgentSelector() appsDir := "./data/app/" appFiles, err := os.ReadDir(appsDir) total = 0 @@ -32,7 +56,8 @@ func ReloadApps() (total int, success int) { total += totalDelta success += successDelta } - CmdMap[0] = AppCore.CmdMap + CmdMap[AppCore.AppKey] = AppCore.CmdMap + GlobalCmdAgentSelector.AddCmdMap(CmdMap) return total, success } @@ -52,103 +77,124 @@ func reloadAPP(file os.DirEntry, appsDir string) (totalDelta int, successDelta i } runtime := goja.New() - _, err = runtime.RunString(string(jsCode)) - if err != nil { - LOG.Error("执行应用 %s 失败: %v", pluginPath, err) - return 1, 0 + runtime.SetFieldNameMapper(CamelCaseFieldNameMapper{}) + runtime.Set("console", map[string]interface{}{ + "log": func(v ...interface{}) { + LOG.Info("JS log: %v", v...) + }, + "error": func(v ...interface{}) { + LOG.Error("JS error: %v", v...) + }, + }) + + // 添加错误捕获 + safeRun := func(fn func() error) { + defer func() { + if r := recover(); r != nil { + LOG.Error("JS执行错误: %v", r) + } + }() + if err := fn(); err != nil { + LOG.Error("JS执行错误: %v", err) + } } + // 修改JS代码执行部分 + safeRun(func() error { + _, err := runtime.RunString(string(jsCode)) + return err + }) + // 创建JS可用的wbaObj对象 wbaObj := runtime.NewObject() - wsp := runtime.NewObject() - wsd := runtime.NewObject() - wst := runtime.NewObject() + //wsp := runtime.NewObject() + //wsd := runtime.NewObject() + //wst := runtime.NewObject() _ = runtime.Set("wba", wbaObj) - _ = wbaObj.Set("NewApp", wba.NewApp) - _ = wbaObj.Set("WithName", wba.WithName) - _ = wbaObj.Set("WithAuthor", wba.WithAuthor) - _ = wbaObj.Set("WithVersion", wba.WithVersion) - _ = wbaObj.Set("WithDescription", wba.WithDescription) - _ = wbaObj.Set("WithWebUrl", wba.WithWebUrl) - _ = wbaObj.Set("WithLicense", wba.WithLicense) - _ = wbaObj.Set("WithAppType", wba.WithAppType) - _ = wbaObj.Set("WithRule", wba.WithRule) - _ = wbaObj.Set("wsp", wsp) - _ = wbaObj.Set("wsd", wsd) - _ = wbaObj.Set("wst", wst) - //WSP注册 - _ = wsp.Set("UnsafelySendMsg", AppApi.UnsafelySendMsg) - _ = wsp.Set("UnsafelySendPrivateMsg", AppApi.UnsafelySendPrivateMsg) - _ = wsp.Set("UnsafelySendGroupMsg", AppApi.UnsafelySendGroupMsg) - _ = wsp.Set("SendMsg", AppApi.SendMsg) - _ = wsp.Set("SendPrivateMsg", AppApi.SendPrivateMsg) - _ = wsp.Set("SendGroupMsg", AppApi.SendGroupMsg) - _ = wsp.Set("UnsafelyDeleteMsg", AppApi.UnsafelyDeleteMsg) - _ = wsp.Set("DeleteMsg", AppApi.DeleteMsg) - _ = wsp.Set("SendLike", AppApi.SendLike) - _ = wsp.Set("SetGroupKick", AppApi.SetGroupKick) - _ = wsp.Set("SetGroupBan", AppApi.SetGroupBan) - _ = wsp.Set("SetGroupWholeBan", AppApi.SetGroupWholeBan) - _ = wsp.Set("SetGroupAdmin", AppApi.SetGroupAdmin) - _ = wsp.Set("SetGroupLeave", AppApi.SetGroupLeave) - _ = wsp.Set("SetGroupCard", AppApi.SetGroupCard) - _ = wsp.Set("SetGroupName", AppApi.SetGroupName) - _ = wsp.Set("SetGroupSpecialTitle", AppApi.SetGroupSpecialTitle) - _ = wsp.Set("SetFriendAddRequest", AppApi.SetFriendAddRequest) - _ = wsp.Set("SetGroupAddRequest", AppApi.SetGroupAddRequest) - _ = wsp.Set("GetLoginInfo", AppApi.GetLoginInfo) - _ = wsp.Set("GetVersionInfo", AppApi.GetVersionInfo) - _ = wsp.Set("GetMsg", AppApi.GetMsg) - _ = wsp.Set("GetGroupInfo", AppApi.GetGroupInfo) - _ = wsp.Set("GetForwardMsg", AppApi.GetForwardMsg) - _ = wsp.Set("GetStrangerInfo", AppApi.GetStrangerInfo) - _ = wsp.Set("GetGroupList", AppApi.GetGroupList) - _ = wsp.Set("GetGroupMemberList", AppApi.GetGroupMemberList) - _ = wsp.Set("GetFriendList", AppApi.GetFriendList) - _ = wsp.Set("GetGroupMemberInfo", AppApi.GetGroupMemberInfo) - _ = wsp.Set("GetGroupHonorInfo", AppApi.GetGroupHonorInfo) - _ = wsp.Set("GetStatus", AppApi.GetStatus) - _ = wsp.Set("GetCookies", AppApi.GetCookies) - _ = wsp.Set("GetCSRFToken", AppApi.GetCSRFToken) - _ = wsp.Set("GetCredentials", AppApi.GetCredentials) - _ = wsp.Set("GetImage", AppApi.GetImage) - _ = wsp.Set("GetRecord", AppApi.GetRecord) - _ = wsp.Set("CanSendImage", AppApi.CanSendImage) - _ = wsp.Set("CanSendRecord", AppApi.CanSendRecord) - _ = wsp.Set("SetRestart", AppApi.SetRestart) - _ = wsp.Set("CleanCache", AppApi.CleanCache) - _ = wsp.Set("GetVersionInfo", AppApi.GetVersionInfo) - //WST注册 - _ = wst.Set("LogWith", AppApi.LogWith) - _ = wst.Set("Log", AppApi.Log) - _ = wst.Set("MsgMarshal", AppApi.MsgUnmarshal) - //WSD注册 - _ = wsd.Set("SetUserVariable", DatabaseApi.SetUserVariable) - _ = wsd.Set("SetGroupVariable", DatabaseApi.SetGroupVariable) - _ = wsd.Set("SetOutUserVariable", DatabaseApi.SetOutUserVariable) - _ = wsd.Set("SetOutGroupVariable", DatabaseApi.SetOutGroupVariable) - _ = wsd.Set("UnsafelySetUserVariable", DatabaseApi.UnsafelySetUserVariable) - _ = wsd.Set("UnsafelySetGroupVariable", DatabaseApi.UnsafelySetGroupVariable) - _ = wsd.Set("UnsafelySetGlobalVariable", DatabaseApi.UnsafelySetGlobalVariable) - _ = wsd.Set("UnsafelySetOutUserVariable", DatabaseApi.UnsafelySetOutUserVariable) - _ = wsd.Set("UnsafelySetOutGroupVariable", DatabaseApi.UnsafelySetOutGroupVariable) - _ = wsd.Set("UnsafelySetOutGlobalVariable", DatabaseApi.UnsafelySetOutGlobalVariable) - _ = wsd.Set("GetUserVariable", DatabaseApi.GetUserVariable) - _ = wsd.Set("GetGroupVariable", DatabaseApi.GetGroupVariable) - _ = wsd.Set("GetOutUserVariable", DatabaseApi.GetOutUserVariable) - _ = wsd.Set("GetOutGroupVariable", DatabaseApi.GetOutGroupVariable) - _ = wsd.Set("UnsafelyGetUserVariable", DatabaseApi.UnsafelyGetUserVariable) - _ = wsd.Set("UnsafelyGetGroupVariable", DatabaseApi.UnsafelyGetGroupVariable) - _ = wsd.Set("UnsafelyGetGlobalVariable", DatabaseApi.UnsafelyGetGlobalVariable) - _ = wsd.Set("UnsafelyGetOutUserVariable", DatabaseApi.UnsafelyGetOutUserVariable) - _ = wsd.Set("UnsafelyGetOutGroupVariable", DatabaseApi.UnsafelyGetOutGroupVariable) - _ = wsd.Set("UnsafelyGetOutGlobalVariable", DatabaseApi.UnsafelyGetOutGlobalVariable) - _ = wsd.Set("GetIntConfig", DatabaseApi.GetIntConfig) - _ = wsd.Set("GetFloatConfig", DatabaseApi.GetFloatConfig) - _ = wsd.Set("GetStringConfig", DatabaseApi.GetStringConfig) - _ = wsd.Set("GetIntSliceConfig", DatabaseApi.GetIntSliceConfig) - _ = wsd.Set("GetStringSliceConfig", DatabaseApi.GetStringSliceConfig) - _ = wsd.Set("UnsafelyCreatePublicDatamap", DatabaseApi.UnsafelyCreatePublicDatamap) + _ = wbaObj.Set("newApp", wba.NewApp) + _ = wbaObj.Set("withName", wba.WithSelector) + _ = wbaObj.Set("withDescription", wba.WithDescription) + _ = wbaObj.Set("withWebUrl", wba.WithWebUrl) + _ = wbaObj.Set("withLicense", wba.WithLicense) + _ = wbaObj.Set("wsp", ProtocolApi) + _ = wbaObj.Set("wsd", DatabaseApi) + _ = wbaObj.Set("wst", ToolsApi) + //_ = wbaObj.Set("wsp", wsp) + //_ = wbaObj.Set("wsd", wsd) + //_ = wbaObj.Set("wst", wst) + ////WSP注册 + //_ = wsp.Set("unsafelySendMsg", ProtocolApi.UnsafelySendMsg) + //_ = wsp.Set("unsafelySendPrivateMsg", ProtocolApi.UnsafelySendPrivateMsg) + //_ = wsp.Set("unsafelySendGroupMsg", ProtocolApi.UnsafelySendGroupMsg) + //_ = wsp.Set("sendMsg", ProtocolApi.SendMsg) + //_ = wsp.Set("sendPrivateMsg", ProtocolApi.SendPrivateMsg) + //_ = wsp.Set("sendGroupMsg", ProtocolApi.SendGroupMsg) + //_ = wsp.Set("unsafelyDeleteMsg", ProtocolApi.UnsafelyDeleteMsg) + //_ = wsp.Set("deleteMsg", ProtocolApi.DeleteMsg) + //_ = wsp.Set("sendLike", ProtocolApi.SendLike) + //_ = wsp.Set("setGroupKick", ProtocolApi.SetGroupKick) + //_ = wsp.Set("setGroupBan", ProtocolApi.SetGroupBan) + //_ = wsp.Set("setGroupWholeBan", ProtocolApi.SetGroupWholeBan) + //_ = wsp.Set("setGroupAdmin", ProtocolApi.SetGroupAdmin) + //_ = wsp.Set("setGroupLeave", ProtocolApi.SetGroupLeave) + //_ = wsp.Set("setGroupCard", ProtocolApi.SetGroupCard) + //_ = wsp.Set("setGroupName", ProtocolApi.SetGroupName) + //_ = wsp.Set("setGroupSpecialTitle", ProtocolApi.SetGroupSpecialTitle) + //_ = wsp.Set("setFriendAddRequest", ProtocolApi.SetFriendAddRequest) + //_ = wsp.Set("setGroupAddRequest", ProtocolApi.SetGroupAddRequest) + //_ = wsp.Set("getLoginInfo", ProtocolApi.GetLoginInfo) + //_ = wsp.Set("getVersionInfo", ProtocolApi.GetVersionInfo) + //_ = wsp.Set("getMsg", ProtocolApi.GetMsg) + //_ = wsp.Set("getGroupInfo", ProtocolApi.GetGroupInfo) + //_ = wsp.Set("getForwardMsg", ProtocolApi.GetForwardMsg) + //_ = wsp.Set("getStrangerInfo", ProtocolApi.GetStrangerInfo) + //_ = wsp.Set("getGroupList", ProtocolApi.GetGroupList) + //_ = wsp.Set("getGroupMemberList", ProtocolApi.GetGroupMemberList) + //_ = wsp.Set("getFriendList", ProtocolApi.GetFriendList) + //_ = wsp.Set("getGroupMemberInfo", ProtocolApi.GetGroupMemberInfo) + //_ = wsp.Set("getGroupHonorInfo", ProtocolApi.GetGroupHonorInfo) + //_ = wsp.Set("getStatus", ProtocolApi.GetStatus) + //_ = wsp.Set("getCookies", ProtocolApi.GetCookies) + //_ = wsp.Set("getCSRFToken", ProtocolApi.GetCSRFToken) + //_ = wsp.Set("getCredentials", ProtocolApi.GetCredentials) + //_ = wsp.Set("getImage", ProtocolApi.GetImage) + //_ = wsp.Set("getRecord", ProtocolApi.GetRecord) + //_ = wsp.Set("canSendImage", ProtocolApi.CanSendImage) + //_ = wsp.Set("canSendRecord", ProtocolApi.CanSendRecord) + //_ = wsp.Set("cetRestart", ProtocolApi.SetRestart) + //_ = wsp.Set("cleanCache", ProtocolApi.CleanCache) + //_ = wsp.Set("getVersionInfo", ProtocolApi.GetVersionInfo) + ////WST注册 + //_ = wst.Set("logWith", ToolsApi.LogWith) + //_ = wst.Set("log", ToolsApi.Log) + //_ = wst.Set("msgMarshal", ToolsApi.MsgUnmarshal) + ////WSD注册 + //_ = wsd.Set("setUserVariable", DatabaseApi.SetUserVariable) + //_ = wsd.Set("setGroupVariable", DatabaseApi.SetGroupVariable) + //_ = wsd.Set("setOutUserVariable", DatabaseApi.SetOutUserVariable) + //_ = wsd.Set("setOutGroupVariable", DatabaseApi.SetOutGroupVariable) + //_ = wsd.Set("unsafelySetUserVariable", DatabaseApi.UnsafelySetUserVariable) + //_ = wsd.Set("unsafelySetGroupVariable", DatabaseApi.UnsafelySetGroupVariable) + //_ = wsd.Set("unsafelySetGlobalVariable", DatabaseApi.UnsafelySetGlobalVariable) + //_ = wsd.Set("unsafelySetOutUserVariable", DatabaseApi.UnsafelySetOutUserVariable) + //_ = wsd.Set("unsafelySetOutGroupVariable", DatabaseApi.UnsafelySetOutGroupVariable) + //_ = wsd.Set("unsafelySetOutGlobalVariable", DatabaseApi.UnsafelySetOutGlobalVariable) + //_ = wsd.Set("getUserVariable", DatabaseApi.GetUserVariable) + //_ = wsd.Set("getGroupVariable", DatabaseApi.GetGroupVariable) + //_ = wsd.Set("getOutUserVariable", DatabaseApi.GetOutUserVariable) + //_ = wsd.Set("getOutGroupVariable", DatabaseApi.GetOutGroupVariable) + //_ = wsd.Set("unsafelyGetUserVariable", DatabaseApi.UnsafelyGetUserVariable) + //_ = wsd.Set("unsafelyGetGroupVariable", DatabaseApi.UnsafelyGetGroupVariable) + //_ = wsd.Set("unsafelyGetGlobalVariable", DatabaseApi.UnsafelyGetGlobalVariable) + //_ = wsd.Set("unsafelyGetOutUserVariable", DatabaseApi.UnsafelyGetOutUserVariable) + //_ = wsd.Set("unsafelyGetOutGroupVariable", DatabaseApi.UnsafelyGetOutGroupVariable) + //_ = wsd.Set("unsafelyGetOutGlobalVariable", DatabaseApi.UnsafelyGetOutGlobalVariable) + //_ = wsd.Set("getIntConfig", DatabaseApi.GetIntConfig) + //_ = wsd.Set("getFloatConfig", DatabaseApi.GetFloatConfig) + //_ = wsd.Set("getStringConfig", DatabaseApi.GetStringConfig) + //_ = wsd.Set("getIntSliceConfig", DatabaseApi.GetIntSliceConfig) + //_ = wsd.Set("getStringSliceConfig", DatabaseApi.GetStringSliceConfig) + //_ = wsd.Set("unsafelyCreatePublicDatamap", DatabaseApi.UnsafelyCreatePublicDatamap) // 获取AppInit函数 appInitVal := runtime.Get("AppInit") @@ -169,49 +215,68 @@ func reloadAPP(file os.DirEntry, appsDir string) (totalDelta int, successDelta i return 1, 0 } - // 调用Init方法 - initVal := jsApp.ToObject(runtime).Get("Init") - initFunc, ok := goja.AssertFunction(initVal) - if !ok { - LOG.Error("应用 %s 缺少有效的Init方法", pluginPath) - return 1, 0 - } - - _, err = initFunc(wbaObj) - if err != nil { - LOG.Trace("应用初始化失败: %v", err) - return 1, 0 - } - - // 调用Get方法 - getVal := jsApp.ToObject(runtime).Get("Get") - getFunc, ok := goja.AssertFunction(getVal) - if !ok { - LOG.Error("应用 %s 缺少有效的Get方法", pluginPath) - return 1, 0 - } - - appInfoVal, err := getFunc(jsApp) - if err != nil { - LOG.Error("获取应用信息失败: %v", err) - return 1, 0 - } + //// 调用Init方法 + //initVal := jsApp.ToObject(runtime).Get("Init") + //initFunc, ok := goja.AssertFunction(initVal) + //if !ok { + // LOG.Error("应用 %s 缺少有效的Init方法 %#v", pluginPath, initFunc) + // return 1, 0 + //} + // + //_, err = initFunc(wbaObj) + //if err != nil { + // LOG.Trace("应用初始化失败: %v", err) + // return 1, 0 + //} + // + //// 调用Get方法 + //getVal := jsApp.ToObject(runtime).Get("Get") + //getFunc, ok := goja.AssertFunction(getVal) + //if !ok { + // LOG.Error("应用 %s 缺少有效的Get方法", pluginPath) + // return 1, 0 + //} + // + //appInfoVal, err := getFunc(jsApp) + //if err != nil { + // LOG.Error("获取应用信息失败: %v", err) + // return 1, 0 + //} // 转换应用信息 var appInfo wba.AppInfo - if err := runtime.ExportTo(appInfoVal, &appInfo); err != nil { + if err := runtime.ExportTo(jsApp, &appInfo); err != nil { LOG.Error("应用信息转换失败: %v", err) return 1, 0 } + // 初始化map字段 + if appInfo.CmdMap == nil { + appInfo.CmdMap = make(map[string]wba.Cmd) + } + if appInfo.ScheduledTasks == nil { + appInfo.ScheduledTasks = make(map[string]wba.ScheduledTaskInfo) + } - AppMap[wba.AppKey{Name: appInfo.AppKey.Name, Type: appInfo.AppKey.Type, Version: appInfo.AppKey.Version, Level: checkAppLevel(appInfo)}] = appInfo - cmdIndex := AppTypeToInt(appInfo.AppKey.Type) - // 合并命令 - CmdMap[cmdIndex] = mergeMaps(CmdMap[cmdIndex], appInfo.CmdMap) + AppMap[appInfo.AppKey] = appInfo + + CmdMap[appInfo.AppKey] = appInfo.CmdMap + + ScheduledTaskMap[appInfo.AppKey] = appInfo.ScheduledTasks // 注册定时任务 for _, task := range appInfo.ScheduledTasks { - RegisterCron(appInfo.AppKey.Name, task) + taskCopy := task + RegisterCron(appInfo.AppKey.Name, wba.ScheduledTaskInfo{ + Name: taskCopy.Name, + Desc: taskCopy.Desc, + Cron: taskCopy.Cron, + Task: func() { + safeRun(func() error { + taskCopy.Task() + return nil + }) + }, + }) } LOG.Info("JS应用 %s 加载成功", pluginPath) @@ -219,31 +284,3 @@ func reloadAPP(file os.DirEntry, appsDir string) (totalDelta int, successDelta i } return 0, 0 } - -func mergeMaps(map1, map2 map[string]wba.Cmd) map[string]wba.Cmd { - // 合并map1和map2到map3中 - map3 := make(map[string]wba.Cmd) - for key, value := range map1 { - map3[key] = value - } - for key, value := range map2 { - map3[key] = value - } - return map3 -} - -func AppTypeToInt(appType string) int32 { - appType = strings.ToLower(appType) - switch appType { - case "system": - return 1 - case "rule": - return 2 - default: - return 3 - } -} - -func checkAppLevel(appInfo wba.AppInfo) int32 { - return 0 -} diff --git a/core/app_core.go b/core/app_core.go index 964a4fb..4c6f896 100644 --- a/core/app_core.go +++ b/core/app_core.go @@ -3,35 +3,27 @@ package core import ( "ProjectWIND/LOG" "ProjectWIND/wba" - "errors" ) type CmdListInfo map[string]wba.Cmd type AppInfo struct { CmdMap map[string]wba.Cmd + AppKey wba.AppKey } -func (app AppInfo) Get() AppInfo { - return app -} - -func (app *AppInfo) Run(cmd string, args []string, msg wba.MessageEventInfo) error { - _, ok := app.CmdMap[cmd] - if !ok { - return errors.New("cmd not found") - } - app.CmdMap[cmd].Solve(args, msg) - return nil -} - -func (app *AppInfo) Init(Api wba.WindStandardProtocolAPI) error { - return nil -} - -func (app *AppInfo) InitWSD(Api wba.WindStandardDataBaseAPI) error { - return nil -} +//func (app AppInfo) Get() AppInfo { +// return app +//} +// +//func (app *AppInfo) Run(cmd string, args []string, msg wba.MessageEventInfo) error { +// _, ok := app.CmdMap[cmd] +// if !ok { +// return errors.New("cmd not found") +// } +// app.CmdMap[cmd].Solve(args, msg) +// return nil +//} func (app *AppInfo) GetCmd() map[string]wba.Cmd { return app.CmdMap @@ -46,14 +38,29 @@ func NewCmd(name string, help string, solve func(args []string, msg wba.MessageE } var AppCore = AppInfo{ + AppKey: wba.AppKey{ + Name: "core", + Level: 0, + Version: "1.0.0", + Selector: "core", + Option: "core", + }, CmdMap: CmdListInfo{ "bot": NewCmd( "bot", "显示WIND版本信息", func(args []string, msg wba.MessageEventInfo) { - AppApi.SendMsg(msg, "WIND 0.1.0", false) + ProtocolApi.SendMsg(msg, "WIND 0.1.0", false) LOG.Info("发送核心版本信息:(至:%v-%v:%v-%v)", msg.MessageType, msg.GroupId, msg.UserId, msg.Sender.Nickname) }, ), + "help": NewCmd( + "help", + "显示帮助信息", + func(args []string, msg wba.MessageEventInfo) { + ProtocolApi.SendMsg(msg, "帮助信息", false) + LOG.Info("发送帮助信息:(至:%v-%v:%v-%v)", msg.MessageType, msg.GroupId, msg.UserId, msg.Sender.Nickname) + }, + ), }, } diff --git a/core/events_handler.go b/core/events_handler.go index 5046f3e..335ef68 100644 --- a/core/events_handler.go +++ b/core/events_handler.go @@ -3,7 +3,6 @@ package core import ( "ProjectWIND/LOG" "ProjectWIND/database" - "ProjectWIND/typed" "ProjectWIND/wba" "encoding/json" "fmt" @@ -19,8 +18,14 @@ func HandleMessage(msgJson []byte) { } // 处理消息 LOG.Info("收到消息:(来自:%v-%v:%v-%v)%v", msg.MessageType, msg.GroupId, msg.UserId, msg.Sender.Nickname, msg.RawMessage) - CmdHandle(msg) - fmt.Printf("%#v\n", msg.Message) + isCmd, ok, err := CmdHandle(msg) + if err != nil { + LOG.Error("命令处理失败: %v", err) + } + if !isCmd && ok { + // 处理消息 + // TODO: 处理消息 + } } func HandleNotice(msgJson []byte) { @@ -50,102 +55,60 @@ func HandleMetaEvent(msgJson []byte) { // TODO: 处理元事件 } -func CmdSplit(msg wba.MessageEventInfo) (string, []string) { - //text := msg.RawMessage - //if strings.HasPrefix(text, fmt.Sprintf("[CQ:at,qq=%d]", msg.SelfId)) { - // text = strings.TrimPrefix(text, fmt.Sprintf("[CQ:at,qq=%d]", msg.SelfId)) - //} else { - // if !statusCheck(msg) { - // return "", []string{} - // } - //} - ////检查有无application.CmdMap中的命令前缀 - //for _, prefix := range cmdPrefix { - // if strings.HasPrefix(text, prefix) { - // text = strings.TrimPrefix(text, prefix) - // for cmdList := range CmdMap { - // for cmd := range CmdMap[cmdList] { - // if strings.HasPrefix(text, cmd) { - // text = strings.TrimPrefix(text, cmd) - // text = strings.TrimPrefix(text, " ") - // return cmd, strings.Split(text, " ") - // } - // } - // } - // } - //} - return "", []string{} -} - -func CmdHandle(msg wba.MessageEventInfo) { +func CmdHandle(msg wba.MessageEventInfo) (isCmd bool, ok bool, err error) { // 获取消息的原始文本 text := msg.GetText() // 初始化会话工作状态 - WorkStatus := typed.SessionWorkSpace{} - - if msg.MessageType == "group" { - // 如果消息类型是群消息 - // 从数据库中获取工作状态信息 - WorkStatusJson, ok := database.MasterGet("WorkStatus", "global", strconv.FormatInt(msg.SelfId, 10), strconv.FormatInt(msg.GroupId, 10)) - // 尝试将获取到的工作状态信息反序列化为 WorkStatus 结构体 - err := json.Unmarshal([]byte(WorkStatusJson.(string)), &WorkStatus) - // 如果获取失败或反序列化失败 - if !ok || err != nil { - // 初始化一个新的工作状态 - WorkStatus = typed.SessionWorkSpace{ - SessionId: fmt.Sprintf("%d-%d", msg.SelfId, msg.GroupId), - Rule: "coc", - Enable: true, - AppEnable: make(map[wba.AppKey]bool), - CmdEnable: make(map[string]bool), - WorkLevel: 0, - } - // 将新的工作状态序列化为 JSON 字符串 - WorkStatusJson, err := json.Marshal(WorkStatus) - if err != nil { - // 如果序列化失败,记录错误信息 - LOG.Error("命令处理过程中,WorkStatusJson序列化失败: %v", err) - } - // 将序列化后的工作状态保存到数据库中 - database.MasterSet("WorkStatus", "global", strconv.FormatInt(msg.SelfId, 10), strconv.FormatInt(msg.GroupId, 10), string(WorkStatusJson)) - } + session := wba.SessionInfo{} + session = session.Load("QQ", msg) + // 从数据库加载配置 + conf, ok := database.MasterGet("GCAS_Config", session.SessionType, strconv.FormatInt(session.SessionId, 10), "GCAS_Config") + if !ok { + conf = "{}" + } + if err = GlobalCmdAgentSelector.LoadConfig(session, conf.(string)); err != nil { + LOG.Error("加载配置失败: %v", err) + return false, false, err } - // 调用 CmdSplit 函数将消息文本拆分为命令和参数 - cmd, args := CmdSplit(msg) - - // 检查是否找到了有效的命令 - if cmd != "" { - // 遍历命令映射表 CmdMap - for _, cmdList := range CmdMap { - for cmdKey, Cmd := range cmdList { - // 如果找到了匹配的命令 - if cmdKey == cmd { - // 检查会话工作状态 - if WorkStatus.SessionId != "" { - if !WorkStatus.Enable { - // 如果会话未启用,忽略该命令 - LOG.Debug("忽略指令:%s,当前会话中bot未启用", cmd) - continue - } - // 检查 APP 或命令是否启用,以及工作级别和规则是否匹配 - if !WorkStatus.AppEnable[Cmd.AppKey] || !WorkStatus.CmdEnable[cmd] || WorkStatus.WorkLevel > Cmd.AppKey.Level || (WorkStatus.Rule != Cmd.AppKey.Rule && WorkStatus.Rule != "none") { - // 如果不满足条件,忽略该命令 - LOG.Debug("忽略指令:%s,当前会话中APP或命令未启用", cmd) - continue - } - } - // 执行命令 - Cmd.Solve(args, msg) - // 记录执行的命令和参数 - LOG.Info("执行命令:%v %v", cmd, args) - } + // 检查命令前缀 + for _, prefix := range cmdPrefix { + if strings.HasPrefix(text, prefix) { + // 解析命令 + text = strings.TrimPrefix(text, prefix) + cmdSlice := strings.Split(text, " ") + if text == "" { + return false, true, nil } + cmd := cmdSlice[0] + args := cmdSlice[1:] + + result, count := GlobalCmdAgentSelector.FindCmd(cmd, true) + if count == 0 { + LOG.Debug("未找到命令: %s", cmd) + return false, true, nil + } + + // 执行找到的第一个匹配命令 + cmdFunc := result[0] + if err := safeExecuteCmd(cmdFunc, args, msg); err != nil { + LOG.Error("执行命令失败: %v", err) + return true, false, err + } + return true, true, nil } - } else { - // 如果未找到有效的命令,记录未找到命令的信息 - LOG.Info("未找到命令:%v", strings.Split(text, " ")[0]) } + return false, true, nil +} + +func safeExecuteCmd(cmdFunc wba.Cmd, args []string, msg wba.MessageEventInfo) (err error) { + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("%v", r) + } + }() + cmdFunc.Solve(args, msg) + return nil } var cmdPrefix = []string{"/", "!", "/", "!", ".", "。"} diff --git a/core/selector_doc.md b/core/selector_doc.md new file mode 100644 index 0000000..54bdf41 --- /dev/null +++ b/core/selector_doc.md @@ -0,0 +1,48 @@ +# Selector设计思路与使用说明 + +## 问题背景 + +在骰子执行指令时,我们经常遇到这样一些问题: +- 不同的插件可能使用相同的指令(很多跑团对应规则都有同名的指令),但是参数和功能完全不同,导致指令无法正常执行。并且当其中有系统级指令时,可能会导致其他严重问题,此时需要一个机制来区分指令。 +- 当我们在进行trpg时日志开启后,可能需要屏蔽某些无关的指令和自定义回复,此时需要一个机制来过滤指令和回复(即权限控制)。 +- 在不同的群聊中,各种插件的开启情况各不相同,此时需要一个机制来管理不同组群插件的开启和关闭情况(包括前两条中的权限控制问题)。 + +为解决上述问题,我们设计了一个指令选择器,用于管理和选择指令。 + +## 设计思路 + +选择器的设计思路如下: +- 每个组群有一个独立的总选择器(Agent Selector),用于管理该组群的指令和回复。 +- 每个选择器有若干个层级(Level),在查找可用指令(Cmd)时,从最高层级(0)开始查找,直到找到可用指令为止。 +- 每个层级有若干个选择器(Selector),每个选择器中包含若干个选项(Option),每个选项对应一个指令集(CmdSet)。每个选择器只能同时选择一个选项,选择后其余选项中的指令集将被屏蔽。 +- 指令集是由若干个应用(App)中的指令组成的,一个应用只能归属于一个指令集,一个指令集可以包含多个应用的指令。 + +数据结构如下: +```go +type AgentSelector struct { + Session wba.Session + Selectors []Selector +} + +type Selector struct { + Level int + Name string + Options []*Option +} + +type Option struct { + Name string + CmdSets map[string]CmdSet +} + +type CmdSet struct { + map[string]wba.Command +} + +type Cmmand struct { + Name string + Desc string + App AppKey + Solve func(*wba.Context) +} +``` \ No newline at end of file diff --git a/core/web_socket.go b/core/web_socket.go index c092f52..66f8149 100644 --- a/core/web_socket.go +++ b/core/web_socket.go @@ -44,9 +44,9 @@ func WebSocketHandler(protocol typed.Protocol) error { } }(conn) LOG.Info("已连接到WebSocket服务器: %v", u.String()) - ProtocolInfo := AppApi.GetVersionInfo() + ProtocolInfo := ProtocolApi.GetVersionInfo() LOG.Info("协议端信息: %v-%v", ProtocolInfo.Data.AppName, ProtocolInfo.Data.AppVersion) - logInfo := AppApi.GetLoginInfo() + logInfo := ProtocolApi.GetLoginInfo() LOG.Info("连接到账号: %v(%v)", logInfo.Data.Nickname, logInfo.Data.UserId) // 定义通道,缓存消息和消息类型,防止消息处理阻塞 diff --git a/wba/app.go b/wba/app.go index b81f200..e535eff 100644 --- a/wba/app.go +++ b/wba/app.go @@ -1,5 +1,7 @@ package wba +import "strings" + type AppInfo struct { AppKey AppKey Author string @@ -64,6 +66,13 @@ func WithLevel(level uint8) AppInfoOption { } } +func toCamelCase(s string) string { + if s == "" { + return s + } + return strings.ToLower(s[:1]) + s[1:] +} + func NewApp(name string, version string, author string, opts ...AppInfoOption) AppInfo { Ext := AppInfo{ AppKey: AppKey{ @@ -84,6 +93,15 @@ func NewApp(name string, version string, author string, opts ...AppInfoOption) A for _, opt := range opts { opt(&Ext) } + + // 添加JS风格方法 + Ext.API = map[string]interface{}{ + toCamelCase("NewCmd"): Ext.NewCmd, + toCamelCase("AddCmd"): Ext.AddCmd, + toCamelCase("NewScheduledTask"): Ext.NewScheduledTask, + toCamelCase("AddScheduledTask"): Ext.AddScheduledTask, + } + return Ext } @@ -127,7 +145,7 @@ type AppKey struct { Option OptionLabel `json:"option"` } -// Priority 是一个整数类型,用于表示命令的优先级。只能是不小于1的整数。 +// Priority 是一个整数类型,用于表示命令的优先级。只能是不小于1且不大于4的整数。 type Priority = uint8 // VersionLabel 是一个字符串类型,用于表示版本标签。 @@ -142,15 +160,45 @@ type OptionLabel = string // SessionLabel 是一个字符串类型,用于表示聊天会话的标签,格式为[平台:类型-ID],如"QQ:group-1145141919810" type SessionLabel = string +// CmdSetLabel 是一个字符串类型,用于表示命令集的标签。 +type CmdSetLabel = string + +// CmdLabel 是一个字符串类型,用于表示命令的标签。 +type CmdLabel = string + // CmdList 是一个字符串到 wba.Cmd 的映射,用于存储命令的列表。 type CmdList = map[string]Cmd +// SessionInfo 是一个结构体,用于存储会话信息。 +// +// 字段: +// - Platform: 表示会话的平台,如"QQ"、"Telegram"等。 +// - SessionType: 表示会话的类型,如"group"、"private"等。 +// - SessionId: 表示会话的ID,如群号、私聊号等。 type SessionInfo struct { Platform string SessionType string SessionId int64 } +func (s *SessionInfo) Load(platform string, msg MessageEventInfo) SessionInfo { + s.Platform = platform + s.SessionType = msg.MessageType + if s.SessionType == "group" { + s.SessionId = msg.GroupId + } + if s.SessionType == "private" { + s.SessionId = msg.UserId + } + return *s +} + +// VersionInfo 是一个结构体,用于存储版本信息。 +// +// 字段: +// - BigVersion: 表示大版本号。 +// - SmallVersion: 表示小版本号。 +// - FixVersion: 表示修复版本号。 type VersionInfo struct { BigVersion uint8 SmallVersion uint8 diff --git a/wba/selector.go b/wba/selector.go new file mode 100644 index 0000000..4228c57 --- /dev/null +++ b/wba/selector.go @@ -0,0 +1,360 @@ +package wba + +import ( + "ProjectWIND/LOG" + "encoding/json" + "fmt" +) + +type CmdAgentSelector struct { + Session SessionInfo + Selectors [4]map[SelectorLabel]Selector + cmdTable map[CmdLabel]map[CmdSetLabel]struct{} + cmdSetTable map[CmdSetLabel]map[OptionLabel]struct{} + optionTable map[OptionLabel][]map[SelectorLabel]struct{} +} + +type Selector struct { + Level Priority + Name SelectorLabel + Options map[OptionLabel]Option + currentOption OptionLabel +} + +type Option struct { + Name string + CmdSets map[CmdSetLabel]CmdSet +} + +type CmdSet struct { + Name CmdSetLabel + Cmds map[CmdLabel]CmdInfo + Enabled bool +} + +type CmdInfo struct { + Name CmdLabel + Cmd Cmd + Enabled bool +} + +// NewCmdAgentSelector 创建一个新的 CmdAgentSelector 实例并初始化。 +// +// 参数: +// +// - session: 所属会话信息。 +func NewCmdAgentSelector() CmdAgentSelector { + c := CmdAgentSelector{ + Session: SessionInfo{}, + Selectors: [4]map[SelectorLabel]Selector{}, + cmdTable: make(map[CmdLabel]map[CmdSetLabel]struct{}), + cmdSetTable: make(map[CmdSetLabel]map[OptionLabel]struct{}), + optionTable: make(map[OptionLabel][]map[SelectorLabel]struct{}), + } + for i := 0; i < 4; i++ { + c.Selectors[i] = make(map[SelectorLabel]Selector) + c.optionTable[fmt.Sprintf("%d", i)] = make([]map[SelectorLabel]struct{}, 4) + } + return c +} + +func (a *CmdAgentSelector) LoadConfig(session SessionInfo, config string) error { + a.Session = session + conf := CmdAgentSelector{} + err := json.Unmarshal([]byte(config), &conf) + if err != nil { + return err + } + for i := 0; i < 4; i++ { + for selectorName, selector := range a.Selectors[i] { + selector.currentOption = conf.Selectors[i][selectorName].currentOption + for optionName, option := range selector.Options { + for cmdSetName, cmdSet := range option.CmdSets { + for cmdName, cmd := range cmdSet.Cmds { + cmd.Enabled = conf.Selectors[i][selectorName].Options[optionName].CmdSets[cmdSetName].Cmds[cmdName].Enabled + } + } + } + } + } + return nil +} + +// AddCmdMap 添加一个命令映射(map[AppKey]CmdList)到 CmdAgentSelector 中。 +// +// 参数: +// +// - cmdMap: 命令映射,键为 AppKey,值为 CmdList。 +func (a *CmdAgentSelector) AddCmdMap(cmdMap map[AppKey]CmdList) { + for appKey, cmds := range cmdMap { + a.AddCmdSet(appKey, cmds) + } +} + +// AddCmdSet 添加一个命令集(即APP)到 CmdAgentSelector 中。 +// +// 参数: +// +// - appKey: 应用键,包含应用名称、级别、选择器和选项。 +// - cmds: 命令列表,键为命令名称,值为 CmdInfo。 +func (a *CmdAgentSelector) AddCmdSet(appKey AppKey, cmds CmdList) { + + for cmdName, cmd := range cmds { + // 确保所有必要的map已初始化 + if a.cmdTable == nil { + a.cmdTable = make(map[CmdLabel]map[CmdSetLabel]struct{}) + } + if a.cmdSetTable == nil { + a.cmdSetTable = make(map[CmdSetLabel]map[OptionLabel]struct{}) + } + if a.optionTable == nil { + a.optionTable = make(map[OptionLabel][]map[SelectorLabel]struct{}) + } + + // 初始化嵌套map + if a.cmdTable[cmdName] == nil { + a.cmdTable[cmdName] = make(map[CmdSetLabel]struct{}) + } + if a.cmdSetTable[appKey.Name] == nil { + a.cmdSetTable[appKey.Name] = make(map[OptionLabel]struct{}) + } + if a.optionTable[appKey.Option] == nil { + a.optionTable[appKey.Option] = make([]map[SelectorLabel]struct{}, 4) + } + if a.optionTable[appKey.Option][appKey.Level] == nil { + a.optionTable[appKey.Option][appKey.Level] = make(map[SelectorLabel]struct{}) + } + + // 确保Selector层级的map已初始化 + if a.Selectors[appKey.Level] == nil { + a.Selectors[appKey.Level] = make(map[SelectorLabel]Selector) + } + if a.Selectors[appKey.Level][appKey.Selector].Options == nil { + selector := a.Selectors[appKey.Level][appKey.Selector] + selector.Options = make(map[OptionLabel]Option) + a.Selectors[appKey.Level][appKey.Selector] = selector + } + if a.Selectors[appKey.Level][appKey.Selector].Options[appKey.Option].CmdSets == nil { + selector := a.Selectors[appKey.Level][appKey.Selector] + option := selector.Options[appKey.Option] + option.CmdSets = make(map[CmdSetLabel]CmdSet) + selector.Options[appKey.Option] = option + a.Selectors[appKey.Level][appKey.Selector] = selector + } + if a.Selectors[appKey.Level][appKey.Selector].Options[appKey.Option].CmdSets[appKey.Name].Cmds == nil { + selector := a.Selectors[appKey.Level][appKey.Selector] + option := selector.Options[appKey.Option] + cmdSet := option.CmdSets[appKey.Name] + cmdSet.Cmds = make(map[CmdLabel]CmdInfo) + option.CmdSets[appKey.Name] = cmdSet + selector.Options[appKey.Option] = option + a.Selectors[appKey.Level][appKey.Selector] = selector + } + a.cmdTable[cmdName][appKey.Name] = struct{}{} + a.cmdSetTable[appKey.Name][appKey.Option] = struct{}{} + a.optionTable[appKey.Option][appKey.Level][appKey.Selector] = struct{}{} + a.Selectors[appKey.Level][appKey.Selector].Options[appKey.Option].CmdSets[appKey.Name].Cmds[cmdName] = CmdInfo{ + Cmd: cmd, + Enabled: true, + } + LOG.Debug("add cmd: %s, app: %s, option: %s, cmdSet: %s", cmdName, appKey.Name, appKey.Option, appKey.Selector) + } + a.Selectors[appKey.Level][appKey.Selector].Options[appKey.Option].CmdSets[appKey.Name] = CmdSet{ + Name: appKey.Name, + Cmds: a.Selectors[appKey.Level][appKey.Selector].Options[appKey.Option].CmdSets[appKey.Name].Cmds, + Enabled: true, + } + if a.Selectors[appKey.Level][appKey.Selector].currentOption == "" { + a.Selectors[appKey.Level][appKey.Selector] = Selector{ + Level: appKey.Level, + Name: appKey.Selector, + Options: a.Selectors[appKey.Level][appKey.Selector].Options, + currentOption: appKey.Option, + } + } +} + +// FindCmd 根据命令名称和是否只查找启用的命令,查找符合条件的命令。 +// +// 参数: +// +// - cmdName: 要查找的命令名称。 +// - onlyEnabled: 是否只查找启用的命令。 +// +// 返回值: +// +// - result: 找到的命令列表(越靠前的优先级越高)。 +// - count: 找到的命令数量。 +func (a *CmdAgentSelector) FindCmd(cmdName string, onlyEnabled bool) (result []Cmd, count int) { + result = make([]Cmd, 0) + count = 0 + cmdSets := a.cmdTable[cmdName] + for cmdSet := range cmdSets { + options := a.cmdSetTable[cmdSet] + for option := range options { + selectors := a.optionTable[option] + for i := 0; i < len(a.Selectors); i++ { + for selector := range selectors[i] { + if option != a.Selectors[i][selector].currentOption && onlyEnabled { + continue + } + cmdInfo := a.Selectors[i][selector].Options[option].CmdSets[cmdSet].Cmds[cmdName] + if onlyEnabled && !cmdInfo.Enabled { + continue + } + result = append(result, cmdInfo.Cmd) + count++ + } + + } + + } + } + return result, count +} + +// FindCmdSet 根据命令集名称和是否只查找启用的命令集,查找符合条件的命令集。 +// +// 参数: +// +// - cmdSetName: 要查找的命令集名称。 +// - onlyEnabled: 是否只查找启用的命令集。 +// +// 返回值: +// +// - result: 找到的命令集列表(越靠前的优先级越高)。 +// - count: 找到的命令集数量。 +func (a *CmdAgentSelector) FindCmdSet(cmdSetName string, onlyEnabled bool) (result []CmdSet, count int) { + result = make([]CmdSet, 0) + count = 0 + options := a.cmdSetTable[cmdSetName] + for option := range options { + selectors := a.optionTable[option] + for i := 0; i < len(a.Selectors); i++ { + for selector := range selectors[i] { + if option != a.Selectors[i][selector].currentOption && onlyEnabled { + continue + } + cmdSet := a.Selectors[i][selector].Options[option].CmdSets[cmdSetName] + result = append(result, cmdSet) + count++ + } + } + } + return result, count +} + +// FindOption 根据选项名称,查找符合条件的选项。 +// +// 参数: +// +// - optionName: 要查找的选项名称。 +// +// 返回值: +// +// - result: 找到的选项列表(越靠前的优先级越高)。 +// - count: 找到的选项数量。 +func (a *CmdAgentSelector) FindOption(optionName string) (result []Option, count int) { + result = make([]Option, 0) + count = 0 + selectors := a.optionTable[optionName] + for i := 0; i < len(a.Selectors); i++ { + for selector := range selectors[i] { + if optionName != a.Selectors[i][selector].currentOption { + continue + } + option := a.Selectors[i][selector].Options[optionName] + result = append(result, option) + count++ + } + } + return result, count +} + +// FindSelector 根据选择器名称,查找符合条件的选择器。 +// +// 参数: +// +// - selectorName: 要查找的选择器名称。 +// +// 返回值: +// +// - result: 找到的选择器列表(越靠前的优先级越高)。 +// - count: 找到的选择器数量。 +func (a *CmdAgentSelector) FindSelector(selectorName string) (result []Selector, count int) { + result = make([]Selector, 0) + count = 0 + selectors := a.optionTable[selectorName] + for i := 0; i < len(a.Selectors); i++ { + for selector := range selectors[i] { + if selectorName != a.Selectors[i][selector].currentOption { + continue + } + selector := a.Selectors[i][selector] + result = append(result, selector) + count++ + } + } + return result, count +} + +// GetCurrentOption 获取当前选择器的当前选项。 +// +// 参数: +// +// - level: 选择器的级别。 +// - selectorName: 选择器的名称。 +// +// 返回值: +// +// - result: 当前选项,当isExist为false时,此结果为 Option 对应的空值。 +// - isExist: 是否存在当前选项。 +func (a *CmdAgentSelector) GetCurrentOption(level int, selectorName string) (result Option, isExist bool) { + result, ok := a.Selectors[level][selectorName].Options[a.Selectors[level][selectorName].currentOption] + if !ok { + return Option{}, false + } + return result, true +} + +// ToggleCmd 切换命令的启用状态。 +// +// 参数: +// +// - enabled: 要设置的启用状态。 +// - level: 选择器的级别。 +// - selectorName: 选择器的名称。 +// - optionName: 选项的名称。 +// - cmdSetName: 命令集的名称。 +// - cmdName: 命令的名称。 +// +// 返回值: +// +// - state: 命令的启用状态。 +// - preState: 命令的之前的启用状态。 +// - err: 错误信息,当发生错误时返回。 +func (a *CmdAgentSelector) ToggleCmd(enabled bool, level Priority, selectorName SelectorLabel, optionName OptionLabel, cmdSetName CmdSetLabel, cmdName CmdLabel) (state bool, preState bool, err error) { + if level < 0 || level > 3 { + return false, false, fmt.Errorf("level must be between 0 and 3") + } + selector, ok := a.Selectors[level][selectorName] + if !ok { + return false, false, fmt.Errorf("selector %s not found", selectorName) + } + option, ok := selector.Options[optionName] + if !ok { + return false, false, fmt.Errorf("option %s not found", optionName) + } + cmdSet, ok := option.CmdSets[cmdName] + if !ok { + return false, false, fmt.Errorf("cmdSet %s not found", cmdName) + } + cmdInfo, ok := cmdSet.Cmds[cmdName] + if !ok { + return false, false, fmt.Errorf("cmd %s not found", cmdName) + } + preState = cmdInfo.Enabled + cmdInfo.Enabled = enabled + a.Selectors[level][selectorName].Options[optionName].CmdSets[cmdSetName].Cmds[cmdName] = cmdInfo + return enabled, preState, nil +} diff --git a/wba/wind_standard_tools.go b/wba/wind_standard_tools.go index a7859d4..4f4e34a 100644 --- a/wba/wind_standard_tools.go +++ b/wba/wind_standard_tools.go @@ -2,8 +2,6 @@ package wba import ( "fmt" - "strconv" - "strings" ) type WindStandardTools interface { @@ -11,63 +9,54 @@ type WindStandardTools interface { // // 参数: // - // - message: 要解析的消息字符串。 + // - message: 要解析的消息JSON字符串。 // // 返回值: // // - msg: 解析后的消息结构体。 MsgUnmarshal(message string) (msg MessageEventInfo) + // LogWith 使用指定日志级别记录日志,支持可变参数占位符。 + // // 参数: - // - level: 日志级别: "trace", "debug", "info", "notice", "warn", "error"。 - // - log: 日志内容。 - // - args: 可变参数,用于格式化日志内容。 + // - level: 日志级别: "trace", "debug", "info", "notice", "warn", "error"。 + // - log: 日志内容。 + // - args: 可变参数,用于格式化日志内容。 LogWith(level string, log string, args ...interface{}) // Log 记录日志,级别为 "info",支持可变参数占位符。 + // // 参数: - // - log: 日志内容。 - // - args: 可变参数,用于格式化日志内容。 + // - log: 日志内容。 + // - args: 可变参数,用于格式化日志内容。 Log(log string, args ...interface{}) + + // VersionLabelAnalysis 解析版本标签为 VersionInfo 结构体。 + // + // 参数: + // - versionLabel: 版本标签字符串,格式为 "大版本.小版本.修复版本"。 + // 返回值: + // - versionInfo: 解析后的版本信息结构体。 + VersionLabelAnalysis(versionLabel VersionLabel) (versionInfo VersionInfo) + + // VersionCompare 比较两个版本标签的大小。 + // + // 参数: + // - version1: 第一个版本标签字符串。 + // - version2: 第二个版本标签字符串。 + // 返回值: + // - result: 比较结果,-1表示version1小于version2,0表示version1等于version2,1表示version1大于version2。 + VersionCompare(version1, version2 VersionLabel) (result int) + + // SessionLabelAnalysis 解析会话标签为 SessionInfo 结构体。 + // + // 参数: + // - sessionLabel: 会话标签字符串,格式为 "平台:会话类型-会话ID"。 + // 返回值: + // - sessionInfo: 解析后的会话信息结构体。 + SessionLabelAnalysis(sessionLabel SessionLabel) (sessionInfo SessionInfo) } func (v VersionInfo) String() string { return fmt.Sprintf("%d.%d.%d", v.BigVersion, v.SmallVersion, v.FixVersion) } - -func VersionLabelAnalysis(versionLabel VersionLabel) VersionInfo { - version := strings.Split(versionLabel, ".") - bigVersion, err := strconv.ParseUint(version[0], 10, 8) - if err != nil { - return VersionInfo{} - } - smallVersion, err := strconv.ParseUint(version[1], 10, 8) - if err != nil { - return VersionInfo{} - } - fixVersion, err := strconv.ParseUint(version[2], 10, 8) - if err != nil { - return VersionInfo{} - } - return VersionInfo{ - BigVersion: uint8(bigVersion), - SmallVersion: uint8(smallVersion), - FixVersion: uint8(fixVersion), - } -} - -func SessionLabelAnalysis(sessionLabel SessionLabel) SessionInfo { - platform := strings.Split(sessionLabel, ":")[0] - sessionTypeAndId := strings.Split(sessionLabel, ":")[1] - sessionType := strings.Split(sessionTypeAndId, "-")[0] - sessionId := strings.Split(sessionTypeAndId, "-")[1] - Id, err := strconv.ParseInt(sessionId, 10, 64) - if err != nil { - return SessionInfo{} - } - return SessionInfo{ - Platform: platform, - SessionType: sessionType, - SessionId: Id, - } -}