From fad0d50f3f4a2b483a95eeb8a1c1c071b669142f Mon Sep 17 00:00:00 2001 From: cosmo Date: Mon, 23 Oct 2023 12:18:50 +0800 Subject: [PATCH 01/27] Compiled main.go and pushed changes --- .gitignore | 3 ++- config_template.yml | 13 +++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 config_template.yml diff --git a/.gitignore b/.gitignore index 29d385c9..52efd70d 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ idmap.ini # Go specific go.mod -go.sum \ No newline at end of file +go.sum +*.exe \ No newline at end of file diff --git a/config_template.yml b/config_template.yml new file mode 100644 index 00000000..cb73fcd4 --- /dev/null +++ b/config_template.yml @@ -0,0 +1,13 @@ +version: 1 +settings: + ws_address: "ws://:" # WebSocket服务的地址 + app_id: # 你的应用ID + token: "" # 你的应用令牌 + client_secret: "" # 你的客户端密钥 + text_intent: # 你希望监听的事件类型 + - "ATMessageEventHandler" # 频道at + - "DirectMessageHandler" + # - "GroupATMessageEventHandler" # 群at 测试频道机器人时候需要注释 + global_channel_to_group: false # 是否将频道转换成群 + global_private_to_channel: false # 是否将私聊转换成频道 + array: false \ No newline at end of file From fda2572e694126febd60def3e6bed89ec1c5ef0c Mon Sep 17 00:00:00 2001 From: cosmo Date: Mon, 23 Oct 2023 13:22:05 +0800 Subject: [PATCH 02/27] test --- readme.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index dcd37574..97f5fe50 100644 --- a/readme.md +++ b/readme.md @@ -43,10 +43,14 @@ _✨ 基于 [OneBot](https://github.com/howmanybots/onebot/blob/master/README.md ## 兼容性 -gensokyo兼容 [OneBot-v11](https://github.com/botuniverse/onebot-11) ,并在其基础上做了一些扩展,详情请看 gensokyo 的文档。 +gensokyo兼容 [OneBot-v11](https://github.com/botuniverse/onebot-11) ,并在其基础上做了一些扩展,详情请看 OneBot 的文档。 + 可将官方的websocket和api转换至onebotv11标准,和koishi,nonebot2,trss等项目的onebot适配器相连接使用. + 实现插件开发和用户开发者无需重新开发,复用过往生态的插件和使用体验. +目前还在开发中..... + ### 接口 - [ ] HTTP API From 34cfac146145e90e6713e9847d218f986bf0ebc3 Mon Sep 17 00:00:00 2001 From: cosmo Date: Tue, 24 Oct 2023 00:43:48 +0800 Subject: [PATCH 03/27] =?UTF-8?q?=E9=80=82=E9=85=8D=E4=BA=86=E9=A2=91?= =?UTF-8?q?=E9=81=93=E7=A7=81=E8=81=8A,=E7=94=A8bolt=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=BA=93=E5=8F=96=E4=BB=A3ini?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Processor/Processor.go | 292 +++++++++++++++++++++++++++++------ botgo/openapi/v2/openapi.go | 3 +- botgo/token/authtoken.go | 4 +- botgo/token/token.go | 2 +- callapi/callapi.go | 5 +- handlers/get_group_info.go | 3 +- handlers/send_group_msg.go | 82 +++++----- handlers/send_private_msg.go | 204 ++++++++++++++++++++++++ idmap/iniMapping.go | 68 -------- idmap/service.go | 49 +++++- main.go | 28 +++- readme.md | 2 +- 12 files changed, 573 insertions(+), 169 deletions(-) create mode 100644 handlers/send_private_msg.go delete mode 100644 idmap/iniMapping.go diff --git a/Processor/Processor.go b/Processor/Processor.go index 1d3d3f89..794a84c7 100644 --- a/Processor/Processor.go +++ b/Processor/Processor.go @@ -2,7 +2,6 @@ package Processor import ( - "context" "encoding/json" "fmt" "log" @@ -21,16 +20,8 @@ import ( "github.com/tencent-connect/botgo/websocket/client" ) -var compatibilityMapping *idmap.IniMapping var err error -func init() { - compatibilityMapping, err = idmap.NewIniMapping() - if err != nil { - log.Fatalf("Failed to initialize IniMapping: %v", err) - } -} - // Processor 结构体用于处理消息 type Processor struct { Api openapi.OpenAPI // API 类型 @@ -45,6 +36,7 @@ type Sender struct { UserID int64 `json:"user_id"` } +// 频道信息事件 type OnebotChannelMessage struct { ChannelID string `json:"channel_id"` GuildID string `json:"guild_id"` @@ -63,7 +55,7 @@ type OnebotChannelMessage struct { Echo string `json:"echo"` } -// OnebotGroupMessage represents the message structure for group messages. +// 群信息事件 type OnebotGroupMessage struct { RawMessage string `json:"raw_message"` MessageID int `json:"message_id"` @@ -82,6 +74,29 @@ type OnebotGroupMessage struct { UserID int64 `json:"user_id"` } +// 私聊信息事件 +type OnebotPrivateMessage struct { + RawMessage string `json:"raw_message"` + MessageID int `json:"message_id"` // Can be either string or int depending on logic + MessageType string `json:"message_type"` + PostType string `json:"post_type"` + SelfID int64 `json:"self_id"` // Can be either string or int depending on logic + Sender PrivateSender `json:"sender"` + SubType string `json:"sub_type"` + Time int64 `json:"time"` + Avatar string `json:"avatar"` + Echo string `json:"echo"` + Message interface{} `json:"message"` // For array format + MessageSeq int `json:"message_seq,omitempty"` // Optional field + Font int `json:"font,omitempty"` // Optional field + UserID int64 `json:"user_id"` // Can be either string or int depending on logic +} + +type PrivateSender struct { + Nickname string `json:"nickname"` + UserID int64 `json:"user_id"` // Can be either string or int depending on logic +} + func FoxTimestamp() int64 { return time.Now().Unix() } @@ -157,9 +172,11 @@ func (p *Processor) ProcessGuildATMessage(data *dto.WSATMessageData) error { } else { // GlobalChannelToGroup为true时的处理逻辑 + //将频道转化为一个群 //获取s s := client.GetGlobalS() - compatibilityMapping.WriteConfig(data.ChannelID, "guild_id", data.GuildID) + //将channelid写入ini,可取出guild_id todo 比ini更好的储存方式 + idmap.WriteConfig(data.ChannelID, "guild_id", data.GuildID) //转换at messageText := handlers.RevertTransformedText(data.Content) //转换appid @@ -247,7 +264,7 @@ func (p *Processor) ProcessGroupMessage(data *dto.WSGroupATMessageData) error { // 获取s s := client.GetGlobalS() - compatibilityMapping.WriteConfig(data.ChannelID, "guild_id", data.GuildID) + idmap.WriteConfig(data.ChannelID, "guild_id", data.GuildID) // 转换at messageText := handlers.RevertTransformedText(data.Content) @@ -320,48 +337,231 @@ func (p *Processor) ProcessChannelDirectMessage(data *dto.WSDirectMessageData) e // 打印data结构体 //PrintStructWithFieldNames(data) - // 从私信中提取必要的信息 + // 从私信中提取必要的信息 这是测试回复需要用到 //recipientID := data.Author.ID - ChannelID := data.ChannelID + //ChannelID := data.ChannelID //sourece是源头频道 - GuildID := data.GuildID - - // 创建私信通道 发主动私信才需要创建 - // dm, err := p.Api.CreateDirectMessage( - // context.Background(), &dto.DirectMessageToCreate{ - // SourceGuildID: sourceGuildID, - // RecipientID: recipientID, - // }, - // ) - // if err != nil { - // log.Println("Error creating direct message channel:", err) - // return nil - // } + //GuildID := data.GuildID - timestamp := time.Now().Unix() // 获取当前时间的int64类型的Unix时间戳 - timestampStr := fmt.Sprintf("%d", timestamp) + //获取当前的s值 当前ws连接所收到的信息条数 + s := client.GetGlobalS() + if !p.Settings.GlobalChannelToGroup { + // 把频道类型的私信转换成普通ob11的私信 - dm := &dto.DirectMessage{ - GuildID: GuildID, - ChannelID: ChannelID, - CreateTime: timestampStr, - } + //转换appidstring + AppIDString := strconv.FormatUint(p.Settings.AppID, 10) + echostr := AppIDString + "_" + strconv.FormatInt(s, 10) - PrintStructWithFieldNames(dm) + //将真实id转为int userid64 + userid64, err := idmap.StoreID(data.Author.ID) + if err != nil { + log.Fatalf("Error storing ID: %v", err) + } + //将真实id写入数据库,可取出ChannelID + idmap.WriteConfig(data.Author.ID, "channel_id", data.ChannelID) + //将channelid写入数据库,可取出guild_id + idmap.WriteConfig(data.ChannelID, "guild_id", data.GuildID) + + //收到私聊信息调用的具体还原步骤 + //1,idmap还原真实userid, + //2,通过idmap获取channelid, + //3,通过idmap用channelid获取guildid, + //发信息使用的是guildid + //todo 优化数据库读写次数 + messageID64, err := idmap.StoreID(data.ID) + if err != nil { + log.Fatalf("Error storing ID: %v", err) + } + messageID := int(messageID64) + + privateMsg := OnebotPrivateMessage{ + RawMessage: data.Content, + Message: data.Content, + MessageID: messageID, + MessageType: "private", + PostType: "message", + SelfID: int64(p.Settings.AppID), + UserID: userid64, + Sender: PrivateSender{ + Nickname: data.Member.Nick, + UserID: userid64, + }, + SubType: "friend", + Time: time.Now().Unix(), + Avatar: data.Author.Avatar, + Echo: echostr, + } + + // 将当前s和appid和message进行映射 + echo.AddMsgID(AppIDString, s, data.ID) + echo.AddMsgType(AppIDString, s, "guild_private") + + // 调试 + PrintStructWithFieldNames(privateMsg) + + // Convert OnebotGroupMessage to map and send + privateMsgMap := structToMap(privateMsg) + err = p.Wsclient.SendMessage(privateMsgMap) + if err != nil { + return fmt.Errorf("error sending group message via wsclient: %v", err) + } + } else { + if !p.Settings.GlobalChannelToGroup { + //将频道私信作为普通频道信息 + + // 将时间字符串转换为时间戳 + t, err := time.Parse(time.RFC3339, string(data.Timestamp)) + if err != nil { + return fmt.Errorf("error parsing time: %v", err) + } + //获取s + s := client.GetGlobalS() + //转换at + messageText := handlers.RevertTransformedText(data.Content) + //转换appid + AppIDString := strconv.FormatUint(p.Settings.AppID, 10) + //构造echo + echostr := AppIDString + "_" + strconv.FormatInt(s, 10) + //映射str的userid到int + userid64, err := idmap.StoreID(data.Author.ID) + if err != nil { + log.Printf("Error storing ID: %v", err) + return nil + } + //OnebotChannelMessage + onebotMsg := OnebotChannelMessage{ + ChannelID: data.ChannelID, + GuildID: data.GuildID, + Message: messageText, + RawMessage: messageText, + MessageID: data.ID, + MessageType: "guild", + PostType: "message", + SelfID: int64(p.Settings.AppID), + UserID: userid64, + SelfTinyID: "", + Sender: Sender{ + Nickname: data.Member.Nick, + TinyID: "", + UserID: userid64, + }, + SubType: "channel", + Time: t.Unix(), + Avatar: data.Author.Avatar, + Echo: echostr, + } + + //将当前s和appid和message进行映射 + echo.AddMsgID(AppIDString, s, data.ID) + //通过echo始终得知真实的事件类型,来对应调用正确的api + echo.AddMsgType(AppIDString, s, "guild_private") + + //调试 + PrintStructWithFieldNames(onebotMsg) + + // 将 onebotMsg 结构体转换为 map[string]interface{} + msgMap := structToMap(onebotMsg) + + // 使用 wsclient 发送消息 + err = p.Wsclient.SendMessage(msgMap) + if err != nil { + return fmt.Errorf("error sending message via wsclient: %v", err) + } + } else { + //将频道信息转化为群信息(特殊需求情况下) + //将channelid写入ini,可取出guild_id + idmap.WriteConfig(data.ChannelID, "guild_id", data.GuildID) + //转换at + messageText := handlers.RevertTransformedText(data.Content) + //转换appid + AppIDString := strconv.FormatUint(p.Settings.AppID, 10) + //构造echo + echostr := AppIDString + "_" + strconv.FormatInt(s, 10) + //把频道号作为群号 + channelIDInt, err := strconv.Atoi(data.ChannelID) + if err != nil { + // handle error, perhaps return it + return fmt.Errorf("failed to convert ChannelID to int: %v", err) + } + //映射str的userid到int + userid64, err := idmap.StoreID(data.Author.ID) + if err != nil { + log.Printf("Error storing ID: %v", err) + return nil + } + //userid := int(userid64) + //映射str的messageID到int + messageID64, err := idmap.StoreID(data.ID) + if err != nil { + log.Printf("Error storing ID: %v", err) + return nil + } + messageID := int(messageID64) + //todo 判断array模式 然后对Message处理成array格式 + groupMsg := OnebotGroupMessage{ + RawMessage: messageText, + Message: messageText, + MessageID: messageID, + GroupID: int64(channelIDInt), + MessageType: "group", + PostType: "message", + SelfID: int64(p.Settings.AppID), + UserID: userid64, + Sender: Sender{ + Nickname: data.Member.Nick, + UserID: userid64, + }, + SubType: "normal", + Time: time.Now().Unix(), + Avatar: data.Author.Avatar, + Echo: echostr, + } + //将当前s和appid和message进行映射 + echo.AddMsgID(AppIDString, s, data.ID) + echo.AddMsgType(AppIDString, s, "guild_private") + + //调试 + PrintStructWithFieldNames(groupMsg) + + // Convert OnebotGroupMessage to map and send + groupMsgMap := structToMap(groupMsg) + err = p.Wsclient.SendMessage(groupMsgMap) + if err != nil { + return fmt.Errorf("error sending group message via wsclient: %v", err) + } + } - // 发送默认回复 - toCreate := &dto.MessageToCreate{ - Content: "默认私信回复", - MsgID: data.ID, - } - _, err = p.Api.PostDirectMessage( - context.Background(), dm, toCreate, - ) - if err != nil { - log.Println("Error sending default reply:", err) - return nil } + //return nil + + //下面是测试时候固定代码 + //发私信给机器人4条机器人不回,就不能继续发了 + + // timestamp := time.Now().Unix() // 获取当前时间的int64类型的Unix时间戳 + // timestampStr := fmt.Sprintf("%d", timestamp) + + // dm := &dto.DirectMessage{ + // GuildID: GuildID, + // ChannelID: ChannelID, + // CreateTime: timestampStr, + // } + + // PrintStructWithFieldNames(dm) + + // // 发送默认回复 + // toCreate := &dto.MessageToCreate{ + // Content: "默认私信回复", + // MsgID: data.ID, + // } + // _, err = p.Api.PostDirectMessage( + // context.Background(), dm, toCreate, + // ) + // if err != nil { + // log.Println("Error sending default reply:", err) + // return nil + // } + return nil } diff --git a/botgo/openapi/v2/openapi.go b/botgo/openapi/v2/openapi.go index 2ace5872..419954ee 100644 --- a/botgo/openapi/v2/openapi.go +++ b/botgo/openapi/v2/openapi.go @@ -93,8 +93,7 @@ func (o *openAPIv2) setupClient() { func(c *resty.Client, r *resty.Request) error { // 设置授权方案为 "QQBot" c.SetAuthScheme("QQBot") - c.SetAuthToken("Sv0g5ZqFKn1E7iSBYxQzzWb7ky0X-W6P6QtGRJy1cgPm8bqGLMl73b9_72kyR9y1mBE-OvXsBMpA") - //c.SetAuthToken(o.token.GetAccessToken()) + c.SetAuthToken(o.token.GetAccessToken()) return nil }, ). diff --git a/botgo/token/authtoken.go b/botgo/token/authtoken.go index 62509b6e..b40695be 100644 --- a/botgo/token/authtoken.go +++ b/botgo/token/authtoken.go @@ -96,11 +96,11 @@ func (atoken *AuthTokenInfo) StartRefreshAccessToken(ctx context.Context, tokenU return } -//测试用 +// 测试用 // func (atoken *AuthTokenInfo) StartRefreshAccessToken(ctx context.Context, tokenURL, appID, clientSecrent string) (err error) { // // 创建一个固定的token信息 // fixedTokenInfo := AccessTokenInfo{ -// Token: "Sv0g5ZqFKn1E7iSBYxQzzWb7ky0X-W6P6QtGRJy1cgPm8bqGLMl73b9_72kyR9y1mBE-OvXsBMpA", +// Token: "SMvPWUBuXwSyAj1UmvTKVcK7D0iEaBrmbTKVNaJFMk9S5RmpgnGvNOOOTvrqG64NhuER-e-jK2IT", // ExpiresIn: 3600, // 这里假设token的有效时间是3600秒,你可以根据需要调整 // } // atoken.setAuthToken(fixedTokenInfo) diff --git a/botgo/token/token.go b/botgo/token/token.go index edcc3c92..2b0ad8f7 100644 --- a/botgo/token/token.go +++ b/botgo/token/token.go @@ -98,7 +98,7 @@ func (t *Token) GetAccessToken() string { // GetAccessToken 取得测试鉴权Token // func (t *Token) GetAccessToken() string { // // 固定的token值 -// return "Sv0g5ZqFKn1E7iSBYxQzzWb7ky0X-W6P6QtGRJy1cgPm8bqGLMl73b9_72kyR9y1mBE-OvXsBMpA" +// return "SMvPWUBuXwSyAj1UmvTKVcK7D0iEaBrmbTKVNaJFMk9S5RmpgnGvNOOOTvrqG64NhuER-e-jK2IT" // } // UpAccessToken 更新accessToken diff --git a/callapi/callapi.go b/callapi/callapi.go index 1b701050..4a64791c 100644 --- a/callapi/callapi.go +++ b/callapi/callapi.go @@ -20,8 +20,9 @@ type ParamsContent struct { BotQQ string `json:"botqq"` ChannelID string `json:"channel_id"` GuildID string `json:"guild_id"` - GroupID interface{} `json:"group_id"` - Message interface{} `json:"message"` // 这里使用interface{}因为它可能是多种类型 + GroupID interface{} `json:"group_id"` // 每一种onebotv11实现的字段类型都可能不同 + Message interface{} `json:"message"` // 这里使用interface{}因为它可能是多种类型 + UserID interface{} `json:"user_id"` // 这里使用interface{}因为它可能是多种类型 } // 自定义一个ParamsContent的UnmarshalJSON 让GroupID同时兼容str和int diff --git a/handlers/get_group_info.go b/handlers/get_group_info.go index e8c8baa2..ffa84266 100644 --- a/handlers/get_group_info.go +++ b/handlers/get_group_info.go @@ -7,6 +7,7 @@ import ( "strconv" "github.com/hoshinonyaruko/gensokyo/callapi" + "github.com/hoshinonyaruko/gensokyo/idmap" "github.com/tencent-connect/botgo/dto" "github.com/tencent-connect/botgo/openapi" @@ -57,7 +58,7 @@ func handleGetGroupInfo(client callapi.Client, api openapi.OpenAPI, apiv2 openap //用GroupID给ChannelID赋值,因为我们是把频道虚拟成了群 ChannelID := params.GroupID //读取ini 通过ChannelID取回之前储存的guild_id - value, err := compatibilityMapping.ReadConfig(ChannelID.(string), "guild_id") + value, err := idmap.ReadConfig(ChannelID.(string), "guild_id") if err != nil { log.Printf("handleGetGroupInfo:Error reading config: %v\n", err) return diff --git a/handlers/send_group_msg.go b/handlers/send_group_msg.go index ec77dda4..2df97b68 100644 --- a/handlers/send_group_msg.go +++ b/handlers/send_group_msg.go @@ -12,15 +12,8 @@ import ( "github.com/tencent-connect/botgo/openapi" ) -var compatibilityMapping *idmap.IniMapping -var err error - func init() { callapi.RegisterHandler("send_group_msg", handleSendGroupMsg) - compatibilityMapping, err = idmap.NewIniMapping() - if err != nil { - log.Fatalf("Failed to initialize IniMapping: %v", err) - } } func handleSendGroupMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.OpenAPI, message callapi.ActionMessage) { @@ -51,7 +44,15 @@ func handleSendGroupMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openap // 优先发送文本信息 if messageText != "" { - groupMessage := generateGroupMessage(messageID, nil, messageText) + groupReply := generateGroupMessage(messageID, nil, messageText) + + // 进行类型断言 + groupMessage, ok := groupReply.(*dto.MessageToCreate) + if !ok { + log.Println("Error: Expected MessageToCreate type.") + return // 或其他错误处理 + } + groupMessage.Timestamp = time.Now().Unix() // 设置时间戳 _, err := apiv2.PostGroupMessage(context.TODO(), message.Params.GroupID.(string), groupMessage) if err != nil { @@ -65,8 +66,16 @@ func handleSendGroupMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openap singleItem[key] = urls groupReply := generateGroupMessage(messageID, singleItem, "") - groupReply.Timestamp = time.Now().Unix() // 设置时间戳 - _, err := apiv2.PostGroupMessage(context.TODO(), message.Params.GroupID.(string), groupReply) + + // 进行类型断言 + richMediaMessage, ok := groupReply.(*dto.RichMediaMessage) + if !ok { + log.Printf("Error: Expected RichMediaMessage type for key %s.", key) + continue // 跳过这个项,继续下一个 + } + + //richMediaMessage.Timestamp = time.Now().Unix() // 设置时间戳 + _, err := apiv2.PostGroupMessage(context.TODO(), message.Params.GroupID.(string), richMediaMessage) if err != nil { log.Printf("发送 %s 信息失败: %v", key, err) } @@ -75,7 +84,7 @@ func handleSendGroupMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openap //用GroupID给ChannelID赋值,因为我们是把频道虚拟成了群 message.Params.ChannelID = message.Params.GroupID.(string) //读取ini 通过ChannelID取回之前储存的guild_id - value, err := compatibilityMapping.ReadConfig(message.Params.ChannelID, "guild_id") + value, err := idmap.ReadConfig(message.Params.ChannelID, "guild_id") if err != nil { log.Printf("Error reading config: %v", err) return @@ -87,45 +96,34 @@ func handleSendGroupMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openap } } -func generateGroupMessage(id string, foundItems map[string][]string, messageText string) *dto.MessageToCreate { - var reply dto.MessageToCreate - +func generateGroupMessage(id string, foundItems map[string][]string, messageText string) interface{} { if imageURLs, ok := foundItems["local_image"]; ok && len(imageURLs) > 0 { - // todo 完善本地文件上传 发送机制 - reply = dto.MessageToCreate{ - //EventID: id, // Use a placeholder event ID for now - Image: imageURLs[0], - MsgID: id, - MsgType: 0, // Assuming type 0 for images + // 本地发图逻辑 todo 适配base64图片 + return &dto.RichMediaMessage{ + EventID: id, + FileType: 1, // 1代表图片 + URL: imageURLs[0], + Content: "", // 这个字段文档没有了 + SrvSendMsg: true, } } else if imageURLs, ok := foundItems["url_image"]; ok && len(imageURLs) > 0 { - // Sending an external image - reply = dto.MessageToCreate{ - //EventID: id, // Use a placeholder event ID for now - Image: "http://" + imageURLs[0], // Using the same Image field for external URLs, adjust if needed - MsgID: id, - MsgType: 2, // Assuming type 0 for images + // 发链接图片 + return &dto.RichMediaMessage{ + EventID: id, + FileType: 1, // 1代表图片 + URL: "http://" + imageURLs[0], + Content: "", // 这个字段文档没有了 + SrvSendMsg: true, } } else if voiceURLs, ok := foundItems["base64_record"]; ok && len(voiceURLs) > 0 { - //还不支持发语音 - // Sending a voice message - // reply = dto.MessageToCreate{ - // EventID: id, - // Embed: &dto.Embed{ - // URL: voiceURLs[0], // Assuming voice is embedded using a URL - // }, - // MsgID: id, - // MsgType: 0, // Adjust type as needed for voice - // } + // 目前不支持发语音 todo 适配base64 slik } else { - // 发文本信息 - reply = dto.MessageToCreate{ - //EventID: id, + // 返回文本信息 + return &dto.MessageToCreate{ Content: messageText, MsgID: id, - MsgType: 0, // Default type for text + MsgType: 0, // 默认文本类型 } } - - return &reply + return nil } diff --git a/handlers/send_private_msg.go b/handlers/send_private_msg.go new file mode 100644 index 00000000..ec7f0f46 --- /dev/null +++ b/handlers/send_private_msg.go @@ -0,0 +1,204 @@ +package handlers + +import ( + "context" + "fmt" + "log" + "strconv" + "time" + + "github.com/hoshinonyaruko/gensokyo/callapi" + "github.com/hoshinonyaruko/gensokyo/echo" + "github.com/hoshinonyaruko/gensokyo/idmap" + "github.com/tencent-connect/botgo/dto" + "github.com/tencent-connect/botgo/openapi" +) + +func init() { + callapi.RegisterHandler("send_private_msg", handleSendPrivateMsg) +} + +func handleSendPrivateMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.OpenAPI, message callapi.ActionMessage) { + // 使用 message.Echo 作为key来获取消息类型 + msgType := echo.GetMsgTypeByKey(message.Echo) + + switch msgType { + case "group_private": + // 解析消息内容 + messageText, foundItems := parseMessageContent(message.Params) + + // 获取 echo 的值 + echostr := message.Echo + + // 使用 echo 获取消息ID + messageID := echo.GetMsgIDByKey(echostr) + log.Println("群组发信息对应的message_id:", messageID) + log.Println("群组发信息messageText:", messageText) + log.Println("foundItems:", foundItems) + + //通过bolt数据库还原真实的GroupID + originalGroupID, err := idmap.RetrieveRowByID(message.Params.GroupID.(string)) + if err != nil { + log.Printf("Error retrieving original GroupID: %v", err) + return + } + message.Params.GroupID = originalGroupID + + // 优先发送文本信息 + if messageText != "" { + groupReply := generatePrivateMessage(messageID, nil, messageText) + + // 进行类型断言 + groupMessage, ok := groupReply.(*dto.MessageToCreate) + if !ok { + log.Println("Error: Expected MessageToCreate type.") + return // 或其他错误处理 + } + + groupMessage.Timestamp = time.Now().Unix() // 设置时间戳 + _, err := apiv2.PostGroupMessage(context.TODO(), message.Params.GroupID.(string), groupMessage) + if err != nil { + log.Printf("发送文本群组信息失败: %v", err) + } + } + + // 遍历foundItems并发送每种信息 + for key, urls := range foundItems { + var singleItem = make(map[string][]string) + singleItem[key] = urls + + groupReply := generatePrivateMessage(messageID, singleItem, "") + + // 进行类型断言 + richMediaMessage, ok := groupReply.(*dto.RichMediaMessage) + if !ok { + log.Printf("Error: Expected RichMediaMessage type for key %s.", key) + continue // 跳过这个项,继续下一个 + } + + //richMediaMessage.Timestamp = time.Now().Unix() // 设置时间戳 + _, err := apiv2.PostGroupMessage(context.TODO(), message.Params.GroupID.(string), richMediaMessage) + if err != nil { + log.Printf("发送 %s 信息失败: %v", key, err) + } + } + case "guild_private": + //当收到发私信调用 并且来源是频道 + handleSendGuildChannelPrivateMsg(client, api, apiv2, message) + default: + log.Printf("Unknown message type: %s", msgType) + } +} + +func generatePrivateMessage(id string, foundItems map[string][]string, messageText string) interface{} { + if imageURLs, ok := foundItems["local_image"]; ok && len(imageURLs) > 0 { + // 本地发图逻辑 todo 适配base64图片 + return &dto.RichMediaMessage{ + EventID: id, + FileType: 1, // 1代表图片 + URL: imageURLs[0], + Content: "", // 这个字段文档没有了 + SrvSendMsg: true, + } + } else if imageURLs, ok := foundItems["url_image"]; ok && len(imageURLs) > 0 { + // 发链接图片 + return &dto.RichMediaMessage{ + EventID: id, + FileType: 1, // 1代表图片 + URL: "http://" + imageURLs[0], + Content: "", // 这个字段文档没有了 + SrvSendMsg: true, + } + } else if voiceURLs, ok := foundItems["base64_record"]; ok && len(voiceURLs) > 0 { + // 目前不支持发语音 todo 适配base64 slik + } else { + // 返回文本信息 + return &dto.MessageToCreate{ + Content: messageText, + MsgID: id, + MsgType: 0, // 默认文本类型 + } + } + return nil +} + +func handleSendGuildChannelPrivateMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.OpenAPI, message callapi.ActionMessage) { + params := message.Params + messageText, foundItems := parseMessageContent(params) + + guildID, channelID, err := getGuildIDFromMessage(message) + if err != nil { + log.Printf("Error getting guild_id and channel_id: %v", err) + return + } + + // 获取 echo 的值 + echostr := message.Echo + messageID := echo.GetMsgIDByKey(echostr) + log.Println("私聊信息对应的message_id:", messageID) + log.Println("私聊信息messageText:", messageText) + log.Println("foundItems:", foundItems) + + timestamp := time.Now().Unix() + timestampStr := fmt.Sprintf("%d", timestamp) + + // 构造 dm (dms 私信事件) + dm := &dto.DirectMessage{ + GuildID: guildID, + ChannelID: channelID, + CreateTime: timestampStr, + } + + // 优先发送文本信息 + if messageText != "" { + textMsg := generateReplyMessage(messageID, nil, messageText) + if _, err := apiv2.PostDirectMessage(context.TODO(), dm, textMsg); err != nil { + log.Printf("发送文本信息失败: %v", err) + } + } + + // 遍历foundItems并发送每种信息 + for key, urls := range foundItems { + var singleItem = make(map[string][]string) + singleItem[key] = urls + + reply := generateReplyMessage(messageID, singleItem, "") + if _, err := apiv2.PostDirectMessage(context.TODO(), dm, reply); err != nil { + log.Printf("发送 %s 信息失败: %v", key, err) + } + } +} + +// 这个函数可以通过int类型的虚拟userid反推真实的guild_id和channel_id +func getGuildIDFromMessage(message callapi.ActionMessage) (string, string, error) { + var userID string + + // 判断UserID的类型,并将其转换为string + switch v := message.Params.UserID.(type) { + case int: + userID = strconv.Itoa(v) + case string: + userID = v + default: + return "", "", fmt.Errorf("unexpected type for UserID") + } + + // 使用RetrieveRowByID还原真实的UserID + realUserID, err := idmap.RetrieveRowByID(userID) + if err != nil { + return "", "", fmt.Errorf("error retrieving real UserID: %v", err) + } + // 使用realUserID作为sectionName从数据库中获取channel_id + channelID, err := idmap.ReadConfig(realUserID, "channel_id") + if err != nil { + return "", "", fmt.Errorf("error reading channel_id: %v", err) + } + + // 使用channelID作为sectionName从数据库中获取guild_id + guildID, err := idmap.ReadConfig(channelID, "guild_id") + if err != nil { + return "", "", fmt.Errorf("error reading guild_id: %v", err) + } + + return guildID, channelID, nil +} diff --git a/idmap/iniMapping.go b/idmap/iniMapping.go deleted file mode 100644 index a49cbbaf..00000000 --- a/idmap/iniMapping.go +++ /dev/null @@ -1,68 +0,0 @@ -package idmap - -import ( - "fmt" - "os" - "path/filepath" - - "gopkg.in/ini.v1" -) - -type IniMapping struct { - cfg *ini.File - filePath string -} - -// NewIniMapping creates a new IniMapping instance with default or provided filePath. -func NewIniMapping(optionalFilePath ...string) (*IniMapping, error) { - defaultPath := filepath.Join(".", "idmap.ini") - if len(optionalFilePath) > 0 { - defaultPath = optionalFilePath[0] - } - - cfg, err := ini.LoadSources(ini.LoadOptions{ - AllowBooleanKeys: true, - }, defaultPath) - if err != nil { - if os.IsNotExist(err) { - cfg = ini.Empty() - } else { - return nil, err - } - } - - return &IniMapping{ - cfg: cfg, - filePath: defaultPath, - }, nil -} - -// WriteConfig writes a value into the specified section and key. -func (m *IniMapping) WriteConfig(sectionName, keyName, value string) error { - section, err := m.cfg.NewSection(sectionName) - if err != nil { - return err - } - - _, err = section.NewKey(keyName, value) - if err != nil { - return err - } - - return m.cfg.SaveToIndent(m.filePath, "\t") -} - -// ReadConfig reads a value from the specified section and key. -func (m *IniMapping) ReadConfig(sectionName, keyName string) (string, error) { - section := m.cfg.Section(sectionName) - if section == nil { - return "", fmt.Errorf("section '%s' does not exist", sectionName) - } - - key := section.Key(keyName) - if key == nil { - return "", fmt.Errorf("key '%s' in section '%s' does not exist", keyName, sectionName) - } - - return key.String(), nil -} diff --git a/idmap/service.go b/idmap/service.go index 8db35aba..32709074 100644 --- a/idmap/service.go +++ b/idmap/service.go @@ -10,9 +10,10 @@ import ( ) const ( - DBName = "idmap.db" - BucketName = "ids" - CounterKey = "currentRow" + DBName = "idmap.db" + BucketName = "ids" + ConfigBucket = "config" + CounterKey = "currentRow" ) var db *bolt.DB @@ -36,6 +37,7 @@ func CloseDB() { db.Close() } +// 根据a储存b func StoreID(id string) (int64, error) { var newRow int64 @@ -72,6 +74,7 @@ func StoreID(id string) (int64, error) { return newRow, err } +// 根据b得到a func RetrieveRowByID(rowid string) (string, error) { var id string err := db.View(func(tx *bolt.Tx) error { @@ -89,3 +92,43 @@ func RetrieveRowByID(rowid string) (string, error) { return id, err } + +// 根据a 以b为类别 储存c +func WriteConfig(sectionName, keyName, value string) error { + return db.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucketIfNotExists([]byte(ConfigBucket)) + if err != nil { + return err + } + + key := joinSectionAndKey(sectionName, keyName) + return b.Put(key, []byte(value)) + }) +} + +// 根据a和b取出c +func ReadConfig(sectionName, keyName string) (string, error) { + var result string + err := db.View(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte(ConfigBucket)) + if b == nil { + return fmt.Errorf("bucket not found") + } + + key := joinSectionAndKey(sectionName, keyName) + v := b.Get(key) + if v == nil { + return fmt.Errorf("key '%s' in section '%s' does not exist", keyName, sectionName) + } + + result = string(v) + return nil + }) + + return result, err +} + +// 灵感,ini配置文件 +func joinSectionAndKey(sectionName, keyName string) []byte { + return []byte(sectionName + ":" + keyName) +} diff --git a/main.go b/main.go index 3daeed84..ea42ba69 100644 --- a/main.go +++ b/main.go @@ -87,7 +87,9 @@ func main() { // 获取 websocket 信息 这里用哪一个api获取就是用哪一个api去连接ws // 测试群时候用api2 并且要注释掉api.me - wsInfo, err := api.WS(ctx, nil, "") + //似乎正式场景都可以用apiv2(群)的方式获取ws连接,包括频道的机器人 + //疑问: 为什么无法用apiv2的方式调用频道的getme接口,会报错 + wsInfo, err := apiV2.WS(ctx, nil, "") if err != nil { log.Fatalln(err) } @@ -310,5 +312,29 @@ func getIDHandler(c *gin.Context) { return } c.JSON(http.StatusOK, gin.H{"id": id}) + + case 3: + // 存储 + section := c.Query("id") + subtype := c.Query("subtype") + value := c.Query("value") + err := idmap.WriteConfig(section, subtype, value) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"status": "success"}) + + case 4: + // 获取值 + section := c.Query("id") + subtype := c.Query("subtype") + value, err := idmap.ReadConfig(section, subtype) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"value": value}) } + } diff --git a/readme.md b/readme.md index 97f5fe50..e3515e23 100644 --- a/readme.md +++ b/readme.md @@ -110,7 +110,7 @@ todo,正在施工中 | API | 功能 | | ------------------------ | ---------------------- | -| /send_private_msg | [发送私聊消息] | +| /send_private_msg√ | [发送私聊消息] | | /send_group_msg√ | [发送群消息] | | /send_guild_channel_msg√ | [发送频道消息] | | /send_msg | [发送消息] | From 7fc1f109b687a6b4475a67a649edb9f80e85c4aa Mon Sep 17 00:00:00 2001 From: cosmo Date: Tue, 24 Oct 2023 14:47:53 +0800 Subject: [PATCH 04/27] =?UTF-8?q?=E9=80=82=E9=85=8D=E4=BA=86nonebot2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Processor/Processor.go | 21 +++- botgo/openapi/iface.go | 2 + botgo/openapi/v1/message.go | 60 ++++++++++ botgo/openapi/v2/message.go | 60 ++++++++++ callapi/callapi.go | 1 + handlers/send_group_msg.go | 80 ++++++++++++- handlers/send_guild_channel_msg.go | 122 ++++++++++++++----- handlers/send_msg.go | 185 +++++++++++++++++++++++++++++ handlers/send_private_msg.go | 30 +++-- wsclient/ws.go | 8 +- 10 files changed, 525 insertions(+), 44 deletions(-) create mode 100644 handlers/send_msg.go diff --git a/Processor/Processor.go b/Processor/Processor.go index 794a84c7..03688c23 100644 --- a/Processor/Processor.go +++ b/Processor/Processor.go @@ -86,10 +86,10 @@ type OnebotPrivateMessage struct { Time int64 `json:"time"` Avatar string `json:"avatar"` Echo string `json:"echo"` - Message interface{} `json:"message"` // For array format - MessageSeq int `json:"message_seq,omitempty"` // Optional field - Font int `json:"font,omitempty"` // Optional field - UserID int64 `json:"user_id"` // Can be either string or int depending on logic + Message interface{} `json:"message"` // For array format + MessageSeq int `json:"message_seq"` // Optional field + Font int `json:"font"` // Optional field + UserID int64 `json:"user_id"` // Can be either string or int depending on logic } type PrivateSender struct { @@ -157,6 +157,8 @@ func (p *Processor) ProcessGuildATMessage(data *dto.WSATMessageData) error { //将当前s和appid和message进行映射 echo.AddMsgID(AppIDString, s, data.ID) echo.AddMsgType(AppIDString, s, "guild") + //为不支持双向echo的ob11服务端映射 + echo.AddMsgType(AppIDString, userid64, "guild") //调试 PrintStructWithFieldNames(onebotMsg) @@ -225,6 +227,9 @@ func (p *Processor) ProcessGuildATMessage(data *dto.WSATMessageData) error { //将当前s和appid和message进行映射 echo.AddMsgID(AppIDString, s, data.ID) echo.AddMsgType(AppIDString, s, "guild") + //为不支持双向echo的ob服务端映射 + fmt.Printf("AppIDString: %s, channelIDInt: %d\n", AppIDString, int64(channelIDInt)) + echo.AddMsgType(AppIDString, int64(channelIDInt), "guild") //调试 PrintStructWithFieldNames(groupMsg) @@ -318,6 +323,8 @@ func (p *Processor) ProcessGroupMessage(data *dto.WSGroupATMessageData) error { // 将当前s和appid和message进行映射 echo.AddMsgID(AppIDString, s, data.ID) echo.AddMsgType(AppIDString, s, "group") + //为不支持双向echo的ob服务端映射 + echo.AddMsgType(AppIDString, userid64, "group") // 调试 PrintStructWithFieldNames(groupMsg) @@ -345,7 +352,7 @@ func (p *Processor) ProcessChannelDirectMessage(data *dto.WSDirectMessageData) e //获取当前的s值 当前ws连接所收到的信息条数 s := client.GetGlobalS() - if !p.Settings.GlobalChannelToGroup { + if !p.Settings.GlobalPrivateToChannel { // 把频道类型的私信转换成普通ob11的私信 //转换appidstring @@ -395,6 +402,8 @@ func (p *Processor) ProcessChannelDirectMessage(data *dto.WSDirectMessageData) e // 将当前s和appid和message进行映射 echo.AddMsgID(AppIDString, s, data.ID) echo.AddMsgType(AppIDString, s, "guild_private") + //其实不需要用AppIDString,因为gensokyo是单机器人框架 + echo.AddMsgType(AppIDString, userid64, "guild_private") // 调试 PrintStructWithFieldNames(privateMsg) @@ -455,6 +464,8 @@ func (p *Processor) ProcessChannelDirectMessage(data *dto.WSDirectMessageData) e echo.AddMsgID(AppIDString, s, data.ID) //通过echo始终得知真实的事件类型,来对应调用正确的api echo.AddMsgType(AppIDString, s, "guild_private") + //为不支持双向echo的ob服务端映射 + echo.AddMsgType(AppIDString, userid64, "guild_private") //调试 PrintStructWithFieldNames(onebotMsg) diff --git a/botgo/openapi/iface.go b/botgo/openapi/iface.go index 48f237fa..03916c88 100644 --- a/botgo/openapi/iface.go +++ b/botgo/openapi/iface.go @@ -67,6 +67,8 @@ type MessageAPI interface { Message(ctx context.Context, channelID string, messageID string) (*dto.Message, error) Messages(ctx context.Context, channelID string, pager *dto.MessagesPager) ([]*dto.Message, error) PostMessage(ctx context.Context, channelID string, msg *dto.MessageToCreate) (*dto.Message, error) + // 增加的新的接口,发频道multipart信息 + PostMessageMultipart(ctx context.Context, channelID string, msg *dto.MessageToCreate, fileImageData []byte) (*dto.Message, error) PatchMessage(ctx context.Context, channelID string, messageID string, msg *dto.MessageToCreate) (*dto.Message, error) RetractMessage(ctx context.Context, channelID, msgID string, options ...RetractMessageOption) error diff --git a/botgo/openapi/v1/message.go b/botgo/openapi/v1/message.go index 1d5b271c..716bc62e 100644 --- a/botgo/openapi/v1/message.go +++ b/botgo/openapi/v1/message.go @@ -1,9 +1,11 @@ package v1 import ( + "bytes" "context" "encoding/json" "fmt" + "strconv" "github.com/tidwall/gjson" @@ -69,6 +71,64 @@ func (o *openAPI) PostMessage(ctx context.Context, channelID string, msg *dto.Me return resp.Result().(*dto.Message), nil } +// PostMessageMultipart 发送消息使用multipart/form-data +func (o *openAPI) PostMessageMultipart(ctx context.Context, channelID string, msg *dto.MessageToCreate, fileImageData []byte) (*dto.Message, error) { + request := o.request(ctx).SetResult(dto.Message{}).SetPathParam("channel_id", channelID) + + // 将MessageToCreate的字段转为multipart form data + if msg.Content != "" { + request = request.SetFormData(map[string]string{"content": msg.Content}) + } + if msg.MsgType != 0 { + request = request.SetFormData(map[string]string{"msg_type": strconv.Itoa(msg.MsgType)}) + } + if msg.Embed != nil { + request = request.SetFormData(map[string]string{"embed": toJSON(msg.Embed)}) + } + if msg.Ark != nil { + request = request.SetFormData(map[string]string{"ark": toJSON(msg.Ark)}) + } + if msg.Image != "" { + request = request.SetFormData(map[string]string{"image": msg.Image}) + } + if msg.MsgID != "" { + request = request.SetFormData(map[string]string{"msg_id": msg.MsgID}) + } + if msg.MessageReference != nil { + request = request.SetFormData(map[string]string{"message_reference": toJSON(msg.MessageReference)}) + } + if msg.Markdown != nil { + request = request.SetFormData(map[string]string{"markdown": toJSON(msg.Markdown)}) + } + if msg.Keyboard != nil { + request = request.SetFormData(map[string]string{"keyboard": toJSON(msg.Keyboard)}) + } + if msg.EventID != "" { + request = request.SetFormData(map[string]string{"event_id": msg.EventID}) + } + + // 如果提供了fileImageData,则设置file_image + if len(fileImageData) > 0 { + request = request.SetFileReader("file_image", "filename.jpg", bytes.NewReader(fileImageData)) + } + + resp, err := request.Post(o.getURL(messagesURI)) + if err != nil { + return nil, err + } + + return resp.Result().(*dto.Message), nil +} + +// 辅助函数:序列化为JSON +func toJSON(obj interface{}) string { + bytes, err := json.Marshal(obj) + if err != nil { + return "" + } + return string(bytes) +} + // PatchMessage 编辑消息 func (o *openAPI) PatchMessage(ctx context.Context, channelID string, messageID string, msg *dto.MessageToCreate) (*dto.Message, error) { diff --git a/botgo/openapi/v2/message.go b/botgo/openapi/v2/message.go index 76e5aae2..ed6a1af9 100644 --- a/botgo/openapi/v2/message.go +++ b/botgo/openapi/v2/message.go @@ -1,9 +1,11 @@ package v2 import ( + "bytes" "context" "encoding/json" "fmt" + "strconv" "github.com/tidwall/gjson" @@ -69,6 +71,64 @@ func (o *openAPIv2) PostMessage(ctx context.Context, channelID string, msg *dto. return resp.Result().(*dto.Message), nil } +// PostMessageMultipart 发送消息使用multipart/form-data +func (o *openAPIv2) PostMessageMultipart(ctx context.Context, channelID string, msg *dto.MessageToCreate, fileImageData []byte) (*dto.Message, error) { + request := o.request(ctx).SetResult(dto.Message{}).SetPathParam("channel_id", channelID) + + // 将MessageToCreate的字段转为multipart form data + if msg.Content != "" { + request = request.SetFormData(map[string]string{"content": msg.Content}) + } + if msg.MsgType != 0 { + request = request.SetFormData(map[string]string{"msg_type": strconv.Itoa(msg.MsgType)}) + } + if msg.Embed != nil { + request = request.SetFormData(map[string]string{"embed": toJSON(msg.Embed)}) + } + if msg.Ark != nil { + request = request.SetFormData(map[string]string{"ark": toJSON(msg.Ark)}) + } + if msg.Image != "" { + request = request.SetFormData(map[string]string{"image": msg.Image}) + } + if msg.MsgID != "" { + request = request.SetFormData(map[string]string{"msg_id": msg.MsgID}) + } + if msg.MessageReference != nil { + request = request.SetFormData(map[string]string{"message_reference": toJSON(msg.MessageReference)}) + } + if msg.Markdown != nil { + request = request.SetFormData(map[string]string{"markdown": toJSON(msg.Markdown)}) + } + if msg.Keyboard != nil { + request = request.SetFormData(map[string]string{"keyboard": toJSON(msg.Keyboard)}) + } + if msg.EventID != "" { + request = request.SetFormData(map[string]string{"event_id": msg.EventID}) + } + + // 如果提供了fileImageData,则设置file_image + if len(fileImageData) > 0 { + request = request.SetFileReader("file_image", "filename.jpg", bytes.NewReader(fileImageData)) + } + + resp, err := request.Post(o.getURL(messagesURI)) + if err != nil { + return nil, err + } + + return resp.Result().(*dto.Message), nil +} + +// 辅助函数:序列化为JSON +func toJSON(obj interface{}) string { + bytes, err := json.Marshal(obj) + if err != nil { + return "" + } + return string(bytes) +} + // PatchMessage 编辑消息 func (o *openAPIv2) PatchMessage(ctx context.Context, channelID string, messageID string, msg *dto.MessageToCreate) (*dto.Message, error) { diff --git a/callapi/callapi.go b/callapi/callapi.go index 4a64791c..bfd3133a 100644 --- a/callapi/callapi.go +++ b/callapi/callapi.go @@ -61,6 +61,7 @@ type Message struct { // 这是一个接口,在wsclient传入client但不需要引用wsclient包,避免循环引用 type Client interface { SendMessage(message map[string]interface{}) error + GetAppID() string } // 根据action订阅handler处理api diff --git a/handlers/send_group_msg.go b/handlers/send_group_msg.go index 2df97b68..fbd1d5b3 100644 --- a/handlers/send_group_msg.go +++ b/handlers/send_group_msg.go @@ -3,6 +3,7 @@ package handlers import ( "context" "log" + "strconv" "time" "github.com/hoshinonyaruko/gensokyo/callapi" @@ -20,6 +21,16 @@ func handleSendGroupMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openap // 使用 message.Echo 作为key来获取消息类型 msgType := echo.GetMsgTypeByKey(message.Echo) + //如果获取不到 就用user_id获取信息类型 + if msgType == "" { + msgType = GetMessageTypeByUserid(client.GetAppID(), message.Params.UserID) + } + + //如果获取不到 就用group_id获取信息类型 + if msgType == "" { + msgType = GetMessageTypeByGroupid(client.GetAppID(), message.Params.GroupID) + } + switch msgType { case "group": // 解析消息内容 @@ -44,7 +55,7 @@ func handleSendGroupMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openap // 优先发送文本信息 if messageText != "" { - groupReply := generateGroupMessage(messageID, nil, messageText) + groupReply := generateMessage(messageID, nil, messageText) // 进行类型断言 groupMessage, ok := groupReply.(*dto.MessageToCreate) @@ -65,7 +76,7 @@ func handleSendGroupMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openap var singleItem = make(map[string][]string) singleItem[key] = urls - groupReply := generateGroupMessage(messageID, singleItem, "") + groupReply := generateMessage(messageID, singleItem, "") // 进行类型断言 richMediaMessage, ok := groupReply.(*dto.RichMediaMessage) @@ -84,6 +95,29 @@ func handleSendGroupMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openap //用GroupID给ChannelID赋值,因为我们是把频道虚拟成了群 message.Params.ChannelID = message.Params.GroupID.(string) //读取ini 通过ChannelID取回之前储存的guild_id + // value, err := idmap.ReadConfig(message.Params.ChannelID, "guild_id") + // if err != nil { + // log.Printf("Error reading config: %v", err) + // return + // } + //这一句是group_private的逻辑,发频道信息用的是channelid,只有频道private需要guildid才需要这些逻辑 + //message.Params.GroupID = value + handleSendGuildChannelMsg(client, api, apiv2, message) + case "guild_private": + //用group_id还原出channelid 这是虚拟成群的私聊信息 + message.Params.ChannelID = message.Params.GroupID.(string) + //读取ini 通过ChannelID取回之前储存的guild_id + value, err := idmap.ReadConfig(message.Params.ChannelID, "guild_id") + if err != nil { + log.Printf("Error reading config: %v", err) + return + } + handleSendGuildChannelPrivateMsg(client, api, apiv2, message, &value, &message.Params.ChannelID) + case "group_private": + //用userid还原出openid 这是虚拟成群的群聊私聊信息 + //todo + message.Params.ChannelID = message.Params.GroupID.(string) + //读取ini 通过ChannelID取回之前储存的guild_id value, err := idmap.ReadConfig(message.Params.ChannelID, "guild_id") if err != nil { log.Printf("Error reading config: %v", err) @@ -127,3 +161,45 @@ func generateGroupMessage(id string, foundItems map[string][]string, messageText } return nil } + +// 通过user_id获取类型 +func GetMessageTypeByUserid(appID string, userID interface{}) string { + // 从appID和userID生成key + var userIDStr string + switch u := userID.(type) { + case int: + userIDStr = strconv.Itoa(u) + case int64: + userIDStr = strconv.FormatInt(u, 10) + case float64: + userIDStr = strconv.FormatFloat(u, 'f', 0, 64) + case string: + userIDStr = u + default: + // 可能需要处理其他类型或报错 + return "" + } + + key := appID + "_" + userIDStr + return echo.GetMsgTypeByKey(key) +} + +// 通过group_id获取类型 +func GetMessageTypeByGroupid(appID string, GroupID interface{}) string { + // 从appID和userID生成key + var GroupIDStr string + switch u := GroupID.(type) { + case int: + GroupIDStr = strconv.Itoa(u) + case int64: + GroupIDStr = strconv.FormatInt(u, 10) + case string: + GroupIDStr = u + default: + // 可能需要处理其他类型或报错 + return "" + } + + key := appID + "_" + GroupIDStr + return echo.GetMsgTypeByKey(key) +} diff --git a/handlers/send_guild_channel_msg.go b/handlers/send_guild_channel_msg.go index 6c002a60..06d24544 100644 --- a/handlers/send_guild_channel_msg.go +++ b/handlers/send_guild_channel_msg.go @@ -2,6 +2,8 @@ package handlers import ( "context" + "encoding/base64" + "fmt" "log" "github.com/hoshinonyaruko/gensokyo/callapi" @@ -17,39 +19,97 @@ func init() { } func handleSendGuildChannelMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.OpenAPI, message callapi.ActionMessage) { - params := message.Params - messageText, foundItems := parseMessageContent(params) - - channelID := params.ChannelID - // 获取 echo 的值 - echostr := message.Echo - - //messageType := echo.GetMsgTypeByKey(echostr) - messageID := echo.GetMsgIDByKey(echostr) - log.Println("频道发信息对应的message_id:", messageID) - log.Println("频道发信息messageText:", messageText) - log.Println("foundItems:", foundItems) - // 优先发送文本信息 - if messageText != "" { - textMsg := generateReplyMessage(messageID, nil, messageText) - if _, err := api.PostMessage(context.TODO(), channelID, textMsg); err != nil { - log.Printf("发送文本信息失败: %v", err) - } + // 使用 message.Echo 作为key来获取消息类型 + msgType := echo.GetMsgTypeByKey(message.Echo) + + //如果获取不到 就用user_id获取信息类型 + if msgType == "" { + msgType = GetMessageTypeByUserid(client.GetAppID(), message.Params.UserID) + } + + //如果获取不到 就用group_id获取信息类型 + if msgType == "" { + appID := client.GetAppID() + groupID := message.Params.GroupID + fmt.Printf("appID: %s, GroupID: %v\n", appID, groupID) + + msgType = GetMessageTypeByGroupid(appID, groupID) + fmt.Printf("msgType: %s\n", msgType) } - // 遍历foundItems并发送每种信息 - for key, urls := range foundItems { - var singleItem = make(map[string][]string) - singleItem[key] = urls + switch msgType { + //原生guild信息 + case "guild": + params := message.Params + messageText, foundItems := parseMessageContent(params) + + channelID := params.ChannelID + // 获取 echo 的值 + echostr := message.Echo + + //messageType := echo.GetMsgTypeByKey(echostr) + messageID := echo.GetMsgIDByKey(echostr) + log.Println("频道发信息对应的message_id:", messageID) + log.Println("频道发信息messageText:", messageText) + log.Println("foundItems:", foundItems) + // 优先发送文本信息 + if messageText != "" { + textMsg, _ := generateReplyMessage(messageID, nil, messageText) + if _, err := api.PostMessage(context.TODO(), channelID, textMsg); err != nil { + log.Printf("发送文本信息失败: %v", err) + } + } + + // 遍历foundItems并发送每种信息 + for key, urls := range foundItems { + var singleItem = make(map[string][]string) + singleItem[key] = urls + + reply, isBase64Image := generateReplyMessage(messageID, singleItem, "") + + if isBase64Image { + // 将base64内容从reply的Content转换回字节 + fileImageData, err := base64.StdEncoding.DecodeString(reply.Content) + if err != nil { + log.Printf("Base64 解码失败: %v", err) + return // 或其他的错误处理方式 + } + // 将解码后的数据保存到文件中以进行验证 + // err = ioutil.WriteFile("1.jpg", fileImageData, 0644) + // if err != nil { + // log.Printf("写入文件失败: %v", err) + // return // 或其他的错误处理方式 + // } + + // 清除reply的Content + reply.Content = "" + + // 使用Multipart方法发送 + if _, err := api.PostMessageMultipart(context.TODO(), channelID, reply, fileImageData); err != nil { + log.Printf("使用multipart发送 %s 信息失败: %v", key, err) + } + } else { + if _, err := api.PostMessage(context.TODO(), channelID, reply); err != nil { + log.Printf("发送 %s 信息失败: %v", key, err) + } + } - reply := generateReplyMessage(messageID, singleItem, "") - if _, err := api.PostMessage(context.TODO(), channelID, reply); err != nil { - log.Printf("发送 %s 信息失败: %v", key, err) } + //频道私信 此时直接取出 + case "guild_private": + params := message.Params + channelID := params.ChannelID + guildID := params.GuildID + handleSendGuildChannelPrivateMsg(client, api, apiv2, message, &guildID, &channelID) + default: + log.Printf("2Unknown message type: %s", msgType) } } -func generateReplyMessage(id string, foundItems map[string][]string, messageText string) *dto.MessageToCreate { + +// 组合发频道信息需要的MessageToCreate +func generateReplyMessage(id string, foundItems map[string][]string, messageText string) (*dto.MessageToCreate, bool) { var reply dto.MessageToCreate + var isBase64 bool if imageURLs, ok := foundItems["local_image"]; ok && len(imageURLs) > 0 { // todo 完善本地文件上传 发送机制 @@ -78,6 +138,14 @@ func generateReplyMessage(id string, foundItems map[string][]string, messageText // MsgID: id, // MsgType: 0, // Adjust type as needed for voice // } + } else if base64_image, ok := foundItems["base64_image"]; ok && len(base64_image) > 0 { + // base64图片 + reply = dto.MessageToCreate{ + Content: base64_image[0], // 直接使用base64_image[0]作为Content + MsgID: id, + MsgType: 0, // Default type for text + } + isBase64 = true } else { // 发文本信息 reply = dto.MessageToCreate{ @@ -88,5 +156,5 @@ func generateReplyMessage(id string, foundItems map[string][]string, messageText } } - return &reply + return &reply, isBase64 } diff --git a/handlers/send_msg.go b/handlers/send_msg.go new file mode 100644 index 00000000..15b36d96 --- /dev/null +++ b/handlers/send_msg.go @@ -0,0 +1,185 @@ +package handlers + +import ( + "context" + "fmt" + "log" + "time" + + "github.com/hoshinonyaruko/gensokyo/callapi" + "github.com/hoshinonyaruko/gensokyo/echo" + "github.com/hoshinonyaruko/gensokyo/idmap" + "github.com/tencent-connect/botgo/dto" + "github.com/tencent-connect/botgo/openapi" +) + +func init() { + callapi.RegisterHandler("send_msg", handleSendMsg) +} + +func handleSendMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.OpenAPI, message callapi.ActionMessage) { + // 使用 message.Echo 作为key来获取消息类型 + msgType := echo.GetMsgTypeByKey(message.Echo) + + //如果获取不到 就用user_id获取信息类型 + if msgType == "" { + msgType = GetMessageTypeByUserid(client.GetAppID(), message.Params.UserID) + } + + //如果获取不到 就用group_id获取信息类型 + if msgType == "" { + appID := client.GetAppID() + groupID := message.Params.GroupID + fmt.Printf("appID: %s, GroupID: %v\n", appID, groupID) + + msgType = GetMessageTypeByGroupid(appID, groupID) + fmt.Printf("msgType: %s\n", msgType) + } + + switch msgType { + case "group": + // 解析消息内容 + messageText, foundItems := parseMessageContent(message.Params) + + // 获取 echo 的值 + echostr := message.Echo + + // 使用 echo 获取消息ID + messageID := echo.GetMsgIDByKey(echostr) + log.Println("群组发信息对应的message_id:", messageID) + log.Println("群组发信息messageText:", messageText) + log.Println("foundItems:", foundItems) + + //通过bolt数据库还原真实的GroupID + originalGroupID, err := idmap.RetrieveRowByID(message.Params.GroupID.(string)) + if err != nil { + log.Printf("Error retrieving original GroupID: %v", err) + return + } + message.Params.GroupID = originalGroupID + + // 优先发送文本信息 + if messageText != "" { + groupReply := generateGroupMessage(messageID, nil, messageText) + + // 进行类型断言 + groupMessage, ok := groupReply.(*dto.MessageToCreate) + if !ok { + log.Println("Error: Expected MessageToCreate type.") + return // 或其他错误处理 + } + + groupMessage.Timestamp = time.Now().Unix() // 设置时间戳 + _, err := apiv2.PostGroupMessage(context.TODO(), message.Params.GroupID.(string), groupMessage) + if err != nil { + log.Printf("发送文本群组信息失败: %v", err) + } + } + + // 遍历foundItems并发送每种信息 + for key, urls := range foundItems { + var singleItem = make(map[string][]string) + singleItem[key] = urls + + groupReply := generateGroupMessage(messageID, singleItem, "") + + // 进行类型断言 + richMediaMessage, ok := groupReply.(*dto.RichMediaMessage) + if !ok { + log.Printf("Error: Expected RichMediaMessage type for key %s.", key) + continue // 跳过这个项,继续下一个 + } + + //richMediaMessage.Timestamp = time.Now().Unix() // 设置时间戳 + _, err := apiv2.PostGroupMessage(context.TODO(), message.Params.GroupID.(string), richMediaMessage) + if err != nil { + log.Printf("发送 %s 信息失败: %v", key, err) + } + } + case "guild": + //用GroupID给ChannelID赋值,因为我们是把频道虚拟成了群 + message.Params.ChannelID = message.Params.GroupID.(string) + //读取ini 通过ChannelID取回之前储存的guild_id + // value, err := idmap.ReadConfig(message.Params.ChannelID, "guild_id") + // if err != nil { + // log.Printf("Error reading config: %v", err) + // return + // } + // message.Params.GroupID = value + handleSendGuildChannelMsg(client, api, apiv2, message) + case "guild_private": + //send_msg比具体的send_xxx少一层,其包含的字段类型在虚拟化场景已经失去作用 + //根据userid绑定得到的具体真实事件类型,这里也有多种可能性 + //1,私聊(但虚拟成了群),这里用群号取得需要的id + //2,频道私聊(但虚拟成了私聊)这里传递2个nil,用user_id去推测channel_id和guild_id + + var channelIDPtr *string + var GuildidPtr *string + + // 先尝试将GroupID断言为字符串 + if channelID, ok := message.Params.GroupID.(string); ok && channelID != "" { + channelIDPtr = &channelID + // 读取bolt数据库 通过ChannelID取回之前储存的guild_id + if value, err := idmap.ReadConfig(channelID, "guild_id"); err == nil && value != "" { + GuildidPtr = &value + } else { + log.Printf("Error reading config: %v", err) + } + } + + if channelIDPtr == nil || GuildidPtr == nil { + log.Printf("Value or ChannelID is empty or in error. Value: %v, ChannelID: %v", GuildidPtr, channelIDPtr) + } + + handleSendGuildChannelPrivateMsg(client, api, apiv2, message, GuildidPtr, channelIDPtr) + + case "group_private": + //用userid还原出openid 这是虚拟成群的群聊私聊信息 + //todo + message.Params.ChannelID = message.Params.GroupID.(string) + //读取ini 通过ChannelID取回之前储存的guild_id + value, err := idmap.ReadConfig(message.Params.ChannelID, "guild_id") + if err != nil { + log.Printf("Error reading config: %v", err) + return + } + message.Params.GroupID = value + handleSendGuildChannelMsg(client, api, apiv2, message) + default: + log.Printf("1Unknown message type: %s", msgType) + } +} + +func generateMessage(id string, foundItems map[string][]string, messageText string) interface{} { + if imageURLs, ok := foundItems["local_image"]; ok && len(imageURLs) > 0 { + // 本地发图逻辑 todo 适配base64图片 + return &dto.RichMediaMessage{ + EventID: id, + FileType: 1, // 1代表图片 + URL: imageURLs[0], + Content: "", // 这个字段文档没有了 + SrvSendMsg: true, + } + } else if imageURLs, ok := foundItems["url_image"]; ok && len(imageURLs) > 0 { + // 发链接图片 + return &dto.RichMediaMessage{ + EventID: id, + FileType: 1, // 1代表图片 + URL: "http://" + imageURLs[0], + Content: "", // 这个字段文档没有了 + SrvSendMsg: true, + } + } else if base64_image, ok := foundItems["base64_image"]; ok && len(base64_image) > 0 { + // 目前不支持发语音 todo 适配base64 slik + } else if voiceURLs, ok := foundItems["base64_record"]; ok && len(voiceURLs) > 0 { + // 目前不支持发语音 todo 适配base64 slik + } else { + // 返回文本信息 + return &dto.MessageToCreate{ + Content: messageText, + MsgID: id, + MsgType: 0, // 默认文本类型 + } + } + return nil +} diff --git a/handlers/send_private_msg.go b/handlers/send_private_msg.go index ec7f0f46..6de9308f 100644 --- a/handlers/send_private_msg.go +++ b/handlers/send_private_msg.go @@ -84,7 +84,7 @@ func handleSendPrivateMsg(client callapi.Client, api openapi.OpenAPI, apiv2 open } case "guild_private": //当收到发私信调用 并且来源是频道 - handleSendGuildChannelPrivateMsg(client, api, apiv2, message) + handleSendGuildChannelPrivateMsg(client, api, apiv2, message, nil, nil) default: log.Printf("Unknown message type: %s", msgType) } @@ -122,14 +122,24 @@ func generatePrivateMessage(id string, foundItems map[string][]string, messageTe return nil } -func handleSendGuildChannelPrivateMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.OpenAPI, message callapi.ActionMessage) { +// 处理频道私信 最后2个指针参数可空 代表使用userid倒推 +func handleSendGuildChannelPrivateMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.OpenAPI, message callapi.ActionMessage, optionalGuildID *string, optionalChannelID *string) { params := message.Params messageText, foundItems := parseMessageContent(params) - guildID, channelID, err := getGuildIDFromMessage(message) - if err != nil { - log.Printf("Error getting guild_id and channel_id: %v", err) - return + var guildID, channelID string + var err error + + if optionalGuildID != nil && optionalChannelID != nil { + guildID = *optionalGuildID + channelID = *optionalChannelID + } else { + //默认私信场景 通过仅有的userid来还原频道私信需要的guildid + guildID, channelID, err = getGuildIDFromMessage(message) + if err != nil { + log.Printf("获取 guild_id 和 channel_id 出错: %v", err) + return + } } // 获取 echo 的值 @@ -151,7 +161,7 @@ func handleSendGuildChannelPrivateMsg(client callapi.Client, api openapi.OpenAPI // 优先发送文本信息 if messageText != "" { - textMsg := generateReplyMessage(messageID, nil, messageText) + textMsg, _ := generateReplyMessage(messageID, nil, messageText) if _, err := apiv2.PostDirectMessage(context.TODO(), dm, textMsg); err != nil { log.Printf("发送文本信息失败: %v", err) } @@ -162,7 +172,7 @@ func handleSendGuildChannelPrivateMsg(client callapi.Client, api openapi.OpenAPI var singleItem = make(map[string][]string) singleItem[key] = urls - reply := generateReplyMessage(messageID, singleItem, "") + reply, _ := generateReplyMessage(messageID, singleItem, "") if _, err := apiv2.PostDirectMessage(context.TODO(), dm, reply); err != nil { log.Printf("发送 %s 信息失败: %v", key, err) } @@ -177,10 +187,12 @@ func getGuildIDFromMessage(message callapi.ActionMessage) (string, string, error switch v := message.Params.UserID.(type) { case int: userID = strconv.Itoa(v) + case float64: + userID = strconv.FormatInt(int64(v), 10) // 将float64先转为int64,然后再转为string case string: userID = v default: - return "", "", fmt.Errorf("unexpected type for UserID") + return "", "", fmt.Errorf("unexpected type for UserID: %T", v) // 使用%T来打印具体的类型 } // 使用RetrieveRowByID还原真实的UserID diff --git a/wsclient/ws.go b/wsclient/ws.go index 7f870d09..8e8207dd 100644 --- a/wsclient/ws.go +++ b/wsclient/ws.go @@ -17,6 +17,12 @@ type WebSocketClient struct { conn *websocket.Conn api openapi.OpenAPI apiv2 openapi.OpenAPI + appid string +} + +// 获取appid +func (c *WebSocketClient) GetAppID() string { + return c.appid } // 发送json信息给onebot应用端 @@ -126,7 +132,7 @@ func NewWebSocketClient(urlStr string, botID string, api openapi.OpenAPI, apiv2 } } - client := &WebSocketClient{conn: conn, api: api, apiv2: apiv2} + client := &WebSocketClient{conn: conn, api: api, apiv2: apiv2, appid: botID} // Sending initial message similar to your setupB function message := map[string]interface{}{ From 1336c6a5544582b5594a47d135b677df2e861b3d Mon Sep 17 00:00:00 2001 From: cosmo Date: Tue, 24 Oct 2023 17:50:17 +0800 Subject: [PATCH 05/27] add license --- LICENSE | 674 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ readme.md | 2 +- 2 files changed, 675 insertions(+), 1 deletion(-) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..e72bfdda --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. \ No newline at end of file diff --git a/readme.md b/readme.md index e3515e23..5087bd86 100644 --- a/readme.md +++ b/readme.md @@ -14,7 +14,7 @@ _✨ 基于 [OneBot](https://github.com/howmanybots/onebot/blob/master/README.md

- + license From b094949eef2f4efb43542dc6e2608ec13f8cf420 Mon Sep 17 00:00:00 2001 From: cosmo Date: Wed, 25 Oct 2023 14:22:09 +0800 Subject: [PATCH 06/27] add a lot --- .gitignore | 5 +- Processor/ProcessChannelDirectMessage.go | 16 +- Processor/ProcessGroupMessage.go | 8 +- Processor/ProcessGuildATMessage.go | 10 +- Processor/ProcessGuildNormalMessage.go | 10 +- botgo/token/authtoken.go | 98 +++++----- botgo/token/token.go | 14 +- callapi/callapi.go | 40 +++- config/config.go | 64 +++++- config_template.go | 34 ++++ config_template.yml | 36 ++-- handlers/get_group_info.go | 2 +- handlers/send_group_msg.go | 75 +++++-- handlers/send_guild_channel_msg.go | 4 +- handlers/send_msg.go | 71 +++++-- handlers/send_private_msg.go | 16 +- idmap/service.go | 164 +++++++++++++++- main.go | 101 ++++------ readme.md | 4 +- server/getIDHandler.go | 64 ++++++ server/uploadpic.go | 238 +++++++++++++++++++++++ wsclient/ws.go | 5 +- 22 files changed, 872 insertions(+), 207 deletions(-) create mode 100644 config_template.go create mode 100644 server/getIDHandler.go create mode 100644 server/uploadpic.go diff --git a/.gitignore b/.gitignore index 52efd70d..4a3dc0c7 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,7 @@ idmap.ini # Go specific go.mod go.sum -*.exe \ No newline at end of file +*.exe + +# Ignore channel_temp +channel_temp/ \ No newline at end of file diff --git a/Processor/ProcessChannelDirectMessage.go b/Processor/ProcessChannelDirectMessage.go index 4ff809d4..e2418474 100644 --- a/Processor/ProcessChannelDirectMessage.go +++ b/Processor/ProcessChannelDirectMessage.go @@ -36,14 +36,14 @@ func (p *Processor) ProcessChannelDirectMessage(data *dto.WSDirectMessageData) e echostr := AppIDString + "_" + strconv.FormatInt(s, 10) //将真实id转为int userid64 - userid64, err := idmap.StoreID(data.Author.ID) + userid64, err := idmap.StoreIDv2(data.Author.ID) if err != nil { log.Fatalf("Error storing ID: %v", err) } //将真实id写入数据库,可取出ChannelID - idmap.WriteConfig(data.Author.ID, "channel_id", data.ChannelID) + idmap.WriteConfigv2(data.Author.ID, "channel_id", data.ChannelID) //将channelid写入数据库,可取出guild_id - idmap.WriteConfig(data.ChannelID, "guild_id", data.GuildID) + idmap.WriteConfigv2(data.ChannelID, "guild_id", data.GuildID) //收到私聊信息调用的具体还原步骤 //1,idmap还原真实userid, @@ -51,7 +51,7 @@ func (p *Processor) ProcessChannelDirectMessage(data *dto.WSDirectMessageData) e //3,通过idmap用channelid获取guildid, //发信息使用的是guildid //todo 优化数据库读写次数 - messageID64, err := idmap.StoreID(data.ID) + messageID64, err := idmap.StoreIDv2(data.ID) if err != nil { log.Fatalf("Error storing ID: %v", err) } @@ -109,7 +109,7 @@ func (p *Processor) ProcessChannelDirectMessage(data *dto.WSDirectMessageData) e //构造echo echostr := AppIDString + "_" + strconv.FormatInt(s, 10) //映射str的userid到int - userid64, err := idmap.StoreID(data.Author.ID) + userid64, err := idmap.StoreIDv2(data.Author.ID) if err != nil { log.Printf("Error storing ID: %v", err) return nil @@ -159,7 +159,7 @@ func (p *Processor) ProcessChannelDirectMessage(data *dto.WSDirectMessageData) e } else { //将频道信息转化为群信息(特殊需求情况下) //将channelid写入ini,可取出guild_id - idmap.WriteConfig(data.ChannelID, "guild_id", data.GuildID) + idmap.WriteConfigv2(data.ChannelID, "guild_id", data.GuildID) //转换at messageText := handlers.RevertTransformedText(data.Content) //转换appid @@ -173,14 +173,14 @@ func (p *Processor) ProcessChannelDirectMessage(data *dto.WSDirectMessageData) e return fmt.Errorf("failed to convert ChannelID to int: %v", err) } //映射str的userid到int - userid64, err := idmap.StoreID(data.Author.ID) + userid64, err := idmap.StoreIDv2(data.Author.ID) if err != nil { log.Printf("Error storing ID: %v", err) return nil } //userid := int(userid64) //映射str的messageID到int - messageID64, err := idmap.StoreID(data.ID) + messageID64, err := idmap.StoreIDv2(data.ID) if err != nil { log.Printf("Error storing ID: %v", err) return nil diff --git a/Processor/ProcessGroupMessage.go b/Processor/ProcessGroupMessage.go index cce7a28e..0c823e80 100644 --- a/Processor/ProcessGroupMessage.go +++ b/Processor/ProcessGroupMessage.go @@ -20,7 +20,7 @@ func (p *Processor) ProcessGroupMessage(data *dto.WSGroupATMessageData) error { // 获取s s := client.GetGlobalS() - idmap.WriteConfig(data.ChannelID, "guild_id", data.GuildID) + idmap.WriteConfigv2(data.ChannelID, "guild_id", data.GuildID) // 转换at messageText := handlers.RevertTransformedText(data.Content) @@ -32,20 +32,20 @@ func (p *Processor) ProcessGroupMessage(data *dto.WSGroupATMessageData) error { echostr := AppIDString + "_" + strconv.FormatInt(s, 10) // 映射str的GroupID到int - GroupID64, err := idmap.StoreID(data.GroupID) + GroupID64, err := idmap.StoreIDv2(data.GroupID) if err != nil { return fmt.Errorf("failed to convert ChannelID to int: %v", err) } // 映射str的userid到int - userid64, err := idmap.StoreID(data.Author.ID) + userid64, err := idmap.StoreIDv2(data.Author.ID) if err != nil { log.Printf("Error storing ID: %v", err) return nil } //userid := int(userid64) //映射str的messageID到int - messageID64, err := idmap.StoreID(data.ID) + messageID64, err := idmap.StoreIDv2(data.ID) if err != nil { log.Printf("Error storing ID: %v", err) return nil diff --git a/Processor/ProcessGuildATMessage.go b/Processor/ProcessGuildATMessage.go index 919f3ea9..68a8ed27 100644 --- a/Processor/ProcessGuildATMessage.go +++ b/Processor/ProcessGuildATMessage.go @@ -32,14 +32,14 @@ func (p *Processor) ProcessGuildATMessage(data *dto.WSATMessageData) error { //构造echo echostr := AppIDString + "_" + strconv.FormatInt(s, 10) //映射str的userid到int - userid64, err := idmap.StoreID(data.Author.ID) + userid64, err := idmap.StoreIDv2(data.Author.ID) if err != nil { log.Printf("Error storing ID: %v", err) return nil } //映射str的messageID到int //可以是string - // messageID64, err := idmap.StoreID(data.ID) + // messageID64, err := idmap.StoreIDv2(data.ID) // if err != nil { // log.Printf("Error storing ID: %v", err) // return nil @@ -93,7 +93,7 @@ func (p *Processor) ProcessGuildATMessage(data *dto.WSATMessageData) error { //获取s s := client.GetGlobalS() //将channelid写入ini,可取出guild_id todo 比ini更好的储存方式 - idmap.WriteConfig(data.ChannelID, "guild_id", data.GuildID) + idmap.WriteConfigv2(data.ChannelID, "guild_id", data.GuildID) //转换at messageText := handlers.RevertTransformedText(data.Content) //转换appid @@ -107,14 +107,14 @@ func (p *Processor) ProcessGuildATMessage(data *dto.WSATMessageData) error { return fmt.Errorf("failed to convert ChannelID to int: %v", err) } //映射str的userid到int - userid64, err := idmap.StoreID(data.Author.ID) + userid64, err := idmap.StoreIDv2(data.Author.ID) if err != nil { log.Printf("Error storing ID: %v", err) return nil } //userid := int(userid64) //映射str的messageID到int - messageID64, err := idmap.StoreID(data.ID) + messageID64, err := idmap.StoreIDv2(data.ID) if err != nil { log.Printf("Error storing ID: %v", err) return nil diff --git a/Processor/ProcessGuildNormalMessage.go b/Processor/ProcessGuildNormalMessage.go index 9d66baaf..cba53ac2 100644 --- a/Processor/ProcessGuildNormalMessage.go +++ b/Processor/ProcessGuildNormalMessage.go @@ -31,14 +31,14 @@ func (p *Processor) ProcessGuildNormalMessage(data *dto.WSMessageData) error { //构造echo echostr := AppIDString + "_" + strconv.FormatInt(s, 10) //映射str的userid到int - userid64, err := idmap.StoreID(data.Author.ID) + userid64, err := idmap.StoreIDv2(data.Author.ID) if err != nil { log.Printf("Error storing ID: %v", err) return nil } //映射str的messageID到int //可以是string - // messageID64, err := idmap.StoreID(data.ID) + // messageID64, err := idmap.StoreIDv2(data.ID) // if err != nil { // log.Printf("Error storing ID: %v", err) // return nil @@ -92,7 +92,7 @@ func (p *Processor) ProcessGuildNormalMessage(data *dto.WSMessageData) error { //获取s s := client.GetGlobalS() //将channelid写入ini,可取出guild_id todo 比ini更好的储存方式 - idmap.WriteConfig(data.ChannelID, "guild_id", data.GuildID) + idmap.WriteConfigv2(data.ChannelID, "guild_id", data.GuildID) //转换at messageText := handlers.RevertTransformedText(data.Content) //转换appid @@ -106,14 +106,14 @@ func (p *Processor) ProcessGuildNormalMessage(data *dto.WSMessageData) error { return fmt.Errorf("failed to convert ChannelID to int: %v", err) } //映射str的userid到int - userid64, err := idmap.StoreID(data.Author.ID) + userid64, err := idmap.StoreIDv2(data.Author.ID) if err != nil { log.Printf("Error storing ID: %v", err) return nil } //userid := int(userid64) //映射str的messageID到int - messageID64, err := idmap.StoreID(data.ID) + messageID64, err := idmap.StoreIDv2(data.ID) if err != nil { log.Printf("Error storing ID: %v", err) return nil diff --git a/botgo/token/authtoken.go b/botgo/token/authtoken.go index b40695be..303b302d 100644 --- a/botgo/token/authtoken.go +++ b/botgo/token/authtoken.go @@ -54,59 +54,59 @@ func (atoken *AuthTokenInfo) ForceUpToken(ctx context.Context, reason string) er // StartRefreshAccessToken 启动获取AccessToken的后台刷新 // 该函数首先会立即查询一次AccessToken,并保存。 // 然后它将在后台启动一个goroutine,定期(根据token的有效期)刷新AccessToken。 -func (atoken *AuthTokenInfo) StartRefreshAccessToken(ctx context.Context, tokenURL, appID, clientSecrent string) (err error) { - // 首先,立即获取一次AccessToken - tokenInfo, err := queryAccessToken(ctx, tokenURL, appID, clientSecrent) - if err != nil { - return err - } - atoken.setAuthToken(tokenInfo) - fmt.Printf("获取到的token是: %s\n", tokenInfo.Token) // 输出获取到的token - - // 获取token的有效期(通常以秒为单位) - tokenTTL := tokenInfo.ExpiresIn - // 使用sync.Once保证仅启动一个goroutine进行定时刷新 - atoken.once.Do(func() { - go func() { // 启动一个新的goroutine - for { - // 如果tokenTTL为0或负数,将其设置为1 - if tokenTTL <= 0 { - tokenTTL = 1 - } - select { - case <-time.NewTimer(time.Duration(tokenTTL) * time.Second).C: // 当token过期时 - case upToken := <-atoken.forceUpToken: // 接收强制更新token的信号 - log.Warnf("recv uptoken info:%v", upToken) - case <-ctx.Done(): // 当上下文结束时,退出goroutine - log.Warnf("recv ctx:%v exit refreshAccessToken", ctx.Err()) - return - } - // 查询并获取新的AccessToken - tokenInfo, err := queryAccessToken(ctx, tokenURL, appID, clientSecrent) - if err == nil { - atoken.setAuthToken(tokenInfo) - fmt.Printf("获取到的token是: %s\n", tokenInfo.Token) // 输出获取到的token - tokenTTL = tokenInfo.ExpiresIn - } else { - log.Errorf("queryAccessToken err:%v", err) - } - } - }() - }) - return -} - -// 测试用 // func (atoken *AuthTokenInfo) StartRefreshAccessToken(ctx context.Context, tokenURL, appID, clientSecrent string) (err error) { -// // 创建一个固定的token信息 -// fixedTokenInfo := AccessTokenInfo{ -// Token: "SMvPWUBuXwSyAj1UmvTKVcK7D0iEaBrmbTKVNaJFMk9S5RmpgnGvNOOOTvrqG64NhuER-e-jK2IT", -// ExpiresIn: 3600, // 这里假设token的有效时间是3600秒,你可以根据需要调整 +// // 首先,立即获取一次AccessToken +// tokenInfo, err := queryAccessToken(ctx, tokenURL, appID, clientSecrent) +// if err != nil { +// return err // } -// atoken.setAuthToken(fixedTokenInfo) -// return nil +// atoken.setAuthToken(tokenInfo) +// fmt.Printf("获取到的token是: %s\n", tokenInfo.Token) // 输出获取到的token + +// // 获取token的有效期(通常以秒为单位) +// tokenTTL := tokenInfo.ExpiresIn +// // 使用sync.Once保证仅启动一个goroutine进行定时刷新 +// atoken.once.Do(func() { +// go func() { // 启动一个新的goroutine +// for { +// // 如果tokenTTL为0或负数,将其设置为1 +// if tokenTTL <= 0 { +// tokenTTL = 1 +// } +// select { +// case <-time.NewTimer(time.Duration(tokenTTL) * time.Second).C: // 当token过期时 +// case upToken := <-atoken.forceUpToken: // 接收强制更新token的信号 +// log.Warnf("recv uptoken info:%v", upToken) +// case <-ctx.Done(): // 当上下文结束时,退出goroutine +// log.Warnf("recv ctx:%v exit refreshAccessToken", ctx.Err()) +// return +// } +// // 查询并获取新的AccessToken +// tokenInfo, err := queryAccessToken(ctx, tokenURL, appID, clientSecrent) +// if err == nil { +// atoken.setAuthToken(tokenInfo) +// fmt.Printf("获取到的token是: %s\n", tokenInfo.Token) // 输出获取到的token +// tokenTTL = tokenInfo.ExpiresIn +// } else { +// log.Errorf("queryAccessToken err:%v", err) +// } +// } +// }() +// }) +// return // } +// 测试用 +func (atoken *AuthTokenInfo) StartRefreshAccessToken(ctx context.Context, tokenURL, appID, clientSecrent string) (err error) { + // 创建一个固定的token信息 + fixedTokenInfo := AccessTokenInfo{ + Token: "PpAPgoel0-gTeaxy-ydak0kUKxJrCSlbLcwtuPt99jCPVrahkqh3WSiIy9s63tCZnTEp4asw035u", + ExpiresIn: 3600, // 这里假设token的有效时间是3600秒,你可以根据需要调整 + } + atoken.setAuthToken(fixedTokenInfo) + return nil +} + func (atoken *AuthTokenInfo) getAuthToken() AccessTokenInfo { atoken.lock.RLock() defer atoken.lock.RUnlock() diff --git a/botgo/token/token.go b/botgo/token/token.go index 2b0ad8f7..b669b243 100644 --- a/botgo/token/token.go +++ b/botgo/token/token.go @@ -91,16 +91,16 @@ func (t *Token) GetString_old() string { } // GetAccessToken 取得鉴权Token -func (t *Token) GetAccessToken() string { - return t.authToken.getAuthToken().Token -} - -// GetAccessToken 取得测试鉴权Token // func (t *Token) GetAccessToken() string { -// // 固定的token值 -// return "SMvPWUBuXwSyAj1UmvTKVcK7D0iEaBrmbTKVNaJFMk9S5RmpgnGvNOOOTvrqG64NhuER-e-jK2IT" +// return t.authToken.getAuthToken().Token // } +// GetAccessToken 取得测试鉴权Token +func (t *Token) GetAccessToken() string { + // 固定的token值 + return "PpAPgoel0-gTeaxy-ydak0kUKxJrCSlbLcwtuPt99jCPVrahkqh3WSiIy9s63tCZnTEp4asw035u" +} + // UpAccessToken 更新accessToken func (t *Token) UpAccessToken(ctx context.Context, reason interface{}) error { return t.authToken.ForceUpToken(ctx, fmt.Sprint(reason)) diff --git a/callapi/callapi.go b/callapi/callapi.go index bfd3133a..445ea523 100644 --- a/callapi/callapi.go +++ b/callapi/callapi.go @@ -4,15 +4,53 @@ import ( "encoding/json" "fmt" "log" + "strconv" "github.com/tencent-connect/botgo/openapi" ) +type EchoData struct { + Seq int `json:"seq"` +} + +type EchoContent string + +func (e *EchoContent) UnmarshalJSON(data []byte) error { + // 尝试解析为字符串 + var strVal string + if err := json.Unmarshal(data, &strVal); err == nil { + *e = EchoContent(strVal) + return nil + } + + // 尝试解析为整数 + var intVal int + if err := json.Unmarshal(data, &intVal); err == nil { + *e = EchoContent(strconv.Itoa(intVal)) + return nil + } + + // 尝试解析为EchoData结构体 + var echoData EchoData + if err := json.Unmarshal(data, &echoData); err == nil { + *e = EchoContent(strconv.Itoa(echoData.Seq)) + return nil + } + + // 如果都不符合预期,设置为空字符串 + *e = "" + return nil +} + +// func (e EchoContent) String() string { +// return string(e) +// } + // onebot发来的action调用信息 type ActionMessage struct { Action string `json:"action"` Params ParamsContent `json:"params"` - Echo string `json:"echo,omitempty"` + Echo EchoContent `json:"echo,omitempty"` } // params类型 diff --git a/config/config.go b/config/config.go index f9ca74d1..8019b144 100644 --- a/config/config.go +++ b/config/config.go @@ -3,11 +3,18 @@ package config import ( + "log" "os" + "sync" "gopkg.in/yaml.v3" ) +var ( + instance *Config + mu sync.Mutex +) + type Config struct { Version int `yaml:"version"` Settings Settings `yaml:"settings"` @@ -22,9 +29,12 @@ type Settings struct { GlobalChannelToGroup bool `yaml:"global_channel_to_group"` GlobalPrivateToChannel bool `yaml:"global_private_to_channel"` Array bool `yaml:"array"` + Server_dir string `yaml:"server_dir"` + Lotus bool `yaml:"lotus"` + Port string `yaml:"port"` } -// LoadConfig 从文件中加载配置 +// LoadConfig 从文件中加载配置并初始化单例配置 func LoadConfig(path string) (*Config, error) { configData, err := os.ReadFile(path) if err != nil { @@ -37,5 +47,57 @@ func LoadConfig(path string) (*Config, error) { return nil, err } + mu.Lock() + if instance == nil { + instance = conf + } + mu.Unlock() + return conf, nil } + +// 获取ws地址 +func GetWsAddress() string { + mu.Lock() + defer mu.Unlock() + if instance != nil { + return instance.Settings.WsAddress + } + return "" +} + +// 获取gensokyo服务的地址 +func GetServer_dir() string { + mu.Lock() + defer mu.Unlock() + + if instance == nil { + log.Println("Warning: instance is nil when trying to get upload directory.") + return "" + } + return instance.Settings.Server_dir +} + +// 获取lotus的值 +func GetLotusValue() bool { + mu.Lock() + defer mu.Unlock() + + if instance == nil { + log.Println("Warning: instance is nil when trying to get lotus value.") + return false + } + return instance.Settings.Lotus +} + +// 获取port的值 +func GetPortValue() string { + mu.Lock() + defer mu.Unlock() + + if instance == nil { + log.Println("Warning: instance is nil when trying to get port value.") + return "" + } + return instance.Settings.Port +} diff --git a/config_template.go b/config_template.go new file mode 100644 index 00000000..69999947 --- /dev/null +++ b/config_template.go @@ -0,0 +1,34 @@ +package main + +const configTemplate = ` +version: 1 +settings: + ws_address: "ws://:" # WebSocket服务的地址 + app_id: # 你的应用ID + token: "" # 你的应用令牌 + client_secret: "" # 你的客户端密钥 + + text_intent: # 请根据公域 私域来选择intent,错误的intent将连接失败 + - "ATMessageEventHandler" # 频道at信息 + - "DirectMessageHandler" # 私域频道私信(dms) + # - "ReadyHandler" # 连接成功 + # - "ErrorNotifyHandler" # 连接关闭 + # - "GuildEventHandler" # 频道事件 + # - "MemberEventHandler" # 频道成员新增 + # - "ChannelEventHandler" # 频道事件 + # - "CreateMessageHandler" # 频道不at信息 + # - "InteractionHandler" # 添加频道互动回应 + # - "GroupATMessageEventHandler" # 群at信息 仅频道机器人时候需要注释 + # - "C2CMessageEventHandler" # 群私聊 仅频道机器人时候需要注释 + # - "ThreadEventHandler" # 发帖事件 (当前版本已禁用) + + global_channel_to_group: false # 是否将频道转换成群 + global_private_to_channel: false # 是否将私聊转换成频道 + array: false + + server_dir: ":" # 提供图片上传服务的服务器(图床)需要带端口号. 如果需要发base64图,需为公网ip,且开放对应端口 + port: "" # idmaps和图床对外开放的端口号 + lotus: false # lotus特性默认为false,当为true时,将会连接到另一个lotus为false的gensokyo。 + # 使用它提供的图床和idmaps服务(场景:同一个机器人在不同服务器运行,或内网需要发送base64图)。 + # 如果需要发送base64图片,需要设置正确的公网server_dir和开放对应的port +` diff --git a/config_template.yml b/config_template.yml index 7dfbf44e..bbfd3259 100644 --- a/config_template.yml +++ b/config_template.yml @@ -4,19 +4,27 @@ settings: app_id: # 你的应用ID token: "" # 你的应用令牌 client_secret: "" # 你的客户端密钥 -text_intent: #请根据公域 私域来选择intent,错误的intent将连接失败 - - "ATMessageEventHandler" # 频道at信息 - - "DirectMessageHandler" # 私域频道私信(dms) - # - "ReadyHandler" # 连接成功 - # - "ErrorNotifyHandler" # 连接关闭 - # - "GuildEventHandler" # 频道事件 - # - "MemberEventHandler" # 频道成员新增 - # - "ChannelEventHandler" # 频道事件 - # - "CreateMessageHandler" # 频道不at信息 - # - "InteractionHandler" # 添加频道互动回应 - # - "GroupATMessageEventHandler" # 群at信息 仅频道机器人时候需要注释 - # - "C2CMessageEventHandler" # 群私聊 仅频道机器人时候需要注释 - # - "ThreadEventHandler" # 发帖事件 (当前版本已禁用) + + text_intent: # 请根据公域 私域来选择intent,错误的intent将连接失败 + - "ATMessageEventHandler" # 频道at信息 + - "DirectMessageHandler" # 私域频道私信(dms) + # - "ReadyHandler" # 连接成功 + # - "ErrorNotifyHandler" # 连接关闭 + # - "GuildEventHandler" # 频道事件 + # - "MemberEventHandler" # 频道成员新增 + # - "ChannelEventHandler" # 频道事件 + # - "CreateMessageHandler" # 频道不at信息 + # - "InteractionHandler" # 添加频道互动回应 + # - "GroupATMessageEventHandler" # 群at信息 仅频道机器人时候需要注释 + # - "C2CMessageEventHandler" # 群私聊 仅频道机器人时候需要注释 + # - "ThreadEventHandler" # 发帖事件 (当前版本已禁用) + global_channel_to_group: false # 是否将频道转换成群 global_private_to_channel: false # 是否将私聊转换成频道 - array: false \ No newline at end of file + array: false + + server_dir: ":" # 提供图片上传服务的服务器(图床)需要带端口号. 如果需要发base64图,需为公网ip,且开放对应端口 + port: "" # idmaps和图床对外开放的端口号 + lotus: false # lotus特性默认为false,当为true时,将会连接到另一个lotus为false的gensokyo。 + # 使用它提供的图床和idmaps服务(场景:同一个机器人在不同服务器运行,或内网需要发送base64图)。 + # 如果需要发送base64图片,需要设置正确的公网server_dir和开放对应的port \ No newline at end of file diff --git a/handlers/get_group_info.go b/handlers/get_group_info.go index ffa84266..3464a97d 100644 --- a/handlers/get_group_info.go +++ b/handlers/get_group_info.go @@ -58,7 +58,7 @@ func handleGetGroupInfo(client callapi.Client, api openapi.OpenAPI, apiv2 openap //用GroupID给ChannelID赋值,因为我们是把频道虚拟成了群 ChannelID := params.GroupID //读取ini 通过ChannelID取回之前储存的guild_id - value, err := idmap.ReadConfig(ChannelID.(string), "guild_id") + value, err := idmap.ReadConfigv2(ChannelID.(string), "guild_id") if err != nil { log.Printf("handleGetGroupInfo:Error reading config: %v\n", err) return diff --git a/handlers/send_group_msg.go b/handlers/send_group_msg.go index f8d983f1..18115504 100644 --- a/handlers/send_group_msg.go +++ b/handlers/send_group_msg.go @@ -2,13 +2,17 @@ package handlers import ( "context" + "encoding/base64" + "fmt" "log" + "os" "strconv" "time" "github.com/hoshinonyaruko/gensokyo/callapi" "github.com/hoshinonyaruko/gensokyo/echo" "github.com/hoshinonyaruko/gensokyo/idmap" + "github.com/hoshinonyaruko/gensokyo/server" "github.com/tencent-connect/botgo/dto" "github.com/tencent-connect/botgo/openapi" ) @@ -19,7 +23,7 @@ func init() { func handleSendGroupMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.OpenAPI, message callapi.ActionMessage) { // 使用 message.Echo 作为key来获取消息类型 - msgType := echo.GetMsgTypeByKey(message.Echo) + msgType := echo.GetMsgTypeByKey(string(message.Echo)) //如果获取不到 就用user_id获取信息类型 if msgType == "" { @@ -37,7 +41,7 @@ func handleSendGroupMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openap messageText, foundItems := parseMessageContent(message.Params) // 获取 echo 的值 - echostr := message.Echo + echostr := string(message.Echo) // 使用 echo 获取消息ID messageID := echo.GetMsgIDByKey(echostr) @@ -51,7 +55,7 @@ func handleSendGroupMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openap } //通过bolt数据库还原真实的GroupID - originalGroupID, err := idmap.RetrieveRowByID(message.Params.GroupID.(string)) + originalGroupID, err := idmap.RetrieveRowByIDv2(message.Params.GroupID.(string)) if err != nil { log.Printf("Error retrieving original GroupID: %v", err) return @@ -89,18 +93,17 @@ func handleSendGroupMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openap log.Printf("Error: Expected RichMediaMessage type for key %s.", key) continue // 跳过这个项,继续下一个 } - - //richMediaMessage.Timestamp = time.Now().Unix() // 设置时间戳 + fmt.Printf("richMediaMessage: %+v\n", richMediaMessage) _, err := apiv2.PostGroupMessage(context.TODO(), message.Params.GroupID.(string), richMediaMessage) if err != nil { - log.Printf("发送 %s 信息失败: %v", key, err) + log.Printf("发送 %s 信息失败_send_group_msg: %v", key, err) } } case "guild": //用GroupID给ChannelID赋值,因为我们是把频道虚拟成了群 message.Params.ChannelID = message.Params.GroupID.(string) //读取ini 通过ChannelID取回之前储存的guild_id - // value, err := idmap.ReadConfig(message.Params.ChannelID, "guild_id") + // value, err := idmap.ReadConfigv2(message.Params.ChannelID, "guild_id") // if err != nil { // log.Printf("Error reading config: %v", err) // return @@ -112,7 +115,7 @@ func handleSendGroupMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openap //用group_id还原出channelid 这是虚拟成群的私聊信息 message.Params.ChannelID = message.Params.GroupID.(string) //读取ini 通过ChannelID取回之前储存的guild_id - value, err := idmap.ReadConfig(message.Params.ChannelID, "guild_id") + value, err := idmap.ReadConfigv2(message.Params.ChannelID, "guild_id") if err != nil { log.Printf("Error reading config: %v", err) return @@ -123,7 +126,7 @@ func handleSendGroupMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openap //todo message.Params.ChannelID = message.Params.GroupID.(string) //读取ini 通过ChannelID取回之前储存的guild_id - value, err := idmap.ReadConfig(message.Params.ChannelID, "guild_id") + value, err := idmap.ReadConfigv2(message.Params.ChannelID, "guild_id") if err != nil { log.Printf("Error reading config: %v", err) return @@ -137,11 +140,29 @@ func handleSendGroupMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openap func generateGroupMessage(id string, foundItems map[string][]string, messageText string) interface{} { if imageURLs, ok := foundItems["local_image"]; ok && len(imageURLs) > 0 { - // 本地发图逻辑 todo 适配base64图片 + // 从本地路径读取图片 + imageData, err := os.ReadFile(imageURLs[0]) + if err != nil { + // 读入文件,如果是本地图,应用端和gensokyo需要在一台电脑 + log.Printf("Error reading the image from path %s: %v", imageURLs[0], err) + return nil + } + + // base64编码 + base64Encoded := base64.StdEncoding.EncodeToString(imageData) + + // 上传base64编码的图片并获取其URL + imageURL, err := server.UploadBase64ImageToServer(base64Encoded) + if err != nil { + log.Printf("Error uploading base64 encoded image: %v", err) + return nil + } + + // 创建RichMediaMessage并返回,当作URL图片处理 return &dto.RichMediaMessage{ EventID: id, FileType: 1, // 1代表图片 - URL: imageURLs[0], + URL: imageURL, Content: "", // 这个字段文档没有了 SrvSendMsg: true, } @@ -149,13 +170,39 @@ func generateGroupMessage(id string, foundItems map[string][]string, messageText // 发链接图片 return &dto.RichMediaMessage{ EventID: id, - FileType: 1, // 1代表图片 - URL: "http://" + imageURLs[0], - Content: "", // 这个字段文档没有了 + FileType: 1, // 1代表图片 + URL: "http://" + imageURLs[0], //url在base64时候被截断了,在这里补全 + Content: "", // 这个字段文档没有了 SrvSendMsg: true, } } else if voiceURLs, ok := foundItems["base64_record"]; ok && len(voiceURLs) > 0 { // 目前不支持发语音 todo 适配base64 slik + } else if base64_image, ok := foundItems["base64_image"]; ok && len(base64_image) > 0 { + // todo 适配base64图片 + //因为QQ群没有 form方式上传,所以在gensokyo内置了图床,需公网,或以lotus方式连接位于公网的gensokyo + //要正确的开放对应的端口和设置正确的ip地址在config,这对于一般用户是有一些难度的 + if base64Image, ok := foundItems["base64_image"]; ok && len(base64Image) > 0 { + // 解码base64图片数据 + fileImageData, err := base64.StdEncoding.DecodeString(base64Image[0]) + if err != nil { + log.Printf("failed to decode base64 image: %v", err) + return nil + } + // 将解码的图片数据转换回base64格式并上传 + imageURL, err := server.UploadBase64ImageToServer(base64.StdEncoding.EncodeToString(fileImageData)) + if err != nil { + log.Printf("failed to upload base64 image: %v", err) + return nil + } + // 创建RichMediaMessage并返回 + return &dto.RichMediaMessage{ + EventID: id, + FileType: 1, // 1代表图片 + URL: imageURL, + Content: "", // 这个字段文档没有了 + SrvSendMsg: true, + } + } } else { // 返回文本信息 return &dto.MessageToCreate{ diff --git a/handlers/send_guild_channel_msg.go b/handlers/send_guild_channel_msg.go index 834cf803..99085d24 100644 --- a/handlers/send_guild_channel_msg.go +++ b/handlers/send_guild_channel_msg.go @@ -21,7 +21,7 @@ func init() { func handleSendGuildChannelMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.OpenAPI, message callapi.ActionMessage) { // 使用 message.Echo 作为key来获取消息类型 - msgType := echo.GetMsgTypeByKey(message.Echo) + msgType := echo.GetMsgTypeByKey(string(message.Echo)) //如果获取不到 就用user_id获取信息类型 if msgType == "" { @@ -46,7 +46,7 @@ func handleSendGuildChannelMsg(client callapi.Client, api openapi.OpenAPI, apiv2 channelID := params.ChannelID // 获取 echo 的值 - echostr := message.Echo + echostr := string(message.Echo) //messageType := echo.GetMsgTypeByKey(echostr) messageID := echo.GetMsgIDByKey(echostr) diff --git a/handlers/send_msg.go b/handlers/send_msg.go index 0b63c8e7..4c4caf01 100644 --- a/handlers/send_msg.go +++ b/handlers/send_msg.go @@ -2,14 +2,17 @@ package handlers import ( "context" + "encoding/base64" "fmt" "log" + "os" "strconv" "time" "github.com/hoshinonyaruko/gensokyo/callapi" "github.com/hoshinonyaruko/gensokyo/echo" "github.com/hoshinonyaruko/gensokyo/idmap" + "github.com/hoshinonyaruko/gensokyo/server" "github.com/tencent-connect/botgo/dto" "github.com/tencent-connect/botgo/openapi" ) @@ -20,7 +23,7 @@ func init() { func handleSendMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.OpenAPI, message callapi.ActionMessage) { // 使用 message.Echo 作为key来获取消息类型 - msgType := echo.GetMsgTypeByKey(message.Echo) + msgType := echo.GetMsgTypeByKey(string(message.Echo)) //如果获取不到 就用group_id获取信息类型 if msgType == "" { @@ -43,7 +46,7 @@ func handleSendMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.Ope messageText, foundItems := parseMessageContent(message.Params) // 获取 echo 的值 - echostr := message.Echo + echostr := string(message.Echo) // 使用 echo 获取消息ID messageID := echo.GetMsgIDByKey(echostr) @@ -57,7 +60,7 @@ func handleSendMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.Ope } //通过bolt数据库还原真实的GroupID - originalGroupID, err := idmap.RetrieveRowByID(message.Params.GroupID.(string)) + originalGroupID, err := idmap.RetrieveRowByIDv2(message.Params.GroupID.(string)) if err != nil { log.Printf("Error retrieving original GroupID: %v", err) return @@ -66,7 +69,7 @@ func handleSendMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.Ope // 优先发送文本信息 if messageText != "" { - groupReply := generateGroupMessage(messageID, nil, messageText) + groupReply := generateMessage(messageID, nil, messageText) // 进行类型断言 groupMessage, ok := groupReply.(*dto.MessageToCreate) @@ -87,7 +90,7 @@ func handleSendMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.Ope var singleItem = make(map[string][]string) singleItem[key] = urls - groupReply := generateGroupMessage(messageID, singleItem, "") + groupReply := generateMessage(messageID, singleItem, "") // 进行类型断言 richMediaMessage, ok := groupReply.(*dto.RichMediaMessage) @@ -96,17 +99,17 @@ func handleSendMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.Ope continue // 跳过这个项,继续下一个 } - //richMediaMessage.Timestamp = time.Now().Unix() // 设置时间戳 + fmt.Printf("richMediaMessage: %+v\n", richMediaMessage) _, err := apiv2.PostGroupMessage(context.TODO(), message.Params.GroupID.(string), richMediaMessage) if err != nil { - log.Printf("发送 %s 信息失败: %v", key, err) + log.Printf("发送 %s 信息失败_send_msg: %v", key, err) } } case "guild": //用GroupID给ChannelID赋值,因为我们是把频道虚拟成了群 message.Params.ChannelID = message.Params.GroupID.(string) //读取ini 通过ChannelID取回之前储存的guild_id - // value, err := idmap.ReadConfig(message.Params.ChannelID, "guild_id") + // value, err := idmap.ReadConfigv2(message.Params.ChannelID, "guild_id") // if err != nil { // log.Printf("Error reading config: %v", err) // return @@ -126,7 +129,7 @@ func handleSendMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.Ope if channelID, ok := message.Params.GroupID.(string); ok && channelID != "" { channelIDPtr = &channelID // 读取bolt数据库 通过ChannelID取回之前储存的guild_id - if value, err := idmap.ReadConfig(channelID, "guild_id"); err == nil && value != "" { + if value, err := idmap.ReadConfigv2(channelID, "guild_id"); err == nil && value != "" { GuildidPtr = &value } else { log.Printf("Error reading config: %v", err) @@ -144,7 +147,7 @@ func handleSendMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.Ope //todo message.Params.ChannelID = message.Params.GroupID.(string) //读取ini 通过ChannelID取回之前储存的guild_id - value, err := idmap.ReadConfig(message.Params.ChannelID, "guild_id") + value, err := idmap.ReadConfigv2(message.Params.ChannelID, "guild_id") if err != nil { log.Printf("Error reading config: %v", err) return @@ -158,11 +161,29 @@ func handleSendMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.Ope func generateMessage(id string, foundItems map[string][]string, messageText string) interface{} { if imageURLs, ok := foundItems["local_image"]; ok && len(imageURLs) > 0 { - // 本地发图逻辑 todo 适配base64图片 + // 从本地路径读取图片 + imageData, err := os.ReadFile(imageURLs[0]) + if err != nil { + // 读入文件,如果是本地图,应用端和gensokyo需要在一台电脑 + log.Printf("Error reading the image from path %s: %v", imageURLs[0], err) + return nil + } + + // base64编码 + base64Encoded := base64.StdEncoding.EncodeToString(imageData) + + // 上传base64编码的图片并获取其URL + imageURL, err := server.UploadBase64ImageToServer(base64Encoded) + if err != nil { + log.Printf("Error uploading base64 encoded image: %v", err) + return nil + } + + // 创建RichMediaMessage并返回,当作URL图片处理 return &dto.RichMediaMessage{ EventID: id, FileType: 1, // 1代表图片 - URL: imageURLs[0], + URL: imageURL, Content: "", // 这个字段文档没有了 SrvSendMsg: true, } @@ -176,7 +197,31 @@ func generateMessage(id string, foundItems map[string][]string, messageText stri SrvSendMsg: true, } } else if base64_image, ok := foundItems["base64_image"]; ok && len(base64_image) > 0 { - // 目前不支持发语音 todo 适配base64 slik + // todo 适配base64图片 + //因为QQ群没有 form方式上传,所以在gensokyo内置了图床,需公网,或以lotus方式连接位于公网的gensokyo + //要正确的开放对应的端口和设置正确的ip地址在config,这对于一般用户是有一些难度的 + if base64Image, ok := foundItems["base64_image"]; ok && len(base64Image) > 0 { + // 解码base64图片数据 + fileImageData, err := base64.StdEncoding.DecodeString(base64Image[0]) + if err != nil { + log.Printf("failed to decode base64 image: %v", err) + return nil + } + // 将解码的图片数据转换回base64格式并上传 + imageURL, err := server.UploadBase64ImageToServer(base64.StdEncoding.EncodeToString(fileImageData)) + if err != nil { + log.Printf("failed to upload base64 image: %v", err) + return nil + } + // 创建RichMediaMessage并返回 + return &dto.RichMediaMessage{ + EventID: id, + FileType: 1, // 1代表图片 + URL: imageURL, + Content: "", // 这个字段文档没有了 + SrvSendMsg: true, + } + } } else if voiceURLs, ok := foundItems["base64_record"]; ok && len(voiceURLs) > 0 { // 目前不支持发语音 todo 适配base64 slik } else { diff --git a/handlers/send_private_msg.go b/handlers/send_private_msg.go index 6c3e3f83..2d52727c 100644 --- a/handlers/send_private_msg.go +++ b/handlers/send_private_msg.go @@ -21,7 +21,7 @@ func init() { func handleSendPrivateMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.OpenAPI, message callapi.ActionMessage) { // 使用 message.Echo 作为key来获取消息类型 - msgType := echo.GetMsgTypeByKey(message.Echo) + msgType := echo.GetMsgTypeByKey(string(message.Echo)) switch msgType { case "group_private": @@ -29,7 +29,7 @@ func handleSendPrivateMsg(client callapi.Client, api openapi.OpenAPI, apiv2 open messageText, foundItems := parseMessageContent(message.Params) // 获取 echo 的值 - echostr := message.Echo + echostr := string(message.Echo) // 使用 echo 获取消息ID messageID := echo.GetMsgIDByKey(echostr) @@ -38,7 +38,7 @@ func handleSendPrivateMsg(client callapi.Client, api openapi.OpenAPI, apiv2 open log.Println("foundItems:", foundItems) //通过bolt数据库还原真实的GroupID - originalGroupID, err := idmap.RetrieveRowByID(message.Params.GroupID.(string)) + originalGroupID, err := idmap.RetrieveRowByIDv2(message.Params.GroupID.(string)) if err != nil { log.Printf("Error retrieving original GroupID: %v", err) return @@ -144,7 +144,7 @@ func handleSendGuildChannelPrivateMsg(client callapi.Client, api openapi.OpenAPI } // 获取 echo 的值 - echostr := message.Echo + echostr := string(message.Echo) messageID := echo.GetMsgIDByKey(echostr) log.Println("私聊信息对应的message_id:", messageID) log.Println("私聊信息messageText:", messageText) @@ -220,19 +220,19 @@ func getGuildIDFromMessage(message callapi.ActionMessage) (string, string, error return "", "", fmt.Errorf("unexpected type for UserID: %T", v) // 使用%T来打印具体的类型 } - // 使用RetrieveRowByID还原真实的UserID - realUserID, err := idmap.RetrieveRowByID(userID) + // 使用RetrieveRowByIDv2还原真实的UserID + realUserID, err := idmap.RetrieveRowByIDv2(userID) if err != nil { return "", "", fmt.Errorf("error retrieving real UserID: %v", err) } // 使用realUserID作为sectionName从数据库中获取channel_id - channelID, err := idmap.ReadConfig(realUserID, "channel_id") + channelID, err := idmap.ReadConfigv2(realUserID, "channel_id") if err != nil { return "", "", fmt.Errorf("error reading channel_id: %v", err) } // 使用channelID作为sectionName从数据库中获取guild_id - guildID, err := idmap.ReadConfig(channelID, "guild_id") + guildID, err := idmap.ReadConfigv2(channelID, "guild_id") if err != nil { return "", "", fmt.Errorf("error reading guild_id: %v", err) } diff --git a/idmap/service.go b/idmap/service.go index 32709074..554b9a7a 100644 --- a/idmap/service.go +++ b/idmap/service.go @@ -2,11 +2,16 @@ package idmap import ( "encoding/binary" + "encoding/json" "errors" "fmt" + "io" "log" + "net/http" + "net/url" "github.com/boltdb/bolt" + "github.com/hoshinonyaruko/gensokyo/config" ) const ( @@ -38,7 +43,7 @@ func CloseDB() { } // 根据a储存b -func StoreID(id string) (int64, error) { +func StoreIDv2(id string) (int64, error) { var newRow int64 err := db.Update(func(tx *bolt.Tx) error { @@ -74,8 +79,44 @@ func StoreID(id string) (int64, error) { return newRow, err } +// StoreIDv2v2 根据a储存b +func StoreIDv2v2(id string) (int64, error) { + if config.GetLotusValue() { + // 使用网络请求方式 + serverDir := config.GetServer_dir() + portValue := config.GetPortValue() + + // 构建请求URL + url := fmt.Sprintf("http://%s:%s/getid?type=1&id=%s", serverDir, portValue, id) + resp, err := http.Get(url) + if err != nil { + return 0, fmt.Errorf("failed to send request: %v", err) + } + defer resp.Body.Close() + + // 解析响应 + var response map[string]interface{} + if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { + return 0, fmt.Errorf("failed to decode response: %v", err) + } + if resp.StatusCode != http.StatusOK { + return 0, fmt.Errorf("error response from server: %s", response["error"]) + } + + rowValue, ok := response["row"].(float64) + if !ok { + return 0, fmt.Errorf("invalid response format") + } + + return int64(rowValue), nil + } + + // 如果lotus为假,就保持原来的store的方法 + return StoreIDv2(id) +} + // 根据b得到a -func RetrieveRowByID(rowid string) (string, error) { +func RetrieveRowByIDv2(rowid string) (string, error) { var id string err := db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(BucketName)) @@ -93,8 +134,44 @@ func RetrieveRowByID(rowid string) (string, error) { return id, err } +// RetrieveRowByIDv2v2 根据b得到a +func RetrieveRowByIDv2v2(rowid string) (string, error) { + if config.GetLotusValue() { + // 使用网络请求方式 + serverDir := config.GetServer_dir() + portValue := config.GetPortValue() + + // 构建请求URL + url := fmt.Sprintf("http://%s:%s/getid?type=2&id=%s", serverDir, portValue, rowid) + resp, err := http.Get(url) + if err != nil { + return "", fmt.Errorf("failed to send request: %v", err) + } + defer resp.Body.Close() + + // 解析响应 + var response map[string]interface{} + if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { + return "", fmt.Errorf("failed to decode response: %v", err) + } + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("error response from server: %s", response["error"]) + } + + idValue, ok := response["id"].(string) + if !ok { + return "", fmt.Errorf("invalid response format") + } + + return idValue, nil + } + + // 如果lotus为假,就保持原来的RetrieveRowByIDv2的方法 + return RetrieveRowByIDv2(rowid) +} + // 根据a 以b为类别 储存c -func WriteConfig(sectionName, keyName, value string) error { +func WriteConfigv2(sectionName, keyName, value string) error { return db.Update(func(tx *bolt.Tx) error { b, err := tx.CreateBucketIfNotExists([]byte(ConfigBucket)) if err != nil { @@ -106,8 +183,41 @@ func WriteConfig(sectionName, keyName, value string) error { }) } +// WriteConfigv2v2 根据a以b为类别储存c +func WriteConfigv2v2(sectionName, keyName, value string) error { + if config.GetLotusValue() { + // 使用网络请求方式 + serverDir := config.GetServer_dir() + portValue := config.GetPortValue() + + // 构建请求URL和参数 + baseURL := fmt.Sprintf("http://%s:%s/getid", serverDir, portValue) + params := url.Values{} + params.Add("type", "3") + params.Add("id", sectionName) + params.Add("subtype", keyName) + params.Add("value", value) + url := baseURL + "?" + params.Encode() + + resp, err := http.Get(url) + if err != nil { + return fmt.Errorf("failed to send request: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("error response from server: %s", resp.Status) + } + + return nil + } + + // 如果lotus为假,则使用原始方法在本地写入配置 + return WriteConfigv2(sectionName, keyName, value) +} + // 根据a和b取出c -func ReadConfig(sectionName, keyName string) (string, error) { +func ReadConfigv2(sectionName, keyName string) (string, error) { var result string err := db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(ConfigBucket)) @@ -128,6 +238,52 @@ func ReadConfig(sectionName, keyName string) (string, error) { return result, err } +// ReadConfigv2v2 根据a和b取出c +func ReadConfigv2v2(sectionName, keyName string) (string, error) { + if config.GetLotusValue() { + // 使用网络请求方式 + serverDir := config.GetServer_dir() + portValue := config.GetPortValue() + + // 构建请求URL和参数 + baseURL := fmt.Sprintf("http://%s:%s/getid", serverDir, portValue) + params := url.Values{} + params.Add("type", "4") + params.Add("id", sectionName) + params.Add("subtype", keyName) + url := baseURL + "?" + params.Encode() + + resp, err := http.Get(url) + if err != nil { + return "", fmt.Errorf("failed to send request: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("error response from server: %s", resp.Status) + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", fmt.Errorf("failed to read response body: %v", err) + } + + var responseMap map[string]interface{} + if err := json.Unmarshal(body, &responseMap); err != nil { + return "", fmt.Errorf("failed to unmarshal response: %v", err) + } + + if value, ok := responseMap["value"]; ok { + return fmt.Sprintf("%v", value), nil + } + + return "", fmt.Errorf("value not found in response") + } + + // 如果lotus为假,则使用原始方法在本地读取配置 + return ReadConfigv2(sectionName, keyName) +} + // 灵感,ini配置文件 func joinSectionAndKey(sectionName, keyName string) []byte { return []byte(sectionName + ":" + keyName) diff --git a/main.go b/main.go index 55628e9f..6b5938e3 100644 --- a/main.go +++ b/main.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "log" - "net/http" "os" "os/signal" "strconv" @@ -16,9 +15,11 @@ import ( "github.com/hoshinonyaruko/gensokyo/config" "github.com/hoshinonyaruko/gensokyo/handlers" "github.com/hoshinonyaruko/gensokyo/idmap" + "github.com/hoshinonyaruko/gensokyo/server" "github.com/hoshinonyaruko/gensokyo/wsclient" "github.com/gin-gonic/gin" + "github.com/sqweek/dialog" "github.com/tencent-connect/botgo" "github.com/tencent-connect/botgo/dto" "github.com/tencent-connect/botgo/event" @@ -41,6 +42,19 @@ func NewProcessor(api openapi.OpenAPI, apiv2 openapi.OpenAPI, settings *config.S } func main() { + if _, err := os.Stat("config.yml"); os.IsNotExist(err) { + // 如果config.yml不存在 + err = os.WriteFile("config.yml", []byte(configTemplate), 0644) + if err != nil { + fmt.Println("Error writing config.yml:", err) + return + } + + dialog.Message("%s", "请配置config.yml然后再次运行.").Title("配置提示").Info() + os.Exit(0) + } + + // 主逻辑 // 加载配置 conf, err := config.LoadConfig("config.yml") if err != nil { @@ -73,16 +87,16 @@ func main() { apiV2 := botgo.NewOpenAPI(token).WithTimeout(3 * time.Second) // 执行API请求 显示机器人信息 - me, err := api.Me(ctx) // Adjusted to pass only the context - if err != nil { - fmt.Printf("Error fetching bot details: %v\n", err) - return - } - fmt.Printf("Bot details: %+v\n", me) + // me, err := api.Me(ctx) // Adjusted to pass only the context + // if err != nil { + // fmt.Printf("Error fetching bot details: %v\n", err) + // return + // } + // fmt.Printf("Bot details: %+v\n", me) //初始化handlers - handlers.BotID = me.ID - //handlers.BotID = "1234" + //handlers.BotID = me.ID + handlers.BotID = "1234" handlers.AppID = fmt.Sprintf("%d", conf.Settings.AppID) // 获取 websocket 信息 这里用哪一个api获取就是用哪一个api去连接ws @@ -151,9 +165,17 @@ func main() { idmap.InitializeDB() defer idmap.CloseDB() - r := gin.Default() - r.GET("/getid", getIDHandler) - r.Run(":15817") + //图片上传 调用次数限制 + rateLimiter := server.NewRateLimiter() + + //如果连接到其他gensokyo,则不需要启动服务器 + if !conf.Settings.Lotus { + r := gin.Default() + r.GET("/getid", server.GetIDHandler) + r.POST("/uploadpic", server.UploadBase64ImageHandler(rateLimiter)) + r.Static("/channel_temp", "./channel_temp") + r.Run("0.0.0.0:" + conf.Settings.Port) // 注意,这里我更改了端口为你提供的Port,并监听0.0.0.0地址 + } // 使用通道来等待信号 sigCh := make(chan os.Signal, 1) @@ -283,58 +305,3 @@ func getHandlerByName(handlerName string) (interface{}, bool) { return nil, false } } - -func getIDHandler(c *gin.Context) { - idOrRow := c.Query("id") - typeVal, err := strconv.Atoi(c.Query("type")) - - if err != nil || (typeVal != 1 && typeVal != 2) { - c.JSON(http.StatusBadRequest, gin.H{"error": "invalid type"}) - return - } - - switch typeVal { - case 1: - newRow, err := idmap.StoreID(idOrRow) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - c.JSON(http.StatusOK, gin.H{"row": newRow}) - - case 2: - id, err := idmap.RetrieveRowByID(idOrRow) - if err == idmap.ErrKeyNotFound { - c.JSON(http.StatusNotFound, gin.H{"error": "ID not found"}) - return - } else if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - c.JSON(http.StatusOK, gin.H{"id": id}) - - case 3: - // 存储 - section := c.Query("id") - subtype := c.Query("subtype") - value := c.Query("value") - err := idmap.WriteConfig(section, subtype, value) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - c.JSON(http.StatusOK, gin.H{"status": "success"}) - - case 4: - // 获取值 - section := c.Query("id") - subtype := c.Query("subtype") - value, err := idmap.ReadConfig(section, subtype) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - c.JSON(http.StatusOK, gin.H{"value": value}) - } - -} diff --git a/readme.md b/readme.md index 5087bd86..85b391ef 100644 --- a/readme.md +++ b/readme.md @@ -49,7 +49,9 @@ gensokyo兼容 [OneBot-v11](https://github.com/botuniverse/onebot-11) ,并在 实现插件开发和用户开发者无需重新开发,复用过往生态的插件和使用体验. -目前还在开发中..... +目前还处于早期阶段.....交流群:196173384 + +欢迎测试,询问任何有关使用的问题,有问必答,有难必帮~ ### 接口 diff --git a/server/getIDHandler.go b/server/getIDHandler.go new file mode 100644 index 00000000..bfe621c5 --- /dev/null +++ b/server/getIDHandler.go @@ -0,0 +1,64 @@ +package server + +import ( + "net/http" + "strconv" + + "github.com/gin-gonic/gin" + "github.com/hoshinonyaruko/gensokyo/idmap" +) + +func GetIDHandler(c *gin.Context) { + idOrRow := c.Query("id") + typeVal, err := strconv.Atoi(c.Query("type")) + + if err != nil || (typeVal != 1 && typeVal != 2) { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid type"}) + return + } + + switch typeVal { + case 1: + newRow, err := idmap.StoreIDv2(idOrRow) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"row": newRow}) + + case 2: + id, err := idmap.RetrieveRowByIDv2(idOrRow) + if err == idmap.ErrKeyNotFound { + c.JSON(http.StatusNotFound, gin.H{"error": "ID not found"}) + return + } else if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"id": id}) + + case 3: + // 存储 + section := c.Query("id") + subtype := c.Query("subtype") + value := c.Query("value") + err := idmap.WriteConfigv2(section, subtype, value) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"status": "success"}) + + case 4: + // 获取值 + section := c.Query("id") + subtype := c.Query("subtype") + value, err := idmap.ReadConfigv2(section, subtype) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"value": value}) + } + +} diff --git a/server/uploadpic.go b/server/uploadpic.go new file mode 100644 index 00000000..78c419d0 --- /dev/null +++ b/server/uploadpic.go @@ -0,0 +1,238 @@ +package server + +import ( + "bytes" + "crypto/md5" + "crypto/rand" + "encoding/base64" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "image" + _ "image/gif" + _ "image/jpeg" + _ "image/png" + "io/ioutil" + "net" + "net/http" + "net/url" + "os" + "strings" + "time" + + "github.com/gin-gonic/gin" + "github.com/hoshinonyaruko/gensokyo/config" +) + +const ( + MaximumImageSize = 10 * 1024 * 1024 + AllowedUploadsPerMinute = 100 + MaxRequests = 30 + RequestInterval = time.Minute +) + +type RateLimiter struct { + Counts map[string][]time.Time +} + +// 频率限制器 +func NewRateLimiter() *RateLimiter { + return &RateLimiter{ + Counts: make(map[string][]time.Time), + } +} + +// 网页后端,图床逻辑,基于gin和www静态文件的简易图床 +func UploadBase64ImageHandler(rateLimiter *RateLimiter) gin.HandlerFunc { + return func(c *gin.Context) { + ipAddress := c.ClientIP() + if !rateLimiter.CheckAndUpdateRateLimit(ipAddress) { + c.JSON(http.StatusTooManyRequests, gin.H{"error": "rate limit exceeded"}) + return + } + + base64Image := c.PostForm("base64Image") + // Print the length of the received base64 data + fmt.Println("Received base64 data length:", len(base64Image), "characters") + + imageBytes, err := base64.StdEncoding.DecodeString(base64Image) + if err != nil { + fmt.Println("Error while decoding base64:", err) // Print error while decoding + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid base64 data"}) + return + } + + imageFormat, err := getImageFormat(imageBytes) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "undefined picture format1"}) + return + } + + fileExt := getFileExtensionFromImageFormat(imageFormat) + if fileExt == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "unsupported image format2"}) + return + } + + fileName := generateRandomMd5() + "." + fileExt + directoryPath := "./channel_temp/" + savePath := directoryPath + fileName + + // Create the directory if it doesn't exist + err = os.MkdirAll(directoryPath, 0755) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "error creating directory"}) + return + } + + err = os.WriteFile(savePath, imageBytes, 0644) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "error saving file"}) + return + } + + serverAddress := config.GetServer_dir() + serverPort := config.GetPortValue() + if serverAddress == "" { + // Handle the case where the server address is not configured + c.JSON(http.StatusInternalServerError, gin.H{"error": "server address is not configured"}) + return + } + + imageURL := fmt.Sprintf("http://%s:%s/channel_temp/%s", serverAddress, serverPort, fileName) + c.JSON(http.StatusOK, gin.H{"url": imageURL}) + } +} + +// 检查是否超过调用频率限制 +// 默认1分钟30次 todo 允许用户自行在config编辑限制次数 +func (rl *RateLimiter) CheckAndUpdateRateLimit(ipAddress string) bool { + now := time.Now() + rl.Counts[ipAddress] = append(rl.Counts[ipAddress], now) + + // Remove expired entries + for len(rl.Counts[ipAddress]) > 0 && now.Sub(rl.Counts[ipAddress][0]) > RequestInterval { + rl.Counts[ipAddress] = rl.Counts[ipAddress][1:] + } + + return len(rl.Counts[ipAddress]) <= MaxRequests +} + +// 获取图片类型 +func getImageFormat(data []byte) (format string, err error) { + // Print the size of the data to check if it's being read correctly + fmt.Println("Received data size:", len(data), "bytes") + + _, format, err = image.DecodeConfig(bytes.NewReader(data)) + if err != nil { + // Print additional error information + fmt.Println("Error while trying to decode image config:", err) + return "", fmt.Errorf("error decoding image config: %w", err) + } + + // Print the detected format + fmt.Println("Detected image format:", format) + + if format == "" { + return "", errors.New("undefined picture format") + } + return format, nil +} + +// 判断并返回图片类型 +func getFileExtensionFromImageFormat(format string) string { + switch format { + case "jpeg": + return "jpg" + case "gif": + return "gif" + case "png": + return "png" + default: + return "" + } +} + +// 生成随机md5图片名,防止碰撞 +func generateRandomMd5() string { + randomBytes := make([]byte, 16) + _, err := rand.Read(randomBytes) + if err != nil { + return "" + } + + md5Hash := md5.Sum(randomBytes) + return hex.EncodeToString(md5Hash[:]) +} + +// 将base64图片通过lotus转换成url +func UploadBase64ImageToServer(base64Image string) (string, error) { + if config.GetLotusValue() { + serverDir := config.GetServer_dir() + serverPort := config.GetPortValue() + url := fmt.Sprintf("http://%s:%s/uploadpic", serverDir, serverPort) + + resp, err := postImageToServer(base64Image, url) + if err != nil { + return "", err + } + return resp, nil + } + + serverDir := config.GetServer_dir() + if isPublicAddress(serverDir) { + url := fmt.Sprintf("http://127.0.0.1:%s/uploadpic", config.GetPortValue()) + + resp, err := postImageToServer(base64Image, url) + if err != nil { + return "", err + } + return resp, nil + } + return "", errors.New("local server uses a private address; image upload failed") +} + +// 请求图床api(图床就是lolus为false的gensokyo) +func postImageToServer(base64Image, targetURL string) (string, error) { + data := url.Values{} + data.Set("base64Image", base64Image) // 修改字段名以与服务器匹配 + + resp, err := http.PostForm(targetURL, data) + if err != nil { + return "", fmt.Errorf("failed to send request: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("error response from server: %s", resp.Status) + } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "", fmt.Errorf("failed to read response body: %v", err) + } + + var responseMap map[string]interface{} + if err := json.Unmarshal(body, &responseMap); err != nil { + return "", fmt.Errorf("failed to unmarshal response: %v", err) + } + + if value, ok := responseMap["url"]; ok { + return fmt.Sprintf("%v", value), nil + } + + return "", fmt.Errorf("URL not found in response") +} + +// 判断是否公网ip 填写域名也会被认为是公网,但需要用户自己确保域名正确解析到gensokyo所在的ip地址 +func isPublicAddress(addr string) bool { + if strings.Contains(addr, "localhost") || strings.HasPrefix(addr, "127.") || strings.HasPrefix(addr, "192.168.") { + return false + } + if net.ParseIP(addr) != nil { + return true + } + // If it's not a recognized IP address format, consider it a domain name (public). + return true +} diff --git a/wsclient/ws.go b/wsclient/ws.go index 8e8207dd..6c01fd48 100644 --- a/wsclient/ws.go +++ b/wsclient/ws.go @@ -64,7 +64,7 @@ func (c *WebSocketClient) recvMessage(msg []byte) { return } - fmt.Println("Received from onebotv11:", truncateMessage(message, 200)) + fmt.Println("Received from onebotv11:", truncateMessage(message, 500)) // 调用callapi callapi.CallAPIFromDict(c, c.api, c.apiv2, message) } @@ -122,9 +122,10 @@ func NewWebSocketClient(urlStr string, botID string, api openapi.OpenAPI, apiv2 // Retry mechanism for { + fmt.Println("Dialing URL:", urlStr) conn, _, err = dialer.Dial(urlStr, headers) if err != nil { - fmt.Printf("Failed to connect to WebSocket: %v, retrying in 5 seconds...\n", err) + fmt.Printf("Failed to connect to WebSocket[%v]: %v, retrying in 5 seconds...\n", urlStr, err) time.Sleep(5 * time.Second) // sleep for 5 seconds before retrying } else { fmt.Printf("成功连接到 %s.\n", urlStr) // 输出连接成功提示 From 86698c6ca667d2f13a44efd7013e6b97a05abc67 Mon Sep 17 00:00:00 2001 From: cosmo Date: Thu, 26 Oct 2023 12:36:37 +0800 Subject: [PATCH 07/27] trss support --- Processor/ProcessC2CMessage.go | 157 +++++++++++++++++++++++ Processor/ProcessChannelDirectMessage.go | 24 +++- Processor/ProcessGroupMessage.go | 13 +- Processor/ProcessGuildATMessage.go | 28 ++-- Processor/ProcessGuildNormalMessage.go | 28 ++-- Processor/Processor.go | 47 +++---- botgo/token/authtoken.go | 98 +++++++------- botgo/token/token.go | 14 +- callapi/callapi.go | 24 +++- config/config.go | 22 ++++ config_template.go | 2 +- config_template.yml | 2 +- handlers/get_friend_list.go | 63 +++++++++ handlers/get_group_info.go | 2 +- handlers/get_group_list.go | 154 ++++++++++++++++++++++ handlers/get_group_member_list.go | 96 ++++++++++++++ handlers/get_guild_channel_list.go | 41 ++++++ handlers/get_guild_list.go | 58 +++++++++ handlers/get_guild_service_profile.go | 51 ++++++++ handlers/get_login_info.go | 59 +++++++++ handlers/get_online_clients.go | 51 ++++++++ handlers/get_version_info.go | 73 +++++++++++ handlers/message_parser.go | 76 +++++++++++ handlers/send_group_msg.go | 22 +--- handlers/send_guild_channel_msg.go | 4 +- handlers/send_msg.go | 65 ++++++++-- handlers/send_private_msg.go | 38 +++--- handlers/set_group_ban.go | 65 ++++++++++ handlers/set_group_whole_ban.go | 60 +++++++++ idmap/service.go | 32 ++--- main.go | 23 ++-- wsclient/ws.go | 15 ++- 32 files changed, 1302 insertions(+), 205 deletions(-) create mode 100644 Processor/ProcessC2CMessage.go create mode 100644 handlers/get_friend_list.go create mode 100644 handlers/get_group_list.go create mode 100644 handlers/get_group_member_list.go create mode 100644 handlers/get_guild_channel_list.go create mode 100644 handlers/get_guild_list.go create mode 100644 handlers/get_guild_service_profile.go create mode 100644 handlers/get_login_info.go create mode 100644 handlers/get_online_clients.go create mode 100644 handlers/get_version_info.go create mode 100644 handlers/set_group_ban.go create mode 100644 handlers/set_group_whole_ban.go diff --git a/Processor/ProcessC2CMessage.go b/Processor/ProcessC2CMessage.go new file mode 100644 index 00000000..72eeda07 --- /dev/null +++ b/Processor/ProcessC2CMessage.go @@ -0,0 +1,157 @@ +// 处理收到的信息事件 +package Processor + +import ( + "fmt" + "log" + "strconv" + "time" + + "github.com/hoshinonyaruko/gensokyo/config" + "github.com/hoshinonyaruko/gensokyo/echo" + "github.com/hoshinonyaruko/gensokyo/handlers" + "github.com/hoshinonyaruko/gensokyo/idmap" + "github.com/tencent-connect/botgo/dto" + "github.com/tencent-connect/botgo/websocket/client" +) + +// ProcessC2CMessage 处理C2C消息 群私聊 +func (p *Processor) ProcessC2CMessage(data *dto.WSC2CMessageData) error { + // 打印data结构体 + PrintStructWithFieldNames(data) + + // 从私信中提取必要的信息 这是测试回复需要用到 + //recipientID := data.Author.ID + //ChannelID := data.ChannelID + //sourece是源头频道 + //GuildID := data.GuildID + + //获取当前的s值 当前ws连接所收到的信息条数 + s := client.GetGlobalS() + if !p.Settings.GlobalPrivateToChannel { + // 直接转换成ob11私信 + + //转换appidstring + AppIDString := strconv.FormatUint(p.Settings.AppID, 10) + echostr := AppIDString + "_" + strconv.FormatInt(s, 10) + + //将真实id转为int userid64 + userid64, err := idmap.StoreIDv2(data.Author.ID) + if err != nil { + log.Fatalf("Error storing ID: %v", err) + } + + //收到私聊信息调用的具体还原步骤 + //1,idmap还原真实userid, + //发信息使用的是userid + + messageID64, err := idmap.StoreIDv2(data.ID) + if err != nil { + log.Fatalf("Error storing ID: %v", err) + } + messageID := int(messageID64) + messageText := data.Content + // 如果在Array模式下, 则处理Message为Segment格式 + var segmentedMessages interface{} = messageText + if config.GetArrayValue() { + segmentedMessages = handlers.ConvertToSegmentedMessage(data) + } + privateMsg := OnebotPrivateMessage{ + RawMessage: messageText, + Message: segmentedMessages, + MessageID: messageID, + MessageType: "private", + PostType: "message", + SelfID: int64(p.Settings.AppID), + UserID: userid64, + Sender: PrivateSender{ + Nickname: "", //这个不支持,但加机器人好友,会收到一个事件,可以对应储存获取,用idmaps可以做到. + UserID: userid64, + }, + SubType: "friend", + Time: time.Now().Unix(), + Avatar: "", //todo 同上 + Echo: echostr, + } + + // 将当前s和appid和message进行映射 + echo.AddMsgID(AppIDString, s, data.ID) + echo.AddMsgType(AppIDString, s, "group_private") + //其实不需要用AppIDString,因为gensokyo是单机器人框架 + //可以试着开发一个,会很棒的 + echo.AddMsgID(AppIDString, userid64, data.ID) + echo.AddMsgType(AppIDString, userid64, "group_private") + //储存当前群或频道号的类型 私信不需要 + //idmap.WriteConfigv2(data.ChannelID, "type", "group_private") + + // 调试 + PrintStructWithFieldNames(privateMsg) + + // Convert OnebotGroupMessage to map and send + privateMsgMap := structToMap(privateMsg) + err = p.Wsclient.SendMessage(privateMsgMap) + if err != nil { + return fmt.Errorf("error sending group message via wsclient: %v", err) + } + } else { + //将私聊信息转化为群信息(特殊需求情况下) + + //转换at + messageText := handlers.RevertTransformedText(data.Content) + //转换appid + AppIDString := strconv.FormatUint(p.Settings.AppID, 10) + //构造echo + echostr := AppIDString + "_" + strconv.FormatInt(s, 10) + //把userid作为群号 + //映射str的userid到int + userid64, err := idmap.StoreIDv2(data.Author.ID) + if err != nil { + log.Printf("Error storing ID: %v", err) + return nil + } + //映射str的messageID到int + messageID64, err := idmap.StoreIDv2(data.ID) + if err != nil { + log.Printf("Error storing ID: %v", err) + return nil + } + messageID := int(messageID64) + //todo 判断array模式 然后对Message处理成array格式 + groupMsg := OnebotGroupMessage{ + RawMessage: messageText, + Message: messageText, + MessageID: messageID, + GroupID: userid64, + MessageType: "group", + PostType: "message", + SelfID: int64(p.Settings.AppID), + UserID: userid64, + Sender: Sender{ + Nickname: "", + UserID: userid64, + }, + SubType: "normal", + Time: time.Now().Unix(), + Avatar: "", + Echo: echostr, + } + //将当前s和appid和message进行映射 + echo.AddMsgID(AppIDString, s, data.ID) + echo.AddMsgType(AppIDString, s, "group_private") + //为不支持双向echo的ob服务端映射 + echo.AddMsgID(AppIDString, userid64, data.ID) + echo.AddMsgType(AppIDString, userid64, "group_private") + + //调试 + PrintStructWithFieldNames(groupMsg) + + // Convert OnebotGroupMessage to map and send + groupMsgMap := structToMap(groupMsg) + err = p.Wsclient.SendMessage(groupMsgMap) + if err != nil { + return fmt.Errorf("error sending group message via wsclient: %v", err) + } + } + + return nil +} diff --git a/Processor/ProcessChannelDirectMessage.go b/Processor/ProcessChannelDirectMessage.go index e2418474..51181ffd 100644 --- a/Processor/ProcessChannelDirectMessage.go +++ b/Processor/ProcessChannelDirectMessage.go @@ -7,6 +7,7 @@ import ( "strconv" "time" + "github.com/hoshinonyaruko/gensokyo/config" "github.com/hoshinonyaruko/gensokyo/echo" "github.com/hoshinonyaruko/gensokyo/handlers" "github.com/hoshinonyaruko/gensokyo/idmap" @@ -56,10 +57,15 @@ func (p *Processor) ProcessChannelDirectMessage(data *dto.WSDirectMessageData) e log.Fatalf("Error storing ID: %v", err) } messageID := int(messageID64) - + messageText := data.Content + // 如果在Array模式下, 则处理Message为Segment格式 + var segmentedMessages interface{} = messageText + if config.GetArrayValue() { + segmentedMessages = handlers.ConvertToSegmentedMessage(data) + } privateMsg := OnebotPrivateMessage{ - RawMessage: data.Content, - Message: data.Content, + RawMessage: messageText, + Message: segmentedMessages, MessageID: messageID, MessageType: "private", PostType: "message", @@ -144,6 +150,8 @@ func (p *Processor) ProcessChannelDirectMessage(data *dto.WSDirectMessageData) e //为不支持双向echo的ob服务端映射 echo.AddMsgID(AppIDString, userid64, data.ID) echo.AddMsgType(AppIDString, userid64, "guild_private") + //储存当前群或频道号的类型 + idmap.WriteConfigv2(data.ChannelID, "type", "guild_private") //调试 PrintStructWithFieldNames(onebotMsg) @@ -186,10 +194,14 @@ func (p *Processor) ProcessChannelDirectMessage(data *dto.WSDirectMessageData) e return nil } messageID := int(messageID64) - //todo 判断array模式 然后对Message处理成array格式 + // 如果在Array模式下, 则处理Message为Segment格式 + var segmentedMessages interface{} = messageText + if config.GetArrayValue() { + segmentedMessages = handlers.ConvertToSegmentedMessage(data) + } groupMsg := OnebotGroupMessage{ RawMessage: messageText, - Message: messageText, + Message: segmentedMessages, MessageID: messageID, GroupID: int64(channelIDInt), MessageType: "group", @@ -211,6 +223,8 @@ func (p *Processor) ProcessChannelDirectMessage(data *dto.WSDirectMessageData) e //为不支持双向echo的ob服务端映射 echo.AddMsgID(AppIDString, userid64, data.ID) echo.AddMsgType(AppIDString, userid64, "guild_private") + //储存当前群或频道号的类型 + idmap.WriteConfigv2(data.ChannelID, "type", "guild_private") //调试 PrintStructWithFieldNames(groupMsg) diff --git a/Processor/ProcessGroupMessage.go b/Processor/ProcessGroupMessage.go index 0c823e80..5610b493 100644 --- a/Processor/ProcessGroupMessage.go +++ b/Processor/ProcessGroupMessage.go @@ -7,6 +7,7 @@ import ( "strconv" "time" + "github.com/hoshinonyaruko/gensokyo/config" "github.com/hoshinonyaruko/gensokyo/echo" "github.com/hoshinonyaruko/gensokyo/handlers" "github.com/hoshinonyaruko/gensokyo/idmap" @@ -43,7 +44,7 @@ func (p *Processor) ProcessGroupMessage(data *dto.WSGroupATMessageData) error { log.Printf("Error storing ID: %v", err) return nil } - //userid := int(userid64) + //映射str的messageID到int messageID64, err := idmap.StoreIDv2(data.ID) if err != nil { @@ -51,10 +52,14 @@ func (p *Processor) ProcessGroupMessage(data *dto.WSGroupATMessageData) error { return nil } messageID := int(messageID64) - // todo 判断array模式 然后对Message处理成array格式 + // 如果在Array模式下, 则处理Message为Segment格式 + var segmentedMessages interface{} = messageText + if config.GetArrayValue() { + segmentedMessages = handlers.ConvertToSegmentedMessage(data) + } groupMsg := OnebotGroupMessage{ RawMessage: messageText, - Message: messageText, + Message: segmentedMessages, MessageID: messageID, GroupID: GroupID64, MessageType: "group", @@ -77,6 +82,8 @@ func (p *Processor) ProcessGroupMessage(data *dto.WSGroupATMessageData) error { //为不支持双向echo的ob服务端映射 echo.AddMsgID(AppIDString, GroupID64, data.ID) echo.AddMsgType(AppIDString, GroupID64, "group") + //储存当前群或频道号的类型 + idmap.WriteConfigv2(data.GroupID, "type", "group") // 调试 PrintStructWithFieldNames(groupMsg) diff --git a/Processor/ProcessGuildATMessage.go b/Processor/ProcessGuildATMessage.go index 68a8ed27..c3de90c5 100644 --- a/Processor/ProcessGuildATMessage.go +++ b/Processor/ProcessGuildATMessage.go @@ -7,6 +7,7 @@ import ( "strconv" "time" + "github.com/hoshinonyaruko/gensokyo/config" "github.com/hoshinonyaruko/gensokyo/echo" "github.com/hoshinonyaruko/gensokyo/handlers" "github.com/hoshinonyaruko/gensokyo/idmap" @@ -37,19 +38,16 @@ func (p *Processor) ProcessGuildATMessage(data *dto.WSATMessageData) error { log.Printf("Error storing ID: %v", err) return nil } - //映射str的messageID到int - //可以是string - // messageID64, err := idmap.StoreIDv2(data.ID) - // if err != nil { - // log.Printf("Error storing ID: %v", err) - // return nil - // } - // messageID := int(messageID64) + // 如果在Array模式下, 则处理Message为Segment格式 + var segmentedMessages interface{} = messageText + if config.GetArrayValue() { + segmentedMessages = handlers.ConvertToSegmentedMessage(data) + } // 处理onebot_channel_message逻辑 onebotMsg := OnebotChannelMessage{ ChannelID: data.ChannelID, GuildID: data.GuildID, - Message: messageText, + Message: segmentedMessages, RawMessage: messageText, MessageID: data.ID, MessageType: "guild", @@ -74,6 +72,8 @@ func (p *Processor) ProcessGuildATMessage(data *dto.WSATMessageData) error { //为不支持双向echo的ob11服务端映射 echo.AddMsgID(AppIDString, userid64, data.ID) echo.AddMsgType(AppIDString, userid64, "guild") + //储存当前群或频道号的类型 + idmap.WriteConfigv2(data.ChannelID, "type", "guild") //调试 PrintStructWithFieldNames(onebotMsg) @@ -120,10 +120,14 @@ func (p *Processor) ProcessGuildATMessage(data *dto.WSATMessageData) error { return nil } messageID := int(messageID64) - //todo 判断array模式 然后对Message处理成array格式 + // 如果在Array模式下, 则处理Message为Segment格式 + var segmentedMessages interface{} = messageText + if config.GetArrayValue() { + segmentedMessages = handlers.ConvertToSegmentedMessage(data) + } groupMsg := OnebotGroupMessage{ RawMessage: messageText, - Message: messageText, + Message: segmentedMessages, MessageID: messageID, GroupID: int64(channelIDInt), MessageType: "group", @@ -145,6 +149,8 @@ func (p *Processor) ProcessGuildATMessage(data *dto.WSATMessageData) error { //为不支持双向echo的ob服务端映射 echo.AddMsgID(AppIDString, int64(channelIDInt), data.ID) echo.AddMsgType(AppIDString, int64(channelIDInt), "guild") + //储存当前群或频道号的类型 + idmap.WriteConfigv2(data.ChannelID, "type", "guild") //调试 PrintStructWithFieldNames(groupMsg) diff --git a/Processor/ProcessGuildNormalMessage.go b/Processor/ProcessGuildNormalMessage.go index cba53ac2..931b02ac 100644 --- a/Processor/ProcessGuildNormalMessage.go +++ b/Processor/ProcessGuildNormalMessage.go @@ -7,6 +7,7 @@ import ( "strconv" "time" + "github.com/hoshinonyaruko/gensokyo/config" "github.com/hoshinonyaruko/gensokyo/echo" "github.com/hoshinonyaruko/gensokyo/handlers" "github.com/hoshinonyaruko/gensokyo/idmap" @@ -36,19 +37,16 @@ func (p *Processor) ProcessGuildNormalMessage(data *dto.WSMessageData) error { log.Printf("Error storing ID: %v", err) return nil } - //映射str的messageID到int - //可以是string - // messageID64, err := idmap.StoreIDv2(data.ID) - // if err != nil { - // log.Printf("Error storing ID: %v", err) - // return nil - // } - // messageID := int(messageID64) + // 如果在Array模式下, 则处理Message为Segment格式 + var segmentedMessages interface{} = messageText + if config.GetArrayValue() { + segmentedMessages = handlers.ConvertToSegmentedMessage(data) + } // 处理onebot_channel_message逻辑 onebotMsg := OnebotChannelMessage{ ChannelID: data.ChannelID, GuildID: data.GuildID, - Message: messageText, + Message: segmentedMessages, RawMessage: messageText, MessageID: data.ID, MessageType: "guild", @@ -73,6 +71,8 @@ func (p *Processor) ProcessGuildNormalMessage(data *dto.WSMessageData) error { //为不支持双向echo的ob11服务端映射 echo.AddMsgID(AppIDString, userid64, data.ID) echo.AddMsgType(AppIDString, userid64, "guild") + //储存当前群或频道号的类型 + idmap.WriteConfigv2(data.ChannelID, "type", "guild") //调试 PrintStructWithFieldNames(onebotMsg) @@ -119,10 +119,14 @@ func (p *Processor) ProcessGuildNormalMessage(data *dto.WSMessageData) error { return nil } messageID := int(messageID64) - //todo 判断array模式 然后对Message处理成array格式 + // 如果在Array模式下, 则处理Message为Segment格式 + var segmentedMessages interface{} = messageText + if config.GetArrayValue() { + segmentedMessages = handlers.ConvertToSegmentedMessage(data) + } groupMsg := OnebotGroupMessage{ RawMessage: messageText, - Message: messageText, + Message: segmentedMessages, MessageID: messageID, GroupID: int64(channelIDInt), MessageType: "group", @@ -144,6 +148,8 @@ func (p *Processor) ProcessGuildNormalMessage(data *dto.WSMessageData) error { //为不支持双向echo的ob服务端映射 echo.AddMsgID(AppIDString, int64(channelIDInt), data.ID) echo.AddMsgType(AppIDString, int64(channelIDInt), "guild") + //储存当前群或频道号的类型 + idmap.WriteConfigv2(data.ChannelID, "type", "guild") //调试 PrintStructWithFieldNames(groupMsg) diff --git a/Processor/Processor.go b/Processor/Processor.go index 2a197cfa..5e2064cc 100644 --- a/Processor/Processor.go +++ b/Processor/Processor.go @@ -30,21 +30,21 @@ type Sender struct { // 频道信息事件 type OnebotChannelMessage struct { - ChannelID string `json:"channel_id"` - GuildID string `json:"guild_id"` - Message string `json:"message"` - MessageID string `json:"message_id"` - MessageType string `json:"message_type"` - PostType string `json:"post_type"` - SelfID int64 `json:"self_id"` - SelfTinyID string `json:"self_tiny_id"` - Sender Sender `json:"sender"` - SubType string `json:"sub_type"` - Time int64 `json:"time"` - Avatar string `json:"avatar"` - UserID int64 `json:"user_id"` - RawMessage string `json:"raw_message"` - Echo string `json:"echo"` + ChannelID string `json:"channel_id"` + GuildID string `json:"guild_id"` + Message interface{} `json:"message"` + MessageID string `json:"message_id"` + MessageType string `json:"message_type"` + PostType string `json:"post_type"` + SelfID int64 `json:"self_id"` + SelfTinyID string `json:"self_tiny_id"` + Sender Sender `json:"sender"` + SubType string `json:"sub_type"` + Time int64 `json:"time"` + Avatar string `json:"avatar"` + UserID int64 `json:"user_id"` + RawMessage string `json:"raw_message"` + Echo string `json:"echo"` } // 群信息事件 @@ -140,23 +140,6 @@ func (p *Processor) ProcessInlineSearch(data *dto.WSInteractionData) error { // return nil // } -// ProcessC2CMessage 处理C2C消息 群私聊 -func (p *Processor) ProcessC2CMessage(rawMessage string, data *dto.WSC2CMessageData) error { - // ctx := context.Background() // 或从更高级别传递一个上下文 - - // // 在这里处理C2C消息 - // // ... - - // // 示例:直接回复收到的消息 - // response := fmt.Sprintf("Received your message: %s", rawMessage) // 创建响应消息 - // err := p.api.PostC2CMessage(ctx, response) // 替换为您的OpenAPI方法 - // if err != nil { - // return err - // } - - return nil -} - // 打印结构体的函数 func PrintStructWithFieldNames(v interface{}) { val := reflect.ValueOf(v) diff --git a/botgo/token/authtoken.go b/botgo/token/authtoken.go index 303b302d..068efe6a 100644 --- a/botgo/token/authtoken.go +++ b/botgo/token/authtoken.go @@ -54,59 +54,59 @@ func (atoken *AuthTokenInfo) ForceUpToken(ctx context.Context, reason string) er // StartRefreshAccessToken 启动获取AccessToken的后台刷新 // 该函数首先会立即查询一次AccessToken,并保存。 // 然后它将在后台启动一个goroutine,定期(根据token的有效期)刷新AccessToken。 -// func (atoken *AuthTokenInfo) StartRefreshAccessToken(ctx context.Context, tokenURL, appID, clientSecrent string) (err error) { -// // 首先,立即获取一次AccessToken -// tokenInfo, err := queryAccessToken(ctx, tokenURL, appID, clientSecrent) -// if err != nil { -// return err -// } -// atoken.setAuthToken(tokenInfo) -// fmt.Printf("获取到的token是: %s\n", tokenInfo.Token) // 输出获取到的token - -// // 获取token的有效期(通常以秒为单位) -// tokenTTL := tokenInfo.ExpiresIn -// // 使用sync.Once保证仅启动一个goroutine进行定时刷新 -// atoken.once.Do(func() { -// go func() { // 启动一个新的goroutine -// for { -// // 如果tokenTTL为0或负数,将其设置为1 -// if tokenTTL <= 0 { -// tokenTTL = 1 -// } -// select { -// case <-time.NewTimer(time.Duration(tokenTTL) * time.Second).C: // 当token过期时 -// case upToken := <-atoken.forceUpToken: // 接收强制更新token的信号 -// log.Warnf("recv uptoken info:%v", upToken) -// case <-ctx.Done(): // 当上下文结束时,退出goroutine -// log.Warnf("recv ctx:%v exit refreshAccessToken", ctx.Err()) -// return -// } -// // 查询并获取新的AccessToken -// tokenInfo, err := queryAccessToken(ctx, tokenURL, appID, clientSecrent) -// if err == nil { -// atoken.setAuthToken(tokenInfo) -// fmt.Printf("获取到的token是: %s\n", tokenInfo.Token) // 输出获取到的token -// tokenTTL = tokenInfo.ExpiresIn -// } else { -// log.Errorf("queryAccessToken err:%v", err) -// } -// } -// }() -// }) -// return -// } - -// 测试用 func (atoken *AuthTokenInfo) StartRefreshAccessToken(ctx context.Context, tokenURL, appID, clientSecrent string) (err error) { - // 创建一个固定的token信息 - fixedTokenInfo := AccessTokenInfo{ - Token: "PpAPgoel0-gTeaxy-ydak0kUKxJrCSlbLcwtuPt99jCPVrahkqh3WSiIy9s63tCZnTEp4asw035u", - ExpiresIn: 3600, // 这里假设token的有效时间是3600秒,你可以根据需要调整 + // 首先,立即获取一次AccessToken + tokenInfo, err := queryAccessToken(ctx, tokenURL, appID, clientSecrent) + if err != nil { + return err } - atoken.setAuthToken(fixedTokenInfo) - return nil + atoken.setAuthToken(tokenInfo) + fmt.Printf("获取到的token是: %s\n", tokenInfo.Token) // 输出获取到的token + + // 获取token的有效期(通常以秒为单位) + tokenTTL := tokenInfo.ExpiresIn + // 使用sync.Once保证仅启动一个goroutine进行定时刷新 + atoken.once.Do(func() { + go func() { // 启动一个新的goroutine + for { + // 如果tokenTTL为0或负数,将其设置为1 + if tokenTTL <= 0 { + tokenTTL = 1 + } + select { + case <-time.NewTimer(time.Duration(tokenTTL) * time.Second).C: // 当token过期时 + case upToken := <-atoken.forceUpToken: // 接收强制更新token的信号 + log.Warnf("recv uptoken info:%v", upToken) + case <-ctx.Done(): // 当上下文结束时,退出goroutine + log.Warnf("recv ctx:%v exit refreshAccessToken", ctx.Err()) + return + } + // 查询并获取新的AccessToken + tokenInfo, err := queryAccessToken(ctx, tokenURL, appID, clientSecrent) + if err == nil { + atoken.setAuthToken(tokenInfo) + fmt.Printf("获取到的token是: %s\n", tokenInfo.Token) // 输出获取到的token + tokenTTL = tokenInfo.ExpiresIn + } else { + log.Errorf("queryAccessToken err:%v", err) + } + } + }() + }) + return } +// 测试用 +// func (atoken *AuthTokenInfo) StartRefreshAccessToken(ctx context.Context, tokenURL, appID, clientSecrent string) (err error) { +// // 创建一个固定的token信息 +// fixedTokenInfo := AccessTokenInfo{ +// Token: "FK2RrubmpJ29LZ1v2LVuE6YrRB6zsjnP4wF3EaHy0g48H-SPYIGn4irXvHNvYMgV6hLP6hEimUgXuw", +// ExpiresIn: 3600, // 这里假设token的有效时间是3600秒,你可以根据需要调整 +// } +// atoken.setAuthToken(fixedTokenInfo) +// return nil +// } + func (atoken *AuthTokenInfo) getAuthToken() AccessTokenInfo { atoken.lock.RLock() defer atoken.lock.RUnlock() diff --git a/botgo/token/token.go b/botgo/token/token.go index b669b243..f594fa4b 100644 --- a/botgo/token/token.go +++ b/botgo/token/token.go @@ -91,16 +91,16 @@ func (t *Token) GetString_old() string { } // GetAccessToken 取得鉴权Token -// func (t *Token) GetAccessToken() string { -// return t.authToken.getAuthToken().Token -// } - -// GetAccessToken 取得测试鉴权Token func (t *Token) GetAccessToken() string { - // 固定的token值 - return "PpAPgoel0-gTeaxy-ydak0kUKxJrCSlbLcwtuPt99jCPVrahkqh3WSiIy9s63tCZnTEp4asw035u" + return t.authToken.getAuthToken().Token } +// GetAccessToken 取得测试鉴权Token +// func (t *Token) GetAccessToken() string { +// // 固定的token值 +// return "FK2RrubmpJ29LZ1v2LVuE6YrRB6zsjnP4wF3EaHy0g48H-SPYIGn4irXvHNvYMgV6hLP6hEimUgXuw" +// } + // UpAccessToken 更新accessToken func (t *Token) UpAccessToken(ctx context.Context, reason interface{}) error { return t.authToken.ForceUpToken(ctx, fmt.Sprint(reason)) diff --git a/callapi/callapi.go b/callapi/callapi.go index 445ea523..51116fd4 100644 --- a/callapi/callapi.go +++ b/callapi/callapi.go @@ -58,9 +58,11 @@ type ParamsContent struct { BotQQ string `json:"botqq"` ChannelID string `json:"channel_id"` GuildID string `json:"guild_id"` - GroupID interface{} `json:"group_id"` // 每一种onebotv11实现的字段类型都可能不同 - Message interface{} `json:"message"` // 这里使用interface{}因为它可能是多种类型 - UserID interface{} `json:"user_id"` // 这里使用interface{}因为它可能是多种类型 + GroupID interface{} `json:"group_id"` // 每一种onebotv11实现的字段类型都可能不同 + Message interface{} `json:"message"` // 这里使用interface{}因为它可能是多种类型 + UserID interface{} `json:"user_id"` // 这里使用interface{}因为它可能是多种类型 + Duration int `json:"duration,omitempty"` // 可选的整数 + Enable bool `json:"enable,omitempty"` // 可选的布尔值 } // 自定义一个ParamsContent的UnmarshalJSON 让GroupID同时兼容str和int @@ -68,6 +70,7 @@ func (p *ParamsContent) UnmarshalJSON(data []byte) error { type Alias ParamsContent aux := &struct { GroupID interface{} `json:"group_id"` + UserID interface{} `json:"user_id"` *Alias }{ Alias: (*Alias)(p), @@ -86,6 +89,18 @@ func (p *ParamsContent) UnmarshalJSON(data []byte) error { default: return fmt.Errorf("GroupID has unsupported type") } + + switch v := aux.UserID.(type) { + case nil: // 当UserID不存在时 + p.UserID = "" + case float64: // JSON的数字默认被解码为float64 + p.UserID = fmt.Sprintf("%.0f", v) // 将其转换为字符串,忽略小数点后的部分 + case string: + p.UserID = v + default: + return fmt.Errorf("UserID has unsupported type") + } + return nil } @@ -99,7 +114,8 @@ type Message struct { // 这是一个接口,在wsclient传入client但不需要引用wsclient包,避免循环引用 type Client interface { SendMessage(message map[string]interface{}) error - GetAppID() string + GetAppID() uint64 + GetAppIDStr() string } // 根据action订阅handler处理api diff --git a/config/config.go b/config/config.go index 8019b144..2d0f37a0 100644 --- a/config/config.go +++ b/config/config.go @@ -101,3 +101,25 @@ func GetPortValue() string { } return instance.Settings.Port } + +// 获取Array的值 +func GetArrayValue() bool { + mu.Lock() + defer mu.Unlock() + + if instance == nil { + log.Println("Warning: instance is nil when trying to get array value.") + return false + } + return instance.Settings.Array +} + +// 获取AppID +func GetAppID() uint64 { + mu.Lock() + defer mu.Unlock() + if instance != nil { + return instance.Settings.AppID + } + return 0 // or whatever default value you'd like to return if instance is nil +} diff --git a/config_template.go b/config_template.go index 69999947..edb75ad5 100644 --- a/config_template.go +++ b/config_template.go @@ -26,7 +26,7 @@ settings: global_private_to_channel: false # 是否将私聊转换成频道 array: false - server_dir: ":" # 提供图片上传服务的服务器(图床)需要带端口号. 如果需要发base64图,需为公网ip,且开放对应端口 + server_dir: "" # 提供图片上传服务的服务器(图床)需要带端口号. 如果需要发base64图,需为公网ip,且开放对应端口 port: "" # idmaps和图床对外开放的端口号 lotus: false # lotus特性默认为false,当为true时,将会连接到另一个lotus为false的gensokyo。 # 使用它提供的图床和idmaps服务(场景:同一个机器人在不同服务器运行,或内网需要发送base64图)。 diff --git a/config_template.yml b/config_template.yml index bbfd3259..dc534926 100644 --- a/config_template.yml +++ b/config_template.yml @@ -23,7 +23,7 @@ settings: global_private_to_channel: false # 是否将私聊转换成频道 array: false - server_dir: ":" # 提供图片上传服务的服务器(图床)需要带端口号. 如果需要发base64图,需为公网ip,且开放对应端口 + server_dir: "" # 提供图片上传服务的服务器(图床)需要带端口号. 如果需要发base64图,需为公网ip,且开放对应端口 port: "" # idmaps和图床对外开放的端口号 lotus: false # lotus特性默认为false,当为true时,将会连接到另一个lotus为false的gensokyo。 # 使用它提供的图床和idmaps服务(场景:同一个机器人在不同服务器运行,或内网需要发送base64图)。 diff --git a/handlers/get_friend_list.go b/handlers/get_friend_list.go new file mode 100644 index 00000000..313b2630 --- /dev/null +++ b/handlers/get_friend_list.go @@ -0,0 +1,63 @@ +package handlers + +import ( + "encoding/json" + "log" + + "github.com/hoshinonyaruko/gensokyo/callapi" + "github.com/tencent-connect/botgo/openapi" +) + +func init() { + callapi.RegisterHandler("get_friend_list", handleGetFriendList) +} + +type APIOutput struct { + Data []FriendData `json:"data"` + Message string `json:"message"` + RetCode int `json:"retcode"` + Status string `json:"status"` + Echo interface{} `json:"echo"` +} + +type FriendData struct { + Nickname string `json:"nickname"` + Remark string `json:"remark"` + UserID string `json:"user_id"` +} + +func handleGetFriendList(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.OpenAPI, message callapi.ActionMessage) { + var output APIOutput + + for i := 0; i < 10; i++ { // Assume we want to loop 10 times to create friend data + data := FriendData{ + Nickname: "小狐狸", + Remark: "", + UserID: "2022717137", + } + output.Data = append(output.Data, data) + } + + output.Message = "" + output.RetCode = 0 + output.Status = "ok" + + output.Echo = message.Echo + + // Convert the APIOutput structure to a map[string]interface{} + outputMap := structToMap(output) + + // Send the map + err := client.SendMessage(outputMap) //发回去 + if err != nil { + log.Printf("error sending friend list via wsclient: %v", err) + } + + result, err := json.Marshal(output) + if err != nil { + log.Printf("Error marshaling data: %v", err) + return + } + + log.Printf("get_friend_list: %s", result) +} diff --git a/handlers/get_group_info.go b/handlers/get_group_info.go index 3464a97d..21cc3028 100644 --- a/handlers/get_group_info.go +++ b/handlers/get_group_info.go @@ -77,7 +77,7 @@ func handleGetGroupInfo(client callapi.Client, api openapi.OpenAPI, apiv2 openap groupInfoMap := structToMap(groupInfo) // 打印groupInfoMap的内容 - log.Printf("groupInfoMap(频道): %+v/n", groupInfoMap) + log.Printf("groupInfoMap(频道): %+v\n", groupInfoMap) err = client.SendMessage(groupInfoMap) //发回去 if err != nil { diff --git a/handlers/get_group_list.go b/handlers/get_group_list.go new file mode 100644 index 00000000..604a1327 --- /dev/null +++ b/handlers/get_group_list.go @@ -0,0 +1,154 @@ +package handlers + +import ( + "context" + "encoding/json" + "log" + "strconv" + "time" + + "github.com/hoshinonyaruko/gensokyo/callapi" + "github.com/tencent-connect/botgo/dto" + "github.com/tencent-connect/botgo/openapi" +) + +func init() { + callapi.RegisterHandler("get_group_list", getGroupList) +} + +type Guild struct { + JoinedAt string `json:"joined_at"` + ID string `json:"id"` + OwnerID string `json:"owner_id"` + Description string `json:"description"` + Name string `json:"name"` + MaxMembers string `json:"max_members"` + MemberCount string `json:"member_count"` +} + +type Group struct { + GroupCreateTime string `json:"group_create_time"` + GroupID string `json:"group_id"` + GroupLevel string `json:"group_level"` + GroupMemo string `json:"group_memo"` + GroupName string `json:"group_name"` + MaxMemberCount string `json:"max_member_count"` + MemberCount string `json:"member_count"` +} + +type GroupList struct { + Data []Group `json:"data"` + Message string `json:"message"` + RetCode int `json:"retcode"` + Status string `json:"status"` + Echo interface{} `json:"echo"` +} + +func getGroupList(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.OpenAPI, message callapi.ActionMessage) { + + //群还不支持,这里取得是频道的,如果后期支持了群,那都请求,一起返回 + + // 初始化pager + pager := &dto.GuildPager{ + Limit: "400", + } + + guilds, err := api.MeGuilds(context.TODO(), pager) + if err != nil { + log.Println("Error fetching guild list:", err) + // 创建虚拟的Group + virtualGroup := Group{ + GroupCreateTime: time.Now().Format(time.RFC3339), + GroupID: "0000000", // 或其他虚拟值 + GroupLevel: "0", + GroupMemo: "Error Fetching Guilds", + GroupName: "Error Guild", + MaxMemberCount: "0", + MemberCount: "0", + } + + // 创建包含虚拟Group的GroupList + groupList := GroupList{ + Data: []Group{virtualGroup}, + Message: "Error fetching guilds", + RetCode: -1, // 可以使用其他的错误代码 + Status: "error", + Echo: "0", + } + + if message.Echo == "" { + groupList.Echo = "0" + } else { + groupList.Echo = message.Echo + + outputMap := structToMap(groupList) + + log.Printf("getGroupList(频道): %+v\n", outputMap) + + err = client.SendMessage(outputMap) + if err != nil { + log.Printf("error sending group info via wsclient: %v", err) + } + + result, err := json.Marshal(groupList) + if err != nil { + log.Printf("Error marshaling data: %v", err) + return + } + + log.Printf("get_group_list: %s", result) + return + } + } + + var groups []Group + for _, guild := range guilds { + joinedAtTime, err := guild.JoinedAt.Time() + if err != nil { + log.Println("Error parsing JoinedAt timestamp:", err) + continue + } + joinedAtStr := joinedAtTime.Format(time.RFC3339) // or any other format you prefer + + group := Group{ + GroupCreateTime: joinedAtStr, + GroupID: guild.ID, + GroupLevel: guild.OwnerID, + GroupMemo: guild.Desc, + GroupName: guild.Name, + MaxMemberCount: strconv.FormatInt(guild.MaxMembers, 10), + MemberCount: strconv.Itoa(guild.MemberCount), + // Add other fields if necessary + } + groups = append(groups, group) + } + + groupList := GroupList{ + Data: groups, + Message: "", + RetCode: 0, + Status: "ok", + } + if message.Echo == "" { + groupList.Echo = "0" + } else { + groupList.Echo = message.Echo + + outputMap := structToMap(groupList) + + log.Printf("getGroupList(频道): %+v\n", outputMap) + + err = client.SendMessage(outputMap) + if err != nil { + log.Printf("error sending group info via wsclient: %v", err) + } + + result, err := json.Marshal(groupList) + if err != nil { + log.Printf("Error marshaling data: %v", err) + return + } + + log.Printf("get_group_list: %s", result) + } +} diff --git a/handlers/get_group_member_list.go b/handlers/get_group_member_list.go new file mode 100644 index 00000000..b3c71333 --- /dev/null +++ b/handlers/get_group_member_list.go @@ -0,0 +1,96 @@ +package handlers + +import ( + "context" + "log" + "time" + + "github.com/hoshinonyaruko/gensokyo/callapi" + "github.com/hoshinonyaruko/gensokyo/idmap" + "github.com/tencent-connect/botgo/dto" + "github.com/tencent-connect/botgo/openapi" +) + +// Member Onebot 群成员 +type MemberList struct { + UserID string `json:"user_id"` + GroupID string `json:"group_id"` + Nickname string `json:"nickname"` + Role string `json:"role"` + JoinTime string `json:"join_time"` + LastSentTime string `json:"last_sent_time"` +} + +func init() { + callapi.RegisterHandler("get_group_member_list", getGroupMemberList) +} + +func getGroupMemberList(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.OpenAPI, message callapi.ActionMessage) { + + msgType, err := idmap.ReadConfigv2(message.Params.GroupID.(string), "type") + if err != nil { + log.Printf("Error reading config: %v", err) + return + } + + switch msgType { + case "group": + log.Printf("getGroupMemberList(频道): 目前暂未开放该能力") + return + case "private": + log.Printf("getGroupMemberList(频道): 目前暂未适配私聊虚拟群场景获取虚拟群列表能力") + return + case "guild": + pager := &dto.GuildMembersPager{ + Limit: "400", + } + membersFromAPI, err := api.GuildMembers(context.TODO(), message.Params.GroupID.(string), pager) + if err != nil { + log.Printf("Failed to fetch group members for guild %s: %v", message.Params.GroupID.(string), err) + return + } + + var members []MemberList + for _, memberFromAPI := range membersFromAPI { + joinedAtTime, err := memberFromAPI.JoinedAt.Time() + if err != nil { + log.Println("Error parsing JoinedAt timestamp:", err) + continue + } + joinedAtStr := joinedAtTime.Format(time.RFC3339) // or any other format + + member := MemberList{ + UserID: memberFromAPI.User.ID, + GroupID: message.Params.GroupID.(string), + Nickname: memberFromAPI.Nick, + JoinTime: joinedAtStr, + } + for _, role := range memberFromAPI.Roles { + switch role { + case "4": + member.Role = "owner" + case "2": + member.Role = "admin" + case "11", "default": + member.Role = "member" + } + if member.Role == "owner" || member.Role == "admin" { + break + } + } + members = append(members, member) + } + + // Convert the APIOutput structure to a map[string]interface{} + outputMap := structToMap(members) + + log.Printf("getGroupMemberList(频道): %+v\n", outputMap) + + err = client.SendMessage(outputMap) //发回去 + if err != nil { + log.Printf("Error sending message via client: %v", err) + } + default: + log.Printf("Unknown msgType: %s", msgType) + } +} diff --git a/handlers/get_guild_channel_list.go b/handlers/get_guild_channel_list.go new file mode 100644 index 00000000..c2f0acb6 --- /dev/null +++ b/handlers/get_guild_channel_list.go @@ -0,0 +1,41 @@ +package handlers + +import ( + "log" + + "github.com/hoshinonyaruko/gensokyo/callapi" + "github.com/tencent-connect/botgo/openapi" +) + +type GuildChannelListResponse struct { + Data []interface{} `json:"data"` + Message string `json:"message"` + RetCode int `json:"retcode"` + Status string `json:"status"` + Echo interface{} `json:"echo"` +} + +func init() { + callapi.RegisterHandler("get_guild_channel_list", getGuildChannelList) +} + +func getGuildChannelList(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.OpenAPI, message callapi.ActionMessage) { + + var response GuildChannelListResponse + + response.Data = make([]interface{}, 0) // No data at the moment, but can be populated in the future + response.Message = "" + response.RetCode = 0 + response.Status = "ok" + response.Echo = message.Echo + + // Convert the members slice to a map + outputMap := structToMap(response) + + log.Printf("get_guild_channel_list: %s", outputMap) + + err := client.SendMessage(outputMap) //发回去 + if err != nil { + log.Printf("Error sending message via client: %v", err) + } +} diff --git a/handlers/get_guild_list.go b/handlers/get_guild_list.go new file mode 100644 index 00000000..97036ae7 --- /dev/null +++ b/handlers/get_guild_list.go @@ -0,0 +1,58 @@ +package handlers + +import ( + "log" + + "github.com/hoshinonyaruko/gensokyo/callapi" + "github.com/tencent-connect/botgo/openapi" +) + +type GuildListResponse struct { + Data []GuildData `json:"data"` + Message string `json:"message"` + RetCode int `json:"retcode"` + Status string `json:"status"` + Echo interface{} `json:"echo"` +} + +type GuildData struct { + GuildID string `json:"guild_id"` + GuildName string `json:"guild_name"` + GuildDisplayID string `json:"guild_display_id"` +} + +func init() { + callapi.RegisterHandler("get_guild_list", getGuildList) +} + +func getGuildList(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.OpenAPI, message callapi.ActionMessage) { + + var response GuildListResponse + + // Assuming 'a' is some value you want to loop till. + a := 1 // Replace with appropriate value + + for i := 1; i <= a; i++ { + guildData := GuildData{ + GuildID: "0", + GuildName: "868858989", + GuildDisplayID: "868858989", + } + response.Data = append(response.Data, guildData) + } + + response.Message = "" + response.RetCode = 0 + response.Status = "ok" + response.Echo = string(message.Echo) // Directly assign the string value + + // Convert the members slice to a map + outputMap := structToMap(response) + + log.Printf("getGuildList(频道): %+v\n", outputMap) + + err := client.SendMessage(outputMap) + if err != nil { + log.Printf("Error sending message via client: %v", err) + } +} diff --git a/handlers/get_guild_service_profile.go b/handlers/get_guild_service_profile.go new file mode 100644 index 00000000..b4305e09 --- /dev/null +++ b/handlers/get_guild_service_profile.go @@ -0,0 +1,51 @@ +package handlers + +import ( + "log" + + "github.com/hoshinonyaruko/gensokyo/callapi" + "github.com/tencent-connect/botgo/openapi" +) + +type GuildServiceProfileResponse struct { + Data GuildServiceProfileData `json:"data"` + Message string `json:"message"` + RetCode int `json:"retcode"` + Status string `json:"status"` + Echo interface{} `json:"echo"` +} + +type GuildServiceProfileData struct { + Nickname string `json:"nickname"` + TinyID int64 `json:"tiny_id"` +} + +func init() { + callapi.RegisterHandler("get_guild_service_profile", getGuildServiceProfile) +} + +func getGuildServiceProfile(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.OpenAPI, message callapi.ActionMessage) { + + var response GuildServiceProfileResponse + + response.Data = GuildServiceProfileData{ + Nickname: "", + TinyID: 0, + } + response.Message = "" + response.RetCode = 0 + response.Status = "ok" + response.Echo = string(message.Echo) // Directly assign the string value + + // Convert the members slice to a map + outputMap := structToMap(response) + + log.Printf("get_guild_service_profile: %+v/n", outputMap) + + err := client.SendMessage(outputMap) + if err != nil { + log.Printf("Error sending message via client: %v", err) + } else { + log.Printf("响应get_guild_service_profile: %+v", outputMap) + } +} diff --git a/handlers/get_login_info.go b/handlers/get_login_info.go new file mode 100644 index 00000000..1ac51b61 --- /dev/null +++ b/handlers/get_login_info.go @@ -0,0 +1,59 @@ +package handlers + +import ( + "fmt" + "log" + + "github.com/hoshinonyaruko/gensokyo/callapi" + "github.com/hoshinonyaruko/gensokyo/config" + "github.com/tencent-connect/botgo/openapi" +) + +type LoginInfoResponse struct { + Data LoginInfoData `json:"data"` + Message string `json:"message"` + RetCode int `json:"retcode"` + Status string `json:"status"` + Echo interface{} `json:"echo"` +} + +type LoginInfoData struct { + Nickname string `json:"nickname"` + UserID string `json:"user_id"` // Assuming UserID is a string type based on the pseudocode +} + +func init() { + callapi.RegisterHandler("get_login_info", getLoginInfo) +} + +func getLoginInfo(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.OpenAPI, message callapi.ActionMessage) { + + var response LoginInfoResponse + + // Assuming 全局_botid is a global or environment variable + globalBotID := config.GetAppID() // Replace with the actual global variable or value + userIDStr := fmt.Sprintf("%d", globalBotID) + + response.Data = LoginInfoData{ + Nickname: "gensokyo全域机器人", + UserID: userIDStr, + } + response.Message = "" + response.RetCode = 0 + response.Status = "ok" + response.Echo = string(message.Echo) + + // Convert the members slice to a map + outputMap := structToMap(response) + + log.Printf("get_login_info: %+v\n", outputMap) + + err := client.SendMessage(outputMap) + if err != nil { + log.Printf("Error sending message via client: %v", err) + } else { + log.Printf("响应get_login_info: %+v", outputMap) + } + + return +} diff --git a/handlers/get_online_clients.go b/handlers/get_online_clients.go new file mode 100644 index 00000000..6e82aeae --- /dev/null +++ b/handlers/get_online_clients.go @@ -0,0 +1,51 @@ +package handlers + +import ( + "log" + + "github.com/hoshinonyaruko/gensokyo/callapi" + "github.com/tencent-connect/botgo/openapi" +) + +type OnlineClientsResponse struct { + Data OnlineClientsData `json:"data"` + Message string `json:"message"` + RetCode int `json:"retcode"` + Status string `json:"status"` + Echo interface{} `json:"echo"` +} + +type OnlineClientsData struct { + Clients []interface{} `json:"clients"` // It seems you want an empty array for clients + TinyID int64 `json:"tiny_id"` +} + +func init() { + callapi.RegisterHandler("get_online_clients", getOnlineClients) +} + +func getOnlineClients(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.OpenAPI, message callapi.ActionMessage) { + + var response OnlineClientsResponse + + response.Data = OnlineClientsData{ + Clients: make([]interface{}, 0), // Empty array + TinyID: 0, + } + response.Message = "" + response.RetCode = 0 + response.Status = "ok" + response.Echo = string(message.Echo) + + // Convert the members slice to a map + outputMap := structToMap(response) + + log.Printf("get_online_clients: %+v\n", outputMap) + + err := client.SendMessage(outputMap) + if err != nil { + log.Printf("Error sending message via client: %v", err) + } else { + log.Printf("响应get_online_clients: %+v", outputMap) + } +} diff --git a/handlers/get_version_info.go b/handlers/get_version_info.go new file mode 100644 index 00000000..4431a6b2 --- /dev/null +++ b/handlers/get_version_info.go @@ -0,0 +1,73 @@ +package handlers + +import ( + "log" + + "github.com/hoshinonyaruko/gensokyo/callapi" + "github.com/tencent-connect/botgo/openapi" +) + +type VersionInfoResponse struct { + Data VersionData `json:"data"` + Message string `json:"message"` + RetCode int `json:"retcode"` + Status string `json:"status"` + Echo interface{} `json:"echo"` +} + +type VersionData struct { + AppFullName string `json:"app_full_name"` + AppName string `json:"app_name"` + AppVersion string `json:"app_version"` + CoolQDirectory string `json:"coolq_directory"` + CoolQEdition string `json:"coolq_edition"` + GoCQHTTP bool `json:"go-cqhttp"` + PluginBuildConfiguration string `json:"plugin_build_configuration"` + PluginBuildNumber int `json:"plugin_build_number"` + PluginVersion string `json:"plugin_version"` + ProtocolName int `json:"protocol_name"` + ProtocolVersion string `json:"protocol_version"` + RuntimeOS string `json:"runtime_os"` + RuntimeVersion string `json:"runtime_version"` + Version string `json:"version"` +} + +func init() { + callapi.RegisterHandler("get_version_info", getVersionInfo) +} + +func getVersionInfo(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.OpenAPI, message callapi.ActionMessage) { + + var response VersionInfoResponse + + response.Data = VersionData{ + AppFullName: "gensokyo", + AppName: "gensokyo", + AppVersion: "v1.0.0", + CoolQDirectory: "", + CoolQEdition: "pro", + GoCQHTTP: true, + PluginBuildConfiguration: "release", + PluginBuildNumber: 99, + PluginVersion: "4.15.0", + ProtocolName: 4, + ProtocolVersion: "v11", + RuntimeOS: "windows", + RuntimeVersion: "go1.20.2", + Version: "v1.0.0", + } + response.Message = "" + response.RetCode = 0 + response.Status = "ok" + response.Echo = string(message.Echo) // Directly assign the string value + + // Convert the members slice to a map + outputMap := structToMap(response) + + log.Printf("get_version_info: %+v/n", outputMap) + + err := client.SendMessage(outputMap) + if err != nil { + log.Printf("Error sending message via client: %v", err) + } +} diff --git a/handlers/message_parser.go b/handlers/message_parser.go index 58dc8a31..3c98a62d 100644 --- a/handlers/message_parser.go +++ b/handlers/message_parser.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/hoshinonyaruko/gensokyo/callapi" + "github.com/tencent-connect/botgo/dto" ) var BotID string @@ -116,6 +117,7 @@ func transformMessageText(messageText string) string { }) } +// 处理at和其他定形文到onebotv11格式(cq码) func RevertTransformedText(messageText string) string { // Trim leading and trailing spaces messageText = strings.TrimSpace(messageText) @@ -134,3 +136,77 @@ func RevertTransformedText(messageText string) string { return m }) } + +// 将收到的data.content转换为message segment todo,群场景不支持受图片,频道场景的图片可以拼一下 +func ConvertToSegmentedMessage(data interface{}) []map[string]interface{} { + // 强制类型转换,获取Message结构 + var msg *dto.Message + switch v := data.(type) { + case *dto.WSGroupATMessageData: + msg = (*dto.Message)(v) + case *dto.WSATMessageData: + msg = (*dto.Message)(v) + case *dto.WSMessageData: + msg = (*dto.Message)(v) + case *dto.WSDirectMessageData: + msg = (*dto.Message)(v) + case *dto.WSC2CMessageData: + msg = (*dto.Message)(v) + default: + return nil + } + + var messageSegments []map[string]interface{} + + // 处理Attachments字段来构建图片消息 + for _, attachment := range msg.Attachments { + imageFileMD5 := attachment.FileName + for _, ext := range []string{"{", "}", ".png", ".jpg", ".gif", "-"} { + imageFileMD5 = strings.ReplaceAll(imageFileMD5, ext, "") + } + imageSegment := map[string]interface{}{ + "type": "image", + "data": map[string]interface{}{ + "file": imageFileMD5 + ".image", + "subType": "0", + "url": attachment.URL, + }, + } + messageSegments = append(messageSegments, imageSegment) + + // 在msg.Content中替换旧的图片链接 + newImagePattern := "[CQ:image,file=" + attachment.URL + "]" + msg.Content = msg.Content + newImagePattern + } + + // 使用正则表达式查找所有的[@数字]格式 + r := regexp.MustCompile(`<@!(\d+)>`) + atMatches := r.FindAllStringSubmatch(msg.Content, -1) + + for _, match := range atMatches { + // 构建at部分的映射并加入到messageSegments + atSegment := map[string]interface{}{ + "type": "at", + "data": map[string]interface{}{ + "qq": match[1], + }, + } + messageSegments = append(messageSegments, atSegment) + + // 从原始内容中移除at部分 + msg.Content = strings.Replace(msg.Content, match[0], "", 1) + } + + // 如果还有其他内容,那么这些内容被视为文本部分 + if msg.Content != "" { + textSegment := map[string]interface{}{ + "type": "text", + "data": map[string]interface{}{ + "text": msg.Content, + }, + } + messageSegments = append(messageSegments, textSegment) + } + + return messageSegments +} diff --git a/handlers/send_group_msg.go b/handlers/send_group_msg.go index 18115504..76091c21 100644 --- a/handlers/send_group_msg.go +++ b/handlers/send_group_msg.go @@ -27,12 +27,12 @@ func handleSendGroupMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openap //如果获取不到 就用user_id获取信息类型 if msgType == "" { - msgType = GetMessageTypeByUserid(client.GetAppID(), message.Params.UserID) + msgType = GetMessageTypeByUserid(client.GetAppIDStr(), message.Params.UserID) } //如果获取不到 就用group_id获取信息类型 if msgType == "" { - msgType = GetMessageTypeByGroupid(client.GetAppID(), message.Params.GroupID) + msgType = GetMessageTypeByGroupid(client.GetAppIDStr(), message.Params.GroupID) } switch msgType { @@ -50,7 +50,7 @@ func handleSendGroupMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openap log.Println("foundItems:", foundItems) // 如果messageID为空,通过函数获取 if messageID == "" { - messageID = GetMessageIDByUseridOrGroupid(client.GetAppID(), message.Params.GroupID) + messageID = GetMessageIDByUseridOrGroupid(client.GetAppIDStr(), message.Params.GroupID) log.Println("通过GetMessageIDByUserid函数获取的message_id:", messageID) } @@ -64,7 +64,7 @@ func handleSendGroupMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openap // 优先发送文本信息 if messageText != "" { - groupReply := generateMessage(messageID, nil, messageText) + groupReply := generateGroupMessage(messageID, nil, messageText) // 进行类型断言 groupMessage, ok := groupReply.(*dto.MessageToCreate) @@ -85,7 +85,7 @@ func handleSendGroupMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openap var singleItem = make(map[string][]string) singleItem[key] = urls - groupReply := generateMessage(messageID, singleItem, "") + groupReply := generateGroupMessage(messageID, singleItem, "") // 进行类型断言 richMediaMessage, ok := groupReply.(*dto.RichMediaMessage) @@ -123,16 +123,8 @@ func handleSendGroupMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openap handleSendGuildChannelPrivateMsg(client, api, apiv2, message, &value, &message.Params.ChannelID) case "group_private": //用userid还原出openid 这是虚拟成群的群聊私聊信息 - //todo - message.Params.ChannelID = message.Params.GroupID.(string) - //读取ini 通过ChannelID取回之前储存的guild_id - value, err := idmap.ReadConfigv2(message.Params.ChannelID, "guild_id") - if err != nil { - log.Printf("Error reading config: %v", err) - return - } - message.Params.GroupID = value - handleSendGuildChannelMsg(client, api, apiv2, message) + message.Params.UserID = message.Params.GroupID.(string) + handleSendPrivateMsg(client, api, apiv2, message) default: log.Printf("Unknown message type: %s", msgType) } diff --git a/handlers/send_guild_channel_msg.go b/handlers/send_guild_channel_msg.go index 99085d24..71e47fe4 100644 --- a/handlers/send_guild_channel_msg.go +++ b/handlers/send_guild_channel_msg.go @@ -25,12 +25,12 @@ func handleSendGuildChannelMsg(client callapi.Client, api openapi.OpenAPI, apiv2 //如果获取不到 就用user_id获取信息类型 if msgType == "" { - msgType = GetMessageTypeByUserid(client.GetAppID(), message.Params.UserID) + msgType = GetMessageTypeByUserid(client.GetAppIDStr(), message.Params.UserID) } //如果获取不到 就用group_id获取信息类型 if msgType == "" { - appID := client.GetAppID() + appID := client.GetAppIDStr() groupID := message.Params.GroupID fmt.Printf("appID: %s, GroupID: %v\n", appID, groupID) diff --git a/handlers/send_msg.go b/handlers/send_msg.go index 4c4caf01..e20753d4 100644 --- a/handlers/send_msg.go +++ b/handlers/send_msg.go @@ -27,7 +27,7 @@ func handleSendMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.Ope //如果获取不到 就用group_id获取信息类型 if msgType == "" { - appID := client.GetAppID() + appID := client.GetAppIDStr() groupID := message.Params.GroupID fmt.Printf("appID: %s, GroupID: %v\n", appID, groupID) @@ -37,7 +37,7 @@ func handleSendMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.Ope //如果获取不到 就用user_id获取信息类型 if msgType == "" { - msgType = GetMessageTypeByUserid(client.GetAppID(), message.Params.UserID) + msgType = GetMessageTypeByUserid(client.GetAppIDStr(), message.Params.UserID) } switch msgType { @@ -55,7 +55,7 @@ func handleSendMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.Ope log.Println("foundItems:", foundItems) // 如果messageID为空,通过函数获取 if messageID == "" { - messageID = GetMessageIDByUseridOrGroupid(client.GetAppID(), message.Params.GroupID) + messageID = GetMessageIDByUseridOrGroupid(client.GetAppIDStr(), message.Params.GroupID) log.Println("通过GetMessageIDByUserid函数获取的message_id:", messageID) } @@ -143,17 +143,62 @@ func handleSendMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.Ope handleSendGuildChannelPrivateMsg(client, api, apiv2, message, GuildidPtr, channelIDPtr) case "group_private": - //用userid还原出openid 这是虚拟成群的群聊私聊信息 - //todo - message.Params.ChannelID = message.Params.GroupID.(string) - //读取ini 通过ChannelID取回之前储存的guild_id - value, err := idmap.ReadConfigv2(message.Params.ChannelID, "guild_id") + //私聊信息 + //还原真实的userid + UserID, err := idmap.RetrieveRowByIDv2(message.Params.UserID.(string)) if err != nil { log.Printf("Error reading config: %v", err) return } - message.Params.GroupID = value - handleSendGuildChannelMsg(client, api, apiv2, message) + + // 解析消息内容 + messageText, foundItems := parseMessageContent(message.Params) + + // 获取 echo 的值 + echostr := string(message.Echo) + + // 使用 echo 获取消息ID + messageID := echo.GetMsgIDByKey(echostr) + log.Println("私聊发信息对应的message_id:", messageID) + log.Println("私聊发信息messageText:", messageText) + log.Println("foundItems:", foundItems) + + // 优先发送文本信息 + if messageText != "" { + groupReply := generateMessage(messageID, nil, messageText) + + // 进行类型断言 + groupMessage, ok := groupReply.(*dto.MessageToCreate) + if !ok { + log.Println("Error: Expected MessageToCreate type.") + return + } + + groupMessage.Timestamp = time.Now().Unix() // 设置时间戳 + _, err := apiv2.PostC2CMessage(context.TODO(), UserID, groupMessage) + if err != nil { + log.Printf("发送文本私聊信息失败: %v", err) + } + } + + // 遍历 foundItems 并发送每种信息 + for key, urls := range foundItems { + var singleItem = make(map[string][]string) + singleItem[key] = urls + + groupReply := generateMessage(messageID, singleItem, "") + + // 进行类型断言 + richMediaMessage, ok := groupReply.(*dto.RichMediaMessage) + if !ok { + log.Printf("Error: Expected RichMediaMessage type for key %s.", key) + continue + } + _, err := apiv2.PostC2CMessage(context.TODO(), UserID, richMediaMessage) + if err != nil { + log.Printf("发送 %s 私聊信息失败: %v", key, err) + } + } default: log.Printf("1Unknown message type: %s", msgType) } diff --git a/handlers/send_private_msg.go b/handlers/send_private_msg.go index 2d52727c..68423f61 100644 --- a/handlers/send_private_msg.go +++ b/handlers/send_private_msg.go @@ -25,6 +25,14 @@ func handleSendPrivateMsg(client callapi.Client, api openapi.OpenAPI, apiv2 open switch msgType { case "group_private": + //私聊信息 + //还原真实的userid + UserID, err := idmap.RetrieveRowByIDv2(message.Params.UserID.(string)) + if err != nil { + log.Printf("Error reading config: %v", err) + return + } + // 解析消息内容 messageText, foundItems := parseMessageContent(message.Params) @@ -33,18 +41,10 @@ func handleSendPrivateMsg(client callapi.Client, api openapi.OpenAPI, apiv2 open // 使用 echo 获取消息ID messageID := echo.GetMsgIDByKey(echostr) - log.Println("群组发信息对应的message_id:", messageID) - log.Println("群组发信息messageText:", messageText) + log.Println("私聊发信息对应的message_id:", messageID) + log.Println("私聊发信息messageText:", messageText) log.Println("foundItems:", foundItems) - //通过bolt数据库还原真实的GroupID - originalGroupID, err := idmap.RetrieveRowByIDv2(message.Params.GroupID.(string)) - if err != nil { - log.Printf("Error retrieving original GroupID: %v", err) - return - } - message.Params.GroupID = originalGroupID - // 优先发送文本信息 if messageText != "" { groupReply := generatePrivateMessage(messageID, nil, messageText) @@ -53,17 +53,17 @@ func handleSendPrivateMsg(client callapi.Client, api openapi.OpenAPI, apiv2 open groupMessage, ok := groupReply.(*dto.MessageToCreate) if !ok { log.Println("Error: Expected MessageToCreate type.") - return // 或其他错误处理 + return } groupMessage.Timestamp = time.Now().Unix() // 设置时间戳 - _, err := apiv2.PostGroupMessage(context.TODO(), message.Params.GroupID.(string), groupMessage) + _, err := apiv2.PostC2CMessage(context.TODO(), UserID, groupMessage) if err != nil { - log.Printf("发送文本群组信息失败: %v", err) + log.Printf("发送文本私聊信息失败: %v", err) } } - // 遍历foundItems并发送每种信息 + // 遍历 foundItems 并发送每种信息 for key, urls := range foundItems { var singleItem = make(map[string][]string) singleItem[key] = urls @@ -74,13 +74,11 @@ func handleSendPrivateMsg(client callapi.Client, api openapi.OpenAPI, apiv2 open richMediaMessage, ok := groupReply.(*dto.RichMediaMessage) if !ok { log.Printf("Error: Expected RichMediaMessage type for key %s.", key) - continue // 跳过这个项,继续下一个 + continue } - - //richMediaMessage.Timestamp = time.Now().Unix() // 设置时间戳 - _, err := apiv2.PostGroupMessage(context.TODO(), message.Params.GroupID.(string), richMediaMessage) + _, err := apiv2.PostC2CMessage(context.TODO(), UserID, richMediaMessage) if err != nil { - log.Printf("发送 %s 信息失败: %v", key, err) + log.Printf("发送 %s 私聊信息失败: %v", key, err) } } case "guild_private": @@ -151,7 +149,7 @@ func handleSendGuildChannelPrivateMsg(client callapi.Client, api openapi.OpenAPI log.Println("foundItems:", foundItems) // 如果messageID为空,通过函数获取 if messageID == "" { - messageID = GetMessageIDByUseridOrGroupid(client.GetAppID(), message.Params.UserID) + messageID = GetMessageIDByUseridOrGroupid(client.GetAppIDStr(), message.Params.UserID) log.Println("通过GetMessageIDByUserid函数获取的message_id:", messageID) } diff --git a/handlers/set_group_ban.go b/handlers/set_group_ban.go new file mode 100644 index 00000000..affb142b --- /dev/null +++ b/handlers/set_group_ban.go @@ -0,0 +1,65 @@ +package handlers + +import ( + "context" + "log" + "strconv" + + "github.com/hoshinonyaruko/gensokyo/callapi" + "github.com/hoshinonyaruko/gensokyo/idmap" + "github.com/tencent-connect/botgo/dto" + "github.com/tencent-connect/botgo/openapi" +) + +func init() { + callapi.RegisterHandler("get_group_ban", setGroupBan) +} + +func setGroupBan(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.OpenAPI, message callapi.ActionMessage) { + + // 从message中获取group_id和UserID + groupID := message.Params.GroupID.(string) + receivedUserID := message.Params.UserID.(string) + + // 根据group_id读取guild_id + guildID, err := idmap.ReadConfigv2(groupID, "guild_id") + if err != nil { + log.Printf("Error reading config for guild_id: %v", err) + return + } + + // 根据UserID读取真实的userid + realUserID, err := idmap.RetrieveRowByIDv2(receivedUserID) + if err != nil { + log.Printf("Error reading real userID: %v", err) + return + } + + // 读取消息类型 + msgType, err := idmap.ReadConfigv2(groupID, "type") + if err != nil { + log.Printf("Error reading config for message type: %v", err) + return + } + + // 根据消息类型进行操作 + switch msgType { + case "group": + log.Printf("setGroupBan(频道): 目前暂未开放该能力") + return + case "private": + log.Printf("setGroupBan(频道): 目前暂未适配私聊虚拟群场景的禁言能力") + return + case "guild": + duration := strconv.Itoa(message.Params.Duration) + mute := &dto.UpdateGuildMute{ + MuteSeconds: duration, + UserIDs: []string{realUserID}, + } + err := api.MemberMute(context.TODO(), guildID, realUserID, mute) + if err != nil { + log.Printf("Error muting member: %v", err) + } + return + } +} diff --git a/handlers/set_group_whole_ban.go b/handlers/set_group_whole_ban.go new file mode 100644 index 00000000..771dae55 --- /dev/null +++ b/handlers/set_group_whole_ban.go @@ -0,0 +1,60 @@ +package handlers + +import ( + "context" + "log" + + "github.com/hoshinonyaruko/gensokyo/callapi" + "github.com/hoshinonyaruko/gensokyo/idmap" + "github.com/tencent-connect/botgo/dto" + "github.com/tencent-connect/botgo/openapi" +) + +func init() { + callapi.RegisterHandler("get_group_whole_ban", setGroupBan) +} + +func setGroupWholeBan(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.OpenAPI, message callapi.ActionMessage) { + // 从message中获取group_id + groupID := message.Params.GroupID.(string) + + // 根据group_id读取guild_id + guildID, err := idmap.ReadConfigv2(groupID, "guild_id") + if err != nil { + log.Printf("Error reading config for guild_id: %v", err) + return + } + + // 读取消息类型 + msgType, err := idmap.ReadConfigv2(groupID, "type") + if err != nil { + log.Printf("Error reading config for message type: %v", err) + return + } + + // 根据消息类型进行操作 + switch msgType { + case "group": + log.Printf("setGroupWholeBan(频道): 目前暂未开放该能力") + return + case "private": + log.Printf("setGroupWholeBan(频道): 目前暂未适配私聊虚拟群场景的禁言能力") + return + case "guild": + var duration string + if message.Params.Enable { + duration = "604800" // 7天: 60 * 60 * 24 * 7 onebot的全体禁言只有禁言和解开,先尝试7天 + } else { + duration = "0" + } + + mute := &dto.UpdateGuildMute{ + MuteSeconds: duration, + } + err := api.GuildMute(context.TODO(), guildID, mute) + if err != nil { + log.Printf("Error setting whole guild mute: %v", err) + } + return + } +} diff --git a/idmap/service.go b/idmap/service.go index 554b9a7a..d4f6202f 100644 --- a/idmap/service.go +++ b/idmap/service.go @@ -43,7 +43,7 @@ func CloseDB() { } // 根据a储存b -func StoreIDv2(id string) (int64, error) { +func StoreID(id string) (int64, error) { var newRow int64 err := db.Update(func(tx *bolt.Tx) error { @@ -79,8 +79,8 @@ func StoreIDv2(id string) (int64, error) { return newRow, err } -// StoreIDv2v2 根据a储存b -func StoreIDv2v2(id string) (int64, error) { +// StoreIDv2 根据a储存b +func StoreIDv2(id string) (int64, error) { if config.GetLotusValue() { // 使用网络请求方式 serverDir := config.GetServer_dir() @@ -112,11 +112,11 @@ func StoreIDv2v2(id string) (int64, error) { } // 如果lotus为假,就保持原来的store的方法 - return StoreIDv2(id) + return StoreID(id) } // 根据b得到a -func RetrieveRowByIDv2(rowid string) (string, error) { +func RetrieveRowByID(rowid string) (string, error) { var id string err := db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(BucketName)) @@ -134,8 +134,8 @@ func RetrieveRowByIDv2(rowid string) (string, error) { return id, err } -// RetrieveRowByIDv2v2 根据b得到a -func RetrieveRowByIDv2v2(rowid string) (string, error) { +// RetrieveRowByIDv2 根据b得到a +func RetrieveRowByIDv2(rowid string) (string, error) { if config.GetLotusValue() { // 使用网络请求方式 serverDir := config.GetServer_dir() @@ -167,11 +167,11 @@ func RetrieveRowByIDv2v2(rowid string) (string, error) { } // 如果lotus为假,就保持原来的RetrieveRowByIDv2的方法 - return RetrieveRowByIDv2(rowid) + return RetrieveRowByID(rowid) } // 根据a 以b为类别 储存c -func WriteConfigv2(sectionName, keyName, value string) error { +func WriteConfig(sectionName, keyName, value string) error { return db.Update(func(tx *bolt.Tx) error { b, err := tx.CreateBucketIfNotExists([]byte(ConfigBucket)) if err != nil { @@ -183,8 +183,8 @@ func WriteConfigv2(sectionName, keyName, value string) error { }) } -// WriteConfigv2v2 根据a以b为类别储存c -func WriteConfigv2v2(sectionName, keyName, value string) error { +// WriteConfigv2 根据a以b为类别储存c +func WriteConfigv2(sectionName, keyName, value string) error { if config.GetLotusValue() { // 使用网络请求方式 serverDir := config.GetServer_dir() @@ -213,11 +213,11 @@ func WriteConfigv2v2(sectionName, keyName, value string) error { } // 如果lotus为假,则使用原始方法在本地写入配置 - return WriteConfigv2(sectionName, keyName, value) + return WriteConfig(sectionName, keyName, value) } // 根据a和b取出c -func ReadConfigv2(sectionName, keyName string) (string, error) { +func ReadConfig(sectionName, keyName string) (string, error) { var result string err := db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(ConfigBucket)) @@ -238,8 +238,8 @@ func ReadConfigv2(sectionName, keyName string) (string, error) { return result, err } -// ReadConfigv2v2 根据a和b取出c -func ReadConfigv2v2(sectionName, keyName string) (string, error) { +// ReadConfigv2 根据a和b取出c +func ReadConfigv2(sectionName, keyName string) (string, error) { if config.GetLotusValue() { // 使用网络请求方式 serverDir := config.GetServer_dir() @@ -281,7 +281,7 @@ func ReadConfigv2v2(sectionName, keyName string) (string, error) { } // 如果lotus为假,则使用原始方法在本地读取配置 - return ReadConfigv2(sectionName, keyName) + return ReadConfig(sectionName, keyName) } // 灵感,ini配置文件 diff --git a/main.go b/main.go index 6b5938e3..924bab40 100644 --- a/main.go +++ b/main.go @@ -7,7 +7,6 @@ import ( "log" "os" "os/signal" - "strconv" "syscall" "time" @@ -87,16 +86,16 @@ func main() { apiV2 := botgo.NewOpenAPI(token).WithTimeout(3 * time.Second) // 执行API请求 显示机器人信息 - // me, err := api.Me(ctx) // Adjusted to pass only the context - // if err != nil { - // fmt.Printf("Error fetching bot details: %v\n", err) - // return - // } - // fmt.Printf("Bot details: %+v\n", me) + me, err := api.Me(ctx) // Adjusted to pass only the context + if err != nil { + fmt.Printf("Error fetching bot details: %v\n", err) + return + } + fmt.Printf("Bot details: %+v\n", me) //初始化handlers - //handlers.BotID = me.ID - handlers.BotID = "1234" + handlers.BotID = me.ID + //handlers.BotID = "1234" handlers.AppID = fmt.Sprintf("%d", conf.Settings.AppID) // 获取 websocket 信息 这里用哪一个api获取就是用哪一个api去连接ws @@ -139,8 +138,7 @@ func main() { // 在新的 go 函数中初始化 wsClient go func() { - appIDStr := strconv.FormatUint(conf.Settings.AppID, 10) // Assuming base 10 - wsClient, err := wsclient.NewWebSocketClient(conf.Settings.WsAddress, appIDStr, api, apiV2) + wsClient, err := wsclient.NewWebSocketClient(conf.Settings.WsAddress, conf.Settings.AppID, api, apiV2) if err != nil { fmt.Printf("Error creating WebSocketClient: %v\n", err) close(wsClientChan) // 关闭通道表示不再发送值 @@ -269,7 +267,8 @@ func GroupATMessageEventHandler() event.GroupATMessageEventHandler { // C2CMessageEventHandler 实现处理 群私聊 消息的回调 func C2CMessageEventHandler() event.C2CMessageEventHandler { return func(event *dto.WSPayload, data *dto.WSC2CMessageData) error { - return processor.ProcessC2CMessage(string(event.RawMessage), data) + log.Print("1111") + return processor.ProcessC2CMessage(data) } } diff --git a/wsclient/ws.go b/wsclient/ws.go index 6c01fd48..224259a0 100644 --- a/wsclient/ws.go +++ b/wsclient/ws.go @@ -17,14 +17,19 @@ type WebSocketClient struct { conn *websocket.Conn api openapi.OpenAPI apiv2 openapi.OpenAPI - appid string + appid uint64 } // 获取appid -func (c *WebSocketClient) GetAppID() string { +func (c *WebSocketClient) GetAppID() uint64 { return c.appid } +// 获取appid的字符串形式 +func (c *WebSocketClient) GetAppIDStr() string { + return fmt.Sprintf("%d", c.appid) +} + // 发送json信息给onebot应用端 func (c *WebSocketClient) SendMessage(message map[string]interface{}) error { msgBytes, err := json.Marshal(message) @@ -86,7 +91,7 @@ func truncateMessage(message callapi.ActionMessage, maxLength int) string { } // 发送心跳包 -func (c *WebSocketClient) sendHeartbeat(ctx context.Context, botID string) { +func (c *WebSocketClient) sendHeartbeat(ctx context.Context, botID uint64) { for { select { case <-ctx.Done(): @@ -105,11 +110,11 @@ func (c *WebSocketClient) sendHeartbeat(ctx context.Context, botID string) { } // NewWebSocketClient 创建 WebSocketClient 实例,接受 WebSocket URL、botID 和 openapi.OpenAPI 实例 -func NewWebSocketClient(urlStr string, botID string, api openapi.OpenAPI, apiv2 openapi.OpenAPI) (*WebSocketClient, error) { +func NewWebSocketClient(urlStr string, botID uint64, api openapi.OpenAPI, apiv2 openapi.OpenAPI) (*WebSocketClient, error) { headers := http.Header{ "User-Agent": []string{"CQHttp/4.15.0"}, "X-Client-Role": []string{"Universal"}, - "X-Self-ID": []string{botID}, + "X-Self-ID": []string{fmt.Sprintf("%d", botID)}, } dialer := websocket.Dialer{ From 618561c3e93f2d77e0338582ef95d3a632e60228 Mon Sep 17 00:00:00 2001 From: cosmo Date: Thu, 26 Oct 2023 13:04:28 +0800 Subject: [PATCH 08/27] add action --- .github/workflows/cross_compile.yml | 68 +++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 .github/workflows/cross_compile.yml diff --git a/.github/workflows/cross_compile.yml b/.github/workflows/cross_compile.yml new file mode 100644 index 00000000..a4e4873b --- /dev/null +++ b/.github/workflows/cross_compile.yml @@ -0,0 +1,68 @@ +name: Cross Compile Go Project + +on: + pull_request: + types: [opened, synchronize] + +jobs: + build: + name: Build on ${{ matrix.os }} for ${{ matrix.goarch }} + runs-on: ubuntu-latest + strategy: + matrix: + include: + - os: linux + goarch: amd64 + - os: linux + goarch: 386 + - os: linux + goarch: arm + - os: linux + goarch: arm64 + - os: darwin + goarch: amd64 + - os: darwin + goarch: arm64 + - os: windows + goarch: amd64 + - os: windows + goarch: 386 + - os: android + goarch: arm + - os: android + goarch: arm64 + # ... Add other combinations as needed + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: '1.21.1' # Set to specific Go version. + + - name: Setup Android NDK (only for Android builds) + if: matrix.os == 'android' + run: | + sudo apt-get install -y wget unzip + wget https://dl.google.com/android/repository/android-ndk-r21e-linux-x86_64.zip || exit 1 + unzip android-ndk-r21e-linux-x86_64.zip || exit 1 + export ANDROID_NDK_HOME=$PWD/android-ndk-r21e + echo "ANDROID_NDK_HOME=$ANDROID_NDK_HOME" >> $GITHUB_ENV + + - name: Create output directory + run: mkdir -p output + + - name: Compile Go for target + env: + GOOS: ${{ matrix.os }} + GOARCH: ${{ matrix.goarch }} + run: | + go build -o output/gensokyo-${{ matrix.os }}-${{ matrix.goarch }} + + - name: Upload artifacts + uses: actions/upload-artifact@v2 + with: + name: gensokyo-${{ matrix.os }}-${{ matrix.goarch }} + path: output/gensokyo-${{ matrix.os }}-${{ matrix.goarch }} From 7e803aa84dd738a16daf89d7384bb928fd612774 Mon Sep 17 00:00:00 2001 From: cosmo Date: Thu, 26 Oct 2023 13:10:11 +0800 Subject: [PATCH 09/27] add action --- .gitignore | 2 - botgo/examples/go.mod | 27 ++++++ botgo/examples/go.sum | 149 ++++++++++++++++++++++++++++++++ botgo/go.mod | 13 +++ botgo/go.sum | 127 ++++++++++++++++++++++++++++ go.mod | 46 ++++++++++ go.sum | 192 ++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 554 insertions(+), 2 deletions(-) create mode 100644 botgo/examples/go.mod create mode 100644 botgo/examples/go.sum create mode 100644 botgo/go.mod create mode 100644 botgo/go.sum create mode 100644 go.mod create mode 100644 go.sum diff --git a/.gitignore b/.gitignore index 4a3dc0c7..6eb03ec0 100644 --- a/.gitignore +++ b/.gitignore @@ -5,8 +5,6 @@ idmap.db.lock idmap.ini # Go specific -go.mod -go.sum *.exe # Ignore channel_temp diff --git a/botgo/examples/go.mod b/botgo/examples/go.mod new file mode 100644 index 00000000..37f80113 --- /dev/null +++ b/botgo/examples/go.mod @@ -0,0 +1,27 @@ +module examples + +go 1.17 + +require ( + github.com/go-redis/redis/v8 v8.11.4 + github.com/google/uuid v1.3.0 + github.com/tencent-connect/botgo v0.0.0-00010101000000-000000000000 + go.uber.org/zap v1.19.1 + gopkg.in/yaml.v2 v2.4.0 +) + +require ( + github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/go-resty/resty/v2 v2.6.0 // indirect + github.com/gorilla/websocket v1.4.2 // indirect + github.com/tidwall/gjson v1.9.3 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.0 // indirect + go.uber.org/atomic v1.9.0 // indirect + go.uber.org/multierr v1.7.0 // indirect + golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect +) + +replace github.com/tencent-connect/botgo => ../ diff --git a/botgo/examples/go.sum b/botgo/examples/go.sum new file mode 100644 index 00000000..88db2035 --- /dev/null +++ b/botgo/examples/go.sum @@ -0,0 +1,149 @@ +github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg= +github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w= +github.com/go-resty/resty/v2 v2.6.0 h1:joIR5PNLM2EFqqESUjCMGXrWmXNHEU9CEiK813oKYS4= +github.com/go-resty/resty/v2 v2.6.0/go.mod h1:PwvJS6hvaPkjtjNg9ph+VrSD92bi5Zq73w/BIH7cC3Q= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c= +github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tidwall/gjson v1.9.3 h1:hqzS9wAHMO+KVBBkLxYdkEeeFHuqr95GfClRLKlgK0E= +github.com/tidwall/gjson v1.9.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.11-0.20210813005559-691160354723 h1:sHOAIxRGBp443oHZIPB+HsUGaksVCXVQENPxwTfQdH4= +go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.7.0 h1:zaiO/rmgFjbmCXdSYJWQcdvOCsthmdaHfr3Gm2Kx4Ec= +go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= +go.uber.org/zap v1.19.1 h1:ue41HOKd1vGURxrmeKIgELGb3jPW9DMUDGtsinblHwI= +go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/botgo/go.mod b/botgo/go.mod new file mode 100644 index 00000000..34d76325 --- /dev/null +++ b/botgo/go.mod @@ -0,0 +1,13 @@ +module github.com/tencent-connect/botgo + +go 1.16 + +require ( + github.com/go-redis/redis/v8 v8.11.5 + github.com/go-resty/resty/v2 v2.6.0 + github.com/google/uuid v1.3.1 + github.com/gorilla/websocket v1.4.2 + github.com/stretchr/testify v1.7.0 + github.com/tidwall/gjson v1.9.3 + gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c +) diff --git a/botgo/go.sum b/botgo/go.sum new file mode 100644 index 00000000..4f951cd4 --- /dev/null +++ b/botgo/go.sum @@ -0,0 +1,127 @@ +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= +github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= +github.com/go-resty/resty/v2 v2.6.0 h1:joIR5PNLM2EFqqESUjCMGXrWmXNHEU9CEiK813oKYS4= +github.com/go-resty/resty/v2 v2.6.0/go.mod h1:PwvJS6hvaPkjtjNg9ph+VrSD92bi5Zq73w/BIH7cC3Q= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= +github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tidwall/gjson v1.9.3 h1:hqzS9wAHMO+KVBBkLxYdkEeeFHuqr95GfClRLKlgK0E= +github.com/tidwall/gjson v1.9.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/go.mod b/go.mod new file mode 100644 index 00000000..ae3a48a8 --- /dev/null +++ b/go.mod @@ -0,0 +1,46 @@ +module github.com/hoshinonyaruko/gensokyo + +go 1.21.1 + +require ( + github.com/boltdb/bolt v1.3.1 + github.com/gin-gonic/gin v1.9.1 + github.com/gorilla/websocket v1.4.2 + github.com/tencent-connect/botgo v0.1.6 + gopkg.in/ini.v1 v1.67.0 + gopkg.in/yaml.v3 v3.0.1 +) + +replace github.com/tencent-connect/botgo => ./botgo + +require ( + github.com/TheTitanrain/w32 v0.0.0-20180517000239-4f5cfb03fabf // indirect + github.com/bytedance/sonic v1.9.1 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect + github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.14.0 // indirect + github.com/go-resty/resty/v2 v2.6.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.4 // indirect + github.com/leodido/go-urn v1.2.4 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.0.8 // indirect + github.com/sqweek/dialog v0.0.0-20220809060634-e981b270ebbf // indirect + github.com/tidwall/gjson v1.9.3 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.11 // indirect + golang.org/x/arch v0.3.0 // indirect + golang.org/x/crypto v0.9.0 // indirect + golang.org/x/net v0.10.0 // indirect + golang.org/x/sys v0.8.0 // indirect + golang.org/x/text v0.9.0 // indirect + google.golang.org/protobuf v1.30.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..1bed39fa --- /dev/null +++ b/go.sum @@ -0,0 +1,192 @@ +github.com/TheTitanrain/w32 v0.0.0-20180517000239-4f5cfb03fabf h1:FPsprx82rdrX2jiKyS17BH6IrTmUBYqZa/CXT4uvb+I= +github.com/TheTitanrain/w32 v0.0.0-20180517000239-4f5cfb03fabf/go.mod h1:peYoMncQljjNS6tZwI9WVyQB3qZS6u79/N3mBOcnd3I= +github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4= +github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= +github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= +github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= +github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= +github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= +github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= +github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= +github.com/go-resty/resty/v2 v2.6.0 h1:joIR5PNLM2EFqqESUjCMGXrWmXNHEU9CEiK813oKYS4= +github.com/go-resty/resty/v2 v2.6.0/go.mod h1:PwvJS6hvaPkjtjNg9ph+VrSD92bi5Zq73w/BIH7cC3Q= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= +github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= +github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= +github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= +github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sqweek/dialog v0.0.0-20220809060634-e981b270ebbf h1:pCxn3BCfu8n8VUhYl4zS1BftoZoYY0J4qVF3dqAQ4aU= +github.com/sqweek/dialog v0.0.0-20220809060634-e981b270ebbf/go.mod h1:/qNPSY91qTz/8TgHEMioAUc6q7+3SOybeKczHMXFcXw= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/tidwall/gjson v1.9.3 h1:hqzS9wAHMO+KVBBkLxYdkEeeFHuqr95GfClRLKlgK0E= +github.com/tidwall/gjson v1.9.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= +golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= From ece4ceffd06b630fd8997002e0736db66d36c3a9 Mon Sep 17 00:00:00 2001 From: cosmo Date: Thu, 26 Oct 2023 13:13:17 +0800 Subject: [PATCH 10/27] add action --- readme.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/readme.md b/readme.md index 85b391ef..f0eb76a7 100644 --- a/readme.md +++ b/readme.md @@ -115,11 +115,11 @@ todo,正在施工中 | /send_private_msg√ | [发送私聊消息] | | /send_group_msg√ | [发送群消息] | | /send_guild_channel_msg√ | [发送频道消息] | -| /send_msg | [发送消息] | +| /send_msg√ | [发送消息] | | /delete_msg | [撤回信息] | | /set_group_kick | [群组踢人] | -| /set_group_ban | [群组单人禁言] | -| /set_group_whole_ban | [群组全员禁言] | +| /set_group_ban√ | [群组单人禁言] | +| /set_group_whole_ban√ | [群组全员禁言] | | /set_group_admin | [群组设置管理员] | | /set_group_card | [设置群名片(群备注)] | | /set_group_name | [设置群名] | @@ -127,17 +127,17 @@ todo,正在施工中 | /set_group_special_title | [设置群组专属头衔] | | /set_friend_add_request | [处理加好友请求] | | /set_group_add_request | [处理加群请求/邀请] | -| /get_login_info | [获取登录号信息] | +| /get_login_info√ | [获取登录号信息] | | /get_stranger_info | [获取陌生人信息] | -| /get_friend_list | [获取好友列表] | +| /get_friend_list√ | [获取好友列表] | | /get_group_info√ | [获取群/频道信息] | -| /get_group_list | [获取群列表] | -| /get_group_member_info | [获取群成员信息] | -| /get_group_member_list | [获取群成员列表] | +| /get_group_list√ | [获取群列表] | +| /get_group_member_info√ | [获取群成员信息] | +| /get_group_member_list√ | [获取群成员列表] | | /get_group_honor_info | [获取群荣誉信息] | -| /can_send_image | [检查是否可以发送图片] | +| /can_send_image√ | [检查是否可以发送图片] | | /can_send_record | [检查是否可以发送语音] | -| /get_version_info | [获取版本信息] | +| /get_version_info√ | [获取版本信息] | | /set_restart | [重启 gensokyo] | | /.handle_quick_operation | [对事件执行快速操作] | From f864a50c7134d2d065fcdbfbf0579e64f5f1baa6 Mon Sep 17 00:00:00 2001 From: cosmo Date: Thu, 26 Oct 2023 13:17:33 +0800 Subject: [PATCH 11/27] fixbug --- main.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/main.go b/main.go index 924bab40..b95be7a7 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package main import ( + "bufio" "context" "errors" "fmt" @@ -18,7 +19,6 @@ import ( "github.com/hoshinonyaruko/gensokyo/wsclient" "github.com/gin-gonic/gin" - "github.com/sqweek/dialog" "github.com/tencent-connect/botgo" "github.com/tencent-connect/botgo/dto" "github.com/tencent-connect/botgo/event" @@ -42,14 +42,15 @@ func NewProcessor(api openapi.OpenAPI, apiv2 openapi.OpenAPI, settings *config.S func main() { if _, err := os.Stat("config.yml"); os.IsNotExist(err) { - // 如果config.yml不存在 err = os.WriteFile("config.yml", []byte(configTemplate), 0644) if err != nil { fmt.Println("Error writing config.yml:", err) return } - dialog.Message("%s", "请配置config.yml然后再次运行.").Title("配置提示").Info() + fmt.Println("请配置config.yml然后再次运行.") + fmt.Print("按下 Enter 继续...") + bufio.NewReader(os.Stdin).ReadBytes('\n') os.Exit(0) } From 4564731703d3fa8d546b7e1f99ba484eb824ce77 Mon Sep 17 00:00:00 2001 From: cosmo Date: Thu, 26 Oct 2023 17:20:44 +0800 Subject: [PATCH 12/27] add wss --- Processor/ProcessC2CMessage.go | 18 ++++++ Processor/ProcessChannelDirectMessage.go | 35 ++++++++++++ Processor/ProcessGroupMessage.go | 17 ++++++ Processor/ProcessGuildATMessage.go | 35 ++++++++++++ Processor/ProcessGuildNormalMessage.go | 35 ++++++++++++ Processor/Processor.go | 1 + botgo/token/authtoken.go | 2 +- botgo/token/token.go | 2 +- config/config.go | 26 +++++++++ config_template.go | 2 + config_template.yml | 4 +- handlers/get_group_info.go | 20 ++++--- handlers/get_group_member_list.go | 73 ++++++++++++++++++++++-- handlers/send_group_msg_async.go | 9 +++ handlers/send_msg_async.go | 9 +++ handlers/send_private_msg_async.go | 9 +++ wsclient/ws.go | 8 +++ 17 files changed, 289 insertions(+), 16 deletions(-) create mode 100644 handlers/send_group_msg_async.go create mode 100644 handlers/send_msg_async.go create mode 100644 handlers/send_private_msg_async.go diff --git a/Processor/ProcessC2CMessage.go b/Processor/ProcessC2CMessage.go index 72eeda07..d5fcb301 100644 --- a/Processor/ProcessC2CMessage.go +++ b/Processor/ProcessC2CMessage.go @@ -135,6 +135,24 @@ func (p *Processor) ProcessC2CMessage(data *dto.WSC2CMessageData) error { Avatar: "", Echo: echostr, } + // 获取MasterID数组 + masterIDs := config.GetMasterID() + + // 判断userid64是否在masterIDs数组里 + isMaster := false + for _, id := range masterIDs { + if strconv.FormatInt(userid64, 10) == id { + isMaster = true + break + } + } + + // 根据isMaster的值为groupMsg的Sender赋值role字段 + if isMaster { + groupMsg.Sender.Role = "owner" + } else { + groupMsg.Sender.Role = "member" + } //将当前s和appid和message进行映射 echo.AddMsgID(AppIDString, s, data.ID) echo.AddMsgType(AppIDString, s, "group_private") diff --git a/Processor/ProcessChannelDirectMessage.go b/Processor/ProcessChannelDirectMessage.go index 51181ffd..ebf08932 100644 --- a/Processor/ProcessChannelDirectMessage.go +++ b/Processor/ProcessChannelDirectMessage.go @@ -142,7 +142,24 @@ func (p *Processor) ProcessChannelDirectMessage(data *dto.WSDirectMessageData) e Avatar: data.Author.Avatar, Echo: echostr, } + // 获取MasterID数组 + masterIDs := config.GetMasterID() + // 判断userid64是否在masterIDs数组里 + isMaster := false + for _, id := range masterIDs { + if strconv.FormatInt(userid64, 10) == id { + isMaster = true + break + } + } + + // 根据isMaster的值为groupMsg的Sender赋值role字段 + if isMaster { + onebotMsg.Sender.Role = "owner" + } else { + onebotMsg.Sender.Role = "member" + } //将当前s和appid和message进行映射 echo.AddMsgID(AppIDString, s, data.ID) //通过echo始终得知真实的事件类型,来对应调用正确的api @@ -217,6 +234,24 @@ func (p *Processor) ProcessChannelDirectMessage(data *dto.WSDirectMessageData) e Avatar: data.Author.Avatar, Echo: echostr, } + // 获取MasterID数组 + masterIDs := config.GetMasterID() + + // 判断userid64是否在masterIDs数组里 + isMaster := false + for _, id := range masterIDs { + if strconv.FormatInt(userid64, 10) == id { + isMaster = true + break + } + } + + // 根据isMaster的值为groupMsg的Sender赋值role字段 + if isMaster { + groupMsg.Sender.Role = "owner" + } else { + groupMsg.Sender.Role = "member" + } //将当前s和appid和message进行映射 echo.AddMsgID(AppIDString, s, data.ID) echo.AddMsgType(AppIDString, s, "guild_private") diff --git a/Processor/ProcessGroupMessage.go b/Processor/ProcessGroupMessage.go index 5610b493..c414ef35 100644 --- a/Processor/ProcessGroupMessage.go +++ b/Processor/ProcessGroupMessage.go @@ -75,7 +75,24 @@ func (p *Processor) ProcessGroupMessage(data *dto.WSGroupATMessageData) error { Avatar: "", Echo: echostr, } + // 获取MasterID数组 + masterIDs := config.GetMasterID() + // 判断userid64是否在masterIDs数组里 + isMaster := false + for _, id := range masterIDs { + if strconv.FormatInt(userid64, 10) == id { + isMaster = true + break + } + } + + // 根据isMaster的值为groupMsg的Sender赋值role字段 + if isMaster { + groupMsg.Sender.Role = "owner" + } else { + groupMsg.Sender.Role = "member" + } // 将当前s和appid和message进行映射 echo.AddMsgID(AppIDString, s, data.ID) echo.AddMsgType(AppIDString, s, "group") diff --git a/Processor/ProcessGuildATMessage.go b/Processor/ProcessGuildATMessage.go index c3de90c5..80e40c35 100644 --- a/Processor/ProcessGuildATMessage.go +++ b/Processor/ProcessGuildATMessage.go @@ -65,7 +65,24 @@ func (p *Processor) ProcessGuildATMessage(data *dto.WSATMessageData) error { Avatar: data.Author.Avatar, Echo: echostr, } + // 获取MasterID数组 + masterIDs := config.GetMasterID() + // 判断userid64是否在masterIDs数组里 + isMaster := false + for _, id := range masterIDs { + if strconv.FormatInt(userid64, 10) == id { + isMaster = true + break + } + } + + // 根据isMaster的值为groupMsg的Sender赋值role字段 + if isMaster { + onebotMsg.Sender.Role = "owner" + } else { + onebotMsg.Sender.Role = "member" + } //将当前s和appid和message进行映射 echo.AddMsgID(AppIDString, s, data.ID) echo.AddMsgType(AppIDString, s, "guild") @@ -143,6 +160,24 @@ func (p *Processor) ProcessGuildATMessage(data *dto.WSATMessageData) error { Avatar: data.Author.Avatar, Echo: echostr, } + // 获取MasterID数组 + masterIDs := config.GetMasterID() + + // 判断userid64是否在masterIDs数组里 + isMaster := false + for _, id := range masterIDs { + if strconv.FormatInt(userid64, 10) == id { + isMaster = true + break + } + } + + // 根据isMaster的值为groupMsg的Sender赋值role字段 + if isMaster { + groupMsg.Sender.Role = "owner" + } else { + groupMsg.Sender.Role = "member" + } //将当前s和appid和message进行映射 echo.AddMsgID(AppIDString, s, data.ID) echo.AddMsgType(AppIDString, s, "guild") diff --git a/Processor/ProcessGuildNormalMessage.go b/Processor/ProcessGuildNormalMessage.go index 931b02ac..011d96e9 100644 --- a/Processor/ProcessGuildNormalMessage.go +++ b/Processor/ProcessGuildNormalMessage.go @@ -64,7 +64,24 @@ func (p *Processor) ProcessGuildNormalMessage(data *dto.WSMessageData) error { Avatar: data.Author.Avatar, Echo: echostr, } + // 获取MasterID数组 + masterIDs := config.GetMasterID() + // 判断userid64是否在masterIDs数组里 + isMaster := false + for _, id := range masterIDs { + if strconv.FormatInt(userid64, 10) == id { + isMaster = true + break + } + } + + // 根据isMaster的值为groupMsg的Sender赋值role字段 + if isMaster { + onebotMsg.Sender.Role = "owner" + } else { + onebotMsg.Sender.Role = "member" + } //将当前s和appid和message进行映射 echo.AddMsgID(AppIDString, s, data.ID) echo.AddMsgType(AppIDString, s, "guild") @@ -142,6 +159,24 @@ func (p *Processor) ProcessGuildNormalMessage(data *dto.WSMessageData) error { Avatar: data.Author.Avatar, Echo: echostr, } + // 获取MasterID数组 + masterIDs := config.GetMasterID() + + // 判断userid64是否在masterIDs数组里 + isMaster := false + for _, id := range masterIDs { + if strconv.FormatInt(userid64, 10) == id { + isMaster = true + break + } + } + + // 根据isMaster的值为groupMsg的Sender赋值role字段 + if isMaster { + groupMsg.Sender.Role = "owner" + } else { + groupMsg.Sender.Role = "member" + } //将当前s和appid和message进行映射 echo.AddMsgID(AppIDString, s, data.ID) echo.AddMsgType(AppIDString, s, "guild") diff --git a/Processor/Processor.go b/Processor/Processor.go index 5e2064cc..ceada563 100644 --- a/Processor/Processor.go +++ b/Processor/Processor.go @@ -26,6 +26,7 @@ type Sender struct { Nickname string `json:"nickname"` TinyID string `json:"tiny_id"` UserID int64 `json:"user_id"` + Role string `json:"role,omitempty"` } // 频道信息事件 diff --git a/botgo/token/authtoken.go b/botgo/token/authtoken.go index 013aa77e..2c59a421 100644 --- a/botgo/token/authtoken.go +++ b/botgo/token/authtoken.go @@ -100,7 +100,7 @@ func (atoken *AuthTokenInfo) StartRefreshAccessToken(ctx context.Context, tokenU // func (atoken *AuthTokenInfo) StartRefreshAccessToken(ctx context.Context, tokenURL, appID, clientSecrent string) (err error) { // // 创建一个固定的token信息 // fixedTokenInfo := AccessTokenInfo{ -// Token: "PpAPgoel0-gTeaxy-ydak0kUKxJrCSlbLcwtuPt99jCPVrahkqh3WSiIy9s63tCZnTEp4asw035u", +// Token: "PyR4PL9_eRfAkIIlWE4nAawocFMlPfQCySgASB5vJRduWgKh0mSOp4zm4AOzDKpweV9iu5zq-OWm", // ExpiresIn: 3600, // 这里假设token的有效时间是3600秒,你可以根据需要调整 // } // atoken.setAuthToken(fixedTokenInfo) diff --git a/botgo/token/token.go b/botgo/token/token.go index 181312f0..b03a53b7 100644 --- a/botgo/token/token.go +++ b/botgo/token/token.go @@ -98,7 +98,7 @@ func (t *Token) GetAccessToken() string { // GetAccessToken 取得测试鉴权Token // func (t *Token) GetAccessToken() string { // // 固定的token值 -// return "PpAPgoel0-gTeaxy-ydak0kUKxJrCSlbLcwtuPt99jCPVrahkqh3WSiIy9s63tCZnTEp4asw035u" +// return "PyR4PL9_eRfAkIIlWE4nAawocFMlPfQCySgASB5vJRduWgKh0mSOp4zm4AOzDKpweV9iu5zq-OWm" // } // UpAccessToken 更新accessToken diff --git a/config/config.go b/config/config.go index 2d0f37a0..edbc57da 100644 --- a/config/config.go +++ b/config/config.go @@ -32,6 +32,12 @@ type Settings struct { Server_dir string `yaml:"server_dir"` Lotus bool `yaml:"lotus"` Port string `yaml:"port"` + + // 连接wss时使用,不是wss可留空 + WsToken string `yaml:"ws_token,omitempty"` + + // 如果需要在群权限判断是管理员是,将user_id填入这里,master_id是一个文本数组 + MasterID []string `yaml:"master_id,omitempty"` } // LoadConfig 从文件中加载配置并初始化单例配置 @@ -123,3 +129,23 @@ func GetAppID() uint64 { } return 0 // or whatever default value you'd like to return if instance is nil } + +// 获取WsToken +func GetWsToken() string { + mu.Lock() + defer mu.Unlock() + if instance != nil { + return instance.Settings.WsToken + } + return "" // 返回空字符串,如果instance为nil +} + +// 获取MasterID数组 +func GetMasterID() []string { + mu.Lock() + defer mu.Unlock() + if instance != nil { + return instance.Settings.MasterID + } + return nil // 返回nil,如果instance为nil +} diff --git a/config_template.go b/config_template.go index edb75ad5..96238c8a 100644 --- a/config_template.go +++ b/config_template.go @@ -31,4 +31,6 @@ settings: lotus: false # lotus特性默认为false,当为true时,将会连接到另一个lotus为false的gensokyo。 # 使用它提供的图床和idmaps服务(场景:同一个机器人在不同服务器运行,或内网需要发送base64图)。 # 如果需要发送base64图片,需要设置正确的公网server_dir和开放对应的port + ws_token: "" #连接wss地址时服务器所需的token,如果是ws,可留空 + master_id : ["1","2"] #群场景尚未开放获取管理员和列表能力,手动从日志中获取需要设置为管理,的user_id并填入(适用插件有权限判断场景) ` diff --git a/config_template.yml b/config_template.yml index dc534926..d06b677d 100644 --- a/config_template.yml +++ b/config_template.yml @@ -27,4 +27,6 @@ settings: port: "" # idmaps和图床对外开放的端口号 lotus: false # lotus特性默认为false,当为true时,将会连接到另一个lotus为false的gensokyo。 # 使用它提供的图床和idmaps服务(场景:同一个机器人在不同服务器运行,或内网需要发送base64图)。 - # 如果需要发送base64图片,需要设置正确的公网server_dir和开放对应的port \ No newline at end of file + # 如果需要发送base64图片,需要设置正确的公网server_dir和开放对应的port + ws_token: "" #连接wss地址时服务器所需的token,如果是ws,可留空 + master_id : ["1","2"] #群场景尚未开放获取管理员和列表能力,手动从日志中获取需要设置为管理,的user_id并填入(适用插件有权限判断场景) \ No newline at end of file diff --git a/handlers/get_group_info.go b/handlers/get_group_info.go index 21cc3028..4e89d21d 100644 --- a/handlers/get_group_info.go +++ b/handlers/get_group_info.go @@ -27,13 +27,12 @@ type OnebotGroupInfo struct { MaxMemberCount int32 `json:"max_member_count"` } -func ConvertGuildToGroupInfo(guild *dto.Guild) *OnebotGroupInfo { - groupID, err := strconv.ParseInt(guild.ID, 10, 64) +func ConvertGuildToGroupInfo(guild *dto.Guild, GroupId string) *OnebotGroupInfo { + groupidstr, err := strconv.ParseInt(GroupId, 10, 64) if err != nil { - log.Printf("转换ID失败: %v", err) + log.Printf("groupidstr: %v", err) return nil } - ts, err := guild.JoinedAt.Time() if err != nil { log.Printf("转换JoinedAt失败: %v", err) @@ -42,7 +41,7 @@ func ConvertGuildToGroupInfo(guild *dto.Guild) *OnebotGroupInfo { groupCreateTime := uint32(ts.Unix()) return &OnebotGroupInfo{ - GroupID: groupID, + GroupID: groupidstr, GroupName: guild.Name, GroupMemo: guild.Desc, GroupCreateTime: groupCreateTime, @@ -71,8 +70,15 @@ func handleGetGroupInfo(client callapi.Client, api openapi.OpenAPI, apiv2 openap log.Printf("获取频道信息失败: %v", err) return } - - groupInfo := ConvertGuildToGroupInfo(guild) + //用group_id还原出channelid 这是虚拟成群的私聊信息 + message.Params.ChannelID = message.Params.GroupID.(string) + //读取ini 通过ChannelID取回之前储存的guild_id + GroupId, err := idmap.ReadConfigv2(message.Params.ChannelID, "guild_id") + if err != nil { + log.Printf("Error reading config: %v", err) + return + } + groupInfo := ConvertGuildToGroupInfo(guild, GroupId) groupInfoMap := structToMap(groupInfo) diff --git a/handlers/get_group_member_list.go b/handlers/get_group_member_list.go index b3c71333..7117c708 100644 --- a/handlers/get_group_member_list.go +++ b/handlers/get_group_member_list.go @@ -11,6 +11,13 @@ import ( "github.com/tencent-connect/botgo/openapi" ) +type Response struct { + Retcode int `json:"retcode"` + Status string `json:"status"` + Data []MemberList `json:"data"` + Echo interface{} `json:"echo"` // 使用 interface{} 类型以容纳整数或文本 +} + // Member Onebot 群成员 type MemberList struct { UserID string `json:"user_id"` @@ -19,6 +26,7 @@ type MemberList struct { Role string `json:"role"` JoinTime string `json:"join_time"` LastSentTime string `json:"last_sent_time"` + Level string `json:"level,omitempty"` // 我添加了 Level 字段,因为你的示例中有它,但你可以删除它,如果不需要 } func init() { @@ -41,15 +49,29 @@ func getGroupMemberList(client callapi.Client, api openapi.OpenAPI, apiv2 openap log.Printf("getGroupMemberList(频道): 目前暂未适配私聊虚拟群场景获取虚拟群列表能力") return case "guild": + //要把group_id还原成guild_id + //用group_id还原出channelid 这是虚拟成群的私聊信息 + message.Params.ChannelID = message.Params.GroupID.(string) + //读取ini 通过ChannelID取回之前储存的guild_id + value, err := idmap.ReadConfigv2(message.Params.ChannelID, "guild_id") + if err != nil { + log.Printf("Error reading config: %v", err) + return + } pager := &dto.GuildMembersPager{ Limit: "400", } - membersFromAPI, err := api.GuildMembers(context.TODO(), message.Params.GroupID.(string), pager) + membersFromAPI, err := api.GuildMembers(context.TODO(), value, pager) if err != nil { - log.Printf("Failed to fetch group members for guild %s: %v", message.Params.GroupID.(string), err) + log.Printf("Failed to fetch group members for guild %s: %v", value, err) return } + // log.Println("Number of members in membersFromAPI:", len(membersFromAPI)) + // for i, member := range membersFromAPI { + // log.Printf("Member %d: %+v\n", i+1, *member) + // } + var members []MemberList for _, memberFromAPI := range membersFromAPI { joinedAtTime, err := memberFromAPI.JoinedAt.Time() @@ -81,12 +103,12 @@ func getGroupMemberList(client callapi.Client, api openapi.OpenAPI, apiv2 openap members = append(members, member) } - // Convert the APIOutput structure to a map[string]interface{} - outputMap := structToMap(members) + log.Printf("member message.Echors: %+v\n", message.Echo) - log.Printf("getGroupMemberList(频道): %+v\n", outputMap) + responseJSON := buildResponse(members, message.Echo) // assume echoValue is your echo data + log.Printf("getGroupMemberList(频道): %s\n", responseJSON) - err = client.SendMessage(outputMap) //发回去 + err = client.SendMessage(responseJSON) //发回去 if err != nil { log.Printf("Error sending message via client: %v", err) } @@ -94,3 +116,42 @@ func getGroupMemberList(client callapi.Client, api openapi.OpenAPI, apiv2 openap log.Printf("Unknown msgType: %s", msgType) } } + +func buildResponse(members []MemberList, echoValue interface{}) map[string]interface{} { + data := make([]map[string]interface{}, len(members)) + + for i, member := range members { + memberMap := map[string]interface{}{ + "user_id": member.UserID, + "group_id": member.GroupID, + "nickname": member.Nickname, + "role": member.Role, + "join_time": member.JoinTime, + "last_sent_time": member.LastSentTime, + } + data[i] = memberMap + } + + response := map[string]interface{}{ + "retcode": 0, + "status": "ok", + "data": data, + } + + // Set echo based on the type of echoValue + switch v := echoValue.(type) { + case int: + log.Printf("Setting echo as int: %d", v) + response["echo"] = v + case string: + log.Printf("Setting echo as string: %s", v) + response["echo"] = v + case callapi.EchoContent: + log.Printf("Setting echo from EchoContent: %s", string(v)) + response["echo"] = string(v) + default: + log.Printf("Unknown type for echo: %T", v) + } + + return response +} diff --git a/handlers/send_group_msg_async.go b/handlers/send_group_msg_async.go new file mode 100644 index 00000000..62112e23 --- /dev/null +++ b/handlers/send_group_msg_async.go @@ -0,0 +1,9 @@ +package handlers + +import ( + "github.com/hoshinonyaruko/gensokyo/callapi" +) + +func init() { + callapi.RegisterHandler("send_group_msg_async", handleSendGroupMsg) +} diff --git a/handlers/send_msg_async.go b/handlers/send_msg_async.go new file mode 100644 index 00000000..676e36b1 --- /dev/null +++ b/handlers/send_msg_async.go @@ -0,0 +1,9 @@ +package handlers + +import ( + "github.com/hoshinonyaruko/gensokyo/callapi" +) + +func init() { + callapi.RegisterHandler("send_msg_async", handleSendMsg) +} diff --git a/handlers/send_private_msg_async.go b/handlers/send_private_msg_async.go new file mode 100644 index 00000000..8c2c0f44 --- /dev/null +++ b/handlers/send_private_msg_async.go @@ -0,0 +1,9 @@ +package handlers + +import ( + "github.com/hoshinonyaruko/gensokyo/callapi" +) + +func init() { + callapi.RegisterHandler("send_private_msg_async", handleSendPrivateMsg) +} diff --git a/wsclient/ws.go b/wsclient/ws.go index 224259a0..4255b1a3 100644 --- a/wsclient/ws.go +++ b/wsclient/ws.go @@ -10,6 +10,7 @@ import ( "github.com/gorilla/websocket" "github.com/hoshinonyaruko/gensokyo/callapi" + "github.com/hoshinonyaruko/gensokyo/config" "github.com/tencent-connect/botgo/openapi" ) @@ -111,12 +112,19 @@ func (c *WebSocketClient) sendHeartbeat(ctx context.Context, botID uint64) { // NewWebSocketClient 创建 WebSocketClient 实例,接受 WebSocket URL、botID 和 openapi.OpenAPI 实例 func NewWebSocketClient(urlStr string, botID uint64, api openapi.OpenAPI, apiv2 openapi.OpenAPI) (*WebSocketClient, error) { + token := config.GetWsToken() // 从配置中获取 token + headers := http.Header{ "User-Agent": []string{"CQHttp/4.15.0"}, "X-Client-Role": []string{"Universal"}, "X-Self-ID": []string{fmt.Sprintf("%d", botID)}, } + // 如果 token 不为空,将其添加到 headers 中 + if token != "" { + headers["Authorization"] = []string{"Token " + token} + } + dialer := websocket.Dialer{ Proxy: http.ProxyFromEnvironment, HandshakeTimeout: 45 * time.Second, From 84e8fe94766c92446da8a6715791dba97cec7085 Mon Sep 17 00:00:00 2001 From: cosmo Date: Thu, 26 Oct 2023 19:03:14 +0800 Subject: [PATCH 13/27] bugfix --- botgo/token/authtoken.go | 43 ---------------------------------------- botgo/token/token.go | 5 ----- config/config.go | 22 -------------------- 3 files changed, 70 deletions(-) diff --git a/botgo/token/authtoken.go b/botgo/token/authtoken.go index 06543ea4..2c59a421 100644 --- a/botgo/token/authtoken.go +++ b/botgo/token/authtoken.go @@ -96,49 +96,6 @@ func (atoken *AuthTokenInfo) StartRefreshAccessToken(ctx context.Context, tokenU return } -// 测试用 -func (atoken *AuthTokenInfo) StartRefreshAccessToken(ctx context.Context, tokenURL, appID, clientSecrent string) (err error) { - // 首先,立即获取一次AccessToken - tokenInfo, err := queryAccessToken(ctx, tokenURL, appID, clientSecrent) - if err != nil { - return err - } - atoken.setAuthToken(tokenInfo) - fmt.Printf("获取到的token是: %s\n", tokenInfo.Token) // 输出获取到的token - - // 获取token的有效期(通常以秒为单位) - tokenTTL := tokenInfo.ExpiresIn - // 使用sync.Once保证仅启动一个goroutine进行定时刷新 - atoken.once.Do(func() { - go func() { // 启动一个新的goroutine - for { - // 如果tokenTTL为0或负数,将其设置为1 - if tokenTTL <= 0 { - tokenTTL = 1 - } - select { - case <-time.NewTimer(time.Duration(tokenTTL) * time.Second).C: // 当token过期时 - case upToken := <-atoken.forceUpToken: // 接收强制更新token的信号 - log.Warnf("recv uptoken info:%v", upToken) - case <-ctx.Done(): // 当上下文结束时,退出goroutine - log.Warnf("recv ctx:%v exit refreshAccessToken", ctx.Err()) - return - } - // 查询并获取新的AccessToken - tokenInfo, err := queryAccessToken(ctx, tokenURL, appID, clientSecrent) - if err == nil { - atoken.setAuthToken(tokenInfo) - fmt.Printf("获取到的token是: %s\n", tokenInfo.Token) // 输出获取到的token - tokenTTL = tokenInfo.ExpiresIn - } else { - log.Errorf("queryAccessToken err:%v", err) - } - } - }() - }) - return -} - // 测试用 // func (atoken *AuthTokenInfo) StartRefreshAccessToken(ctx context.Context, tokenURL, appID, clientSecrent string) (err error) { // // 创建一个固定的token信息 diff --git a/botgo/token/token.go b/botgo/token/token.go index 489b942d..b03a53b7 100644 --- a/botgo/token/token.go +++ b/botgo/token/token.go @@ -95,11 +95,6 @@ func (t *Token) GetAccessToken() string { return t.authToken.getAuthToken().Token } -// GetAccessToken 取得测试鉴权Token -func (t *Token) GetAccessToken() string { - return t.authToken.getAuthToken().Token -} - // GetAccessToken 取得测试鉴权Token // func (t *Token) GetAccessToken() string { // // 固定的token值 diff --git a/config/config.go b/config/config.go index ffe1f0b0..edbc57da 100644 --- a/config/config.go +++ b/config/config.go @@ -149,25 +149,3 @@ func GetMasterID() []string { } return nil // 返回nil,如果instance为nil } - -// 获取Array的值 -func GetArrayValue() bool { - mu.Lock() - defer mu.Unlock() - - if instance == nil { - log.Println("Warning: instance is nil when trying to get array value.") - return false - } - return instance.Settings.Array -} - -// 获取AppID -func GetAppID() uint64 { - mu.Lock() - defer mu.Unlock() - if instance != nil { - return instance.Settings.AppID - } - return 0 // or whatever default value you'd like to return if instance is nil -} From 0e4fd50890c5b0e441c6ec518cf76764f86dda03 Mon Sep 17 00:00:00 2001 From: cosmo Date: Thu, 26 Oct 2023 19:10:10 +0800 Subject: [PATCH 14/27] fix action --- .github/workflows/cross_compile.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/cross_compile.yml b/.github/workflows/cross_compile.yml index a4e4873b..4796a506 100644 --- a/.github/workflows/cross_compile.yml +++ b/.github/workflows/cross_compile.yml @@ -58,6 +58,7 @@ jobs: env: GOOS: ${{ matrix.os }} GOARCH: ${{ matrix.goarch }} + CGO_ENABLED: ${{ matrix.os == 'android' || matrix.goarch == 'arm' || matrix.goarch == 'arm64' ? '0' : '1' }} run: | go build -o output/gensokyo-${{ matrix.os }}-${{ matrix.goarch }} From fe2234a7360eef05068b0b1a5e98afc2c72aaf38 Mon Sep 17 00:00:00 2001 From: cosmo Date: Thu, 26 Oct 2023 19:13:52 +0800 Subject: [PATCH 15/27] fix action again --- .github/workflows/cross_compile.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cross_compile.yml b/.github/workflows/cross_compile.yml index 4796a506..a38d506f 100644 --- a/.github/workflows/cross_compile.yml +++ b/.github/workflows/cross_compile.yml @@ -58,8 +58,11 @@ jobs: env: GOOS: ${{ matrix.os }} GOARCH: ${{ matrix.goarch }} - CGO_ENABLED: ${{ matrix.os == 'android' || matrix.goarch == 'arm' || matrix.goarch == 'arm64' ? '0' : '1' }} + CGO_ENABLED: 1 run: | + if [[ "${{ matrix.os }}" == "android" || "${{ matrix.goarch }}" == "arm" || "${{ matrix.goarch }}" == "arm64" ]]; then + export CGO_ENABLED=0 + fi go build -o output/gensokyo-${{ matrix.os }}-${{ matrix.goarch }} - name: Upload artifacts From 087583758c8d8f774798dc57c6a5e5308cec36f5 Mon Sep 17 00:00:00 2001 From: cosmo Date: Thu, 26 Oct 2023 19:18:58 +0800 Subject: [PATCH 16/27] fa --- .github/workflows/cross_compile.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/cross_compile.yml b/.github/workflows/cross_compile.yml index a38d506f..cd926c66 100644 --- a/.github/workflows/cross_compile.yml +++ b/.github/workflows/cross_compile.yml @@ -58,11 +58,8 @@ jobs: env: GOOS: ${{ matrix.os }} GOARCH: ${{ matrix.goarch }} - CGO_ENABLED: 1 + CGO_ENABLED: 0 run: | - if [[ "${{ matrix.os }}" == "android" || "${{ matrix.goarch }}" == "arm" || "${{ matrix.goarch }}" == "arm64" ]]; then - export CGO_ENABLED=0 - fi go build -o output/gensokyo-${{ matrix.os }}-${{ matrix.goarch }} - name: Upload artifacts From 3a3c33f39eec8c283769612a6843b0b1f647bd5b Mon Sep 17 00:00:00 2001 From: cosmo Date: Thu, 26 Oct 2023 19:23:37 +0800 Subject: [PATCH 17/27] fix --- .github/workflows/cross_compile.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/cross_compile.yml b/.github/workflows/cross_compile.yml index cd926c66..564015e1 100644 --- a/.github/workflows/cross_compile.yml +++ b/.github/workflows/cross_compile.yml @@ -27,8 +27,6 @@ jobs: goarch: amd64 - os: windows goarch: 386 - - os: android - goarch: arm - os: android goarch: arm64 # ... Add other combinations as needed From 8789fa88761cb75d95409963d760884a9d741ace Mon Sep 17 00:00:00 2001 From: cosmo Date: Fri, 27 Oct 2023 19:09:01 +0800 Subject: [PATCH 18/27] add a lot --- Processor/ProcessC2CMessage.go | 40 +++++++--- Processor/ProcessChannelDirectMessage.go | 58 +++++++++++---- Processor/ProcessGroupMessage.go | 21 ++++-- Processor/ProcessGuildATMessage.go | 43 ++++++++--- Processor/ProcessGuildNormalMessage.go | 43 ++++++++--- Processor/Processor.go | 22 ++++-- callapi/callapi.go | 4 +- config/config.go | 44 ++++++++--- config_template.go | 19 +++-- config_template.yml | 5 +- handlers/get_status.go | 78 ++++++++++++++++++++ handlers/message_parser.go | 78 +++++++++++++++++++- handlers/send_group_msg.go | 14 +++- handlers/send_guild_channel_msg.go | 18 +++-- handlers/send_msg.go | 23 ++++-- handlers/send_private_msg.go | 15 +++- main.go | 94 ++++++++++++------------ server/uploadpic.go | 2 +- server/wsserver.go | 89 ++++++++++++++++++++++ wsclient/ws.go | 58 ++++++++++----- 20 files changed, 604 insertions(+), 164 deletions(-) create mode 100644 handlers/get_status.go create mode 100644 server/wsserver.go diff --git a/Processor/ProcessC2CMessage.go b/Processor/ProcessC2CMessage.go index d5fcb301..50f097e3 100644 --- a/Processor/ProcessC2CMessage.go +++ b/Processor/ProcessC2CMessage.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "strconv" + "strings" "time" "github.com/hoshinonyaruko/gensokyo/config" @@ -16,7 +17,7 @@ import ( ) // ProcessC2CMessage 处理C2C消息 群私聊 -func (p *Processor) ProcessC2CMessage(data *dto.WSC2CMessageData) error { +func (p *Processors) ProcessC2CMessage(data *dto.WSC2CMessageData) error { // 打印data结构体 PrintStructWithFieldNames(data) @@ -89,15 +90,26 @@ func (p *Processor) ProcessC2CMessage(data *dto.WSC2CMessageData) error { // Convert OnebotGroupMessage to map and send privateMsgMap := structToMap(privateMsg) - err = p.Wsclient.SendMessage(privateMsgMap) - if err != nil { - return fmt.Errorf("error sending group message via wsclient: %v", err) + var errors []string + + for _, client := range p.Wsclient { + err = client.SendMessage(privateMsgMap) + if err != nil { + // 记录错误信息,但不立即返回 + errors = append(errors, fmt.Sprintf("error sending private message via wsclient: %v", err)) + } + } + + // 在循环结束后处理记录的错误 + if len(errors) > 0 { + // 使用strings.Join合并所有的错误信息 + return fmt.Errorf(strings.Join(errors, "; ")) } } else { //将私聊信息转化为群信息(特殊需求情况下) //转换at - messageText := handlers.RevertTransformedText(data.Content) + messageText := handlers.RevertTransformedText(data) //转换appid AppIDString := strconv.FormatUint(p.Settings.AppID, 10) //构造echo @@ -165,11 +177,21 @@ func (p *Processor) ProcessC2CMessage(data *dto.WSC2CMessageData) error { // Convert OnebotGroupMessage to map and send groupMsgMap := structToMap(groupMsg) - err = p.Wsclient.SendMessage(groupMsgMap) - if err != nil { - return fmt.Errorf("error sending group message via wsclient: %v", err) + var errors []string + + for _, client := range p.Wsclient { + err = client.SendMessage(groupMsgMap) + if err != nil { + // 记录错误信息,但不立即返回 + errors = append(errors, fmt.Sprintf("error sending group message via wsclient: %v", err)) + } } - } + // 在循环结束后处理记录的错误 + if len(errors) > 0 { + // 使用strings.Join合并所有的错误信息 + log.Println("Encountered errors while sending to wsclients:", strings.Join(errors, "; ")) + } + } return nil } diff --git a/Processor/ProcessChannelDirectMessage.go b/Processor/ProcessChannelDirectMessage.go index ebf08932..52b31940 100644 --- a/Processor/ProcessChannelDirectMessage.go +++ b/Processor/ProcessChannelDirectMessage.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "strconv" + "strings" "time" "github.com/hoshinonyaruko/gensokyo/config" @@ -17,7 +18,7 @@ import ( ) // ProcessChannelDirectMessage 处理频道私信消息 这里我们是被动收到 -func (p *Processor) ProcessChannelDirectMessage(data *dto.WSDirectMessageData) error { +func (p *Processors) ProcessChannelDirectMessage(data *dto.WSDirectMessageData) error { // 打印data结构体 //PrintStructWithFieldNames(data) @@ -93,9 +94,20 @@ func (p *Processor) ProcessChannelDirectMessage(data *dto.WSDirectMessageData) e // Convert OnebotGroupMessage to map and send privateMsgMap := structToMap(privateMsg) - err = p.Wsclient.SendMessage(privateMsgMap) - if err != nil { - return fmt.Errorf("error sending group message via wsclient: %v", err) + var errors []string + + for _, client := range p.Wsclient { + err = client.SendMessage(privateMsgMap) + if err != nil { + // 记录错误信息,但不立即返回 + errors = append(errors, fmt.Sprintf("error sending private message via wsclient: %v", err)) + } + } + + // 在循环结束后处理记录的错误 + if len(errors) > 0 { + // 使用strings.Join合并所有的错误信息 + return fmt.Errorf(strings.Join(errors, "; ")) } } else { if !p.Settings.GlobalChannelToGroup { @@ -109,7 +121,7 @@ func (p *Processor) ProcessChannelDirectMessage(data *dto.WSDirectMessageData) e //获取s s := client.GetGlobalS() //转换at - messageText := handlers.RevertTransformedText(data.Content) + messageText := handlers.RevertTransformedText(data) //转换appid AppIDString := strconv.FormatUint(p.Settings.AppID, 10) //构造echo @@ -175,18 +187,27 @@ func (p *Processor) ProcessChannelDirectMessage(data *dto.WSDirectMessageData) e // 将 onebotMsg 结构体转换为 map[string]interface{} msgMap := structToMap(onebotMsg) + var errors []string - // 使用 wsclient 发送消息 - err = p.Wsclient.SendMessage(msgMap) - if err != nil { - return fmt.Errorf("error sending message via wsclient: %v", err) + for _, client := range p.Wsclient { + err = client.SendMessage(msgMap) + if err != nil { + // 记录错误信息,但不立即返回 + errors = append(errors, fmt.Sprintf("error sending message via wsclient: %v", err)) + } + } + + // 在循环结束后处理记录的错误 + if len(errors) > 0 { + // 使用strings.Join合并所有的错误信息 + return fmt.Errorf(strings.Join(errors, "; ")) } } else { //将频道信息转化为群信息(特殊需求情况下) //将channelid写入ini,可取出guild_id idmap.WriteConfigv2(data.ChannelID, "guild_id", data.GuildID) //转换at - messageText := handlers.RevertTransformedText(data.Content) + messageText := handlers.RevertTransformedText(data) //转换appid AppIDString := strconv.FormatUint(p.Settings.AppID, 10) //构造echo @@ -266,9 +287,20 @@ func (p *Processor) ProcessChannelDirectMessage(data *dto.WSDirectMessageData) e // Convert OnebotGroupMessage to map and send groupMsgMap := structToMap(groupMsg) - err = p.Wsclient.SendMessage(groupMsgMap) - if err != nil { - return fmt.Errorf("error sending group message via wsclient: %v", err) + var errors []string + + for _, client := range p.Wsclient { + err = client.SendMessage(groupMsgMap) + if err != nil { + // 记录错误信息,但不立即返回 + errors = append(errors, fmt.Sprintf("error sending group message via wsclient: %v", err)) + } + } + + // 在循环结束后处理记录的错误 + if len(errors) > 0 { + // 使用strings.Join合并所有的错误信息 + return fmt.Errorf(strings.Join(errors, "; ")) } } diff --git a/Processor/ProcessGroupMessage.go b/Processor/ProcessGroupMessage.go index c414ef35..bb057694 100644 --- a/Processor/ProcessGroupMessage.go +++ b/Processor/ProcessGroupMessage.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "strconv" + "strings" "time" "github.com/hoshinonyaruko/gensokyo/config" @@ -17,14 +18,14 @@ import ( ) // ProcessGroupMessage 处理群组消息 -func (p *Processor) ProcessGroupMessage(data *dto.WSGroupATMessageData) error { +func (p *Processors) ProcessGroupMessage(data *dto.WSGroupATMessageData) error { // 获取s s := client.GetGlobalS() idmap.WriteConfigv2(data.ChannelID, "guild_id", data.GuildID) // 转换at - messageText := handlers.RevertTransformedText(data.Content) + messageText := handlers.RevertTransformedText(data) // 转换appid AppIDString := strconv.FormatUint(p.Settings.AppID, 10) @@ -107,10 +108,20 @@ func (p *Processor) ProcessGroupMessage(data *dto.WSGroupATMessageData) error { // Convert OnebotGroupMessage to map and send groupMsgMap := structToMap(groupMsg) - err = p.Wsclient.SendMessage(groupMsgMap) - if err != nil { - return fmt.Errorf("error sending group message via wsclient: %v", err) + var errors []string + + for _, client := range p.Wsclient { + err = client.SendMessage(groupMsgMap) + if err != nil { + // 记录错误信息,但不立即返回 + errors = append(errors, fmt.Sprintf("error sending group message via wsclient: %v", err)) + } } + // 在循环结束后处理记录的错误 + if len(errors) > 0 { + // 使用strings.Join合并所有的错误信息 + return fmt.Errorf(strings.Join(errors, "; ")) + } return nil } diff --git a/Processor/ProcessGuildATMessage.go b/Processor/ProcessGuildATMessage.go index 80e40c35..ea4e5998 100644 --- a/Processor/ProcessGuildATMessage.go +++ b/Processor/ProcessGuildATMessage.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "strconv" + "strings" "time" "github.com/hoshinonyaruko/gensokyo/config" @@ -17,7 +18,7 @@ import ( ) // ProcessGuildATMessage 处理消息,执行逻辑并可能使用 api 发送响应 -func (p *Processor) ProcessGuildATMessage(data *dto.WSATMessageData) error { +func (p *Processors) ProcessGuildATMessage(data *dto.WSATMessageData) error { if !p.Settings.GlobalChannelToGroup { // 将时间字符串转换为时间戳 t, err := time.Parse(time.RFC3339, string(data.Timestamp)) @@ -27,7 +28,7 @@ func (p *Processor) ProcessGuildATMessage(data *dto.WSATMessageData) error { //获取s s := client.GetGlobalS() //转换at - messageText := handlers.RevertTransformedText(data.Content) + messageText := handlers.RevertTransformedText(data) //转换appid AppIDString := strconv.FormatUint(p.Settings.AppID, 10) //构造echo @@ -98,12 +99,21 @@ func (p *Processor) ProcessGuildATMessage(data *dto.WSATMessageData) error { // 将 onebotMsg 结构体转换为 map[string]interface{} msgMap := structToMap(onebotMsg) - // 使用 wsclient 发送消息 - err = p.Wsclient.SendMessage(msgMap) - if err != nil { - return fmt.Errorf("error sending message via wsclient: %v", err) + var errors []string + + for _, client := range p.Wsclient { + err = client.SendMessage(msgMap) + if err != nil { + // 记录错误信息,但不立即返回 + errors = append(errors, fmt.Sprintf("error sending message via wsclient: %v", err)) + } } + // 在循环结束后处理记录的错误 + if len(errors) > 0 { + // 使用strings.Join合并所有的错误信息 + return fmt.Errorf(strings.Join(errors, "; ")) + } } else { // GlobalChannelToGroup为true时的处理逻辑 //将频道转化为一个群 @@ -111,8 +121,8 @@ func (p *Processor) ProcessGuildATMessage(data *dto.WSATMessageData) error { s := client.GetGlobalS() //将channelid写入ini,可取出guild_id todo 比ini更好的储存方式 idmap.WriteConfigv2(data.ChannelID, "guild_id", data.GuildID) - //转换at - messageText := handlers.RevertTransformedText(data.Content) + //转换at和图片 + messageText := handlers.RevertTransformedText(data) //转换appid AppIDString := strconv.FormatUint(p.Settings.AppID, 10) //构造echo @@ -192,9 +202,20 @@ func (p *Processor) ProcessGuildATMessage(data *dto.WSATMessageData) error { // Convert OnebotGroupMessage to map and send groupMsgMap := structToMap(groupMsg) - err = p.Wsclient.SendMessage(groupMsgMap) - if err != nil { - return fmt.Errorf("error sending group message via wsclient: %v", err) + var errors []string + + for _, client := range p.Wsclient { + err = client.SendMessage(groupMsgMap) + if err != nil { + // 记录错误信息,但不立即返回 + errors = append(errors, fmt.Sprintf("error sending group message via wsclient: %v", err)) + } + } + + // 在循环结束后处理记录的错误 + if len(errors) > 0 { + // 使用strings.Join合并所有的错误信息 + return fmt.Errorf(strings.Join(errors, "; ")) } } diff --git a/Processor/ProcessGuildNormalMessage.go b/Processor/ProcessGuildNormalMessage.go index 011d96e9..5eeb84a2 100644 --- a/Processor/ProcessGuildNormalMessage.go +++ b/Processor/ProcessGuildNormalMessage.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "strconv" + "strings" "time" "github.com/hoshinonyaruko/gensokyo/config" @@ -16,7 +17,7 @@ import ( ) // ProcessGuildNormalMessage 处理频道常规消息 -func (p *Processor) ProcessGuildNormalMessage(data *dto.WSMessageData) error { +func (p *Processors) ProcessGuildNormalMessage(data *dto.WSMessageData) error { if !p.Settings.GlobalChannelToGroup { // 将时间字符串转换为时间戳 t, err := time.Parse(time.RFC3339, string(data.Timestamp)) @@ -26,7 +27,7 @@ func (p *Processor) ProcessGuildNormalMessage(data *dto.WSMessageData) error { //获取s s := client.GetGlobalS() //转换at - messageText := handlers.RevertTransformedText(data.Content) + messageText := handlers.RevertTransformedText(data) //转换appid AppIDString := strconv.FormatUint(p.Settings.AppID, 10) //构造echo @@ -97,12 +98,22 @@ func (p *Processor) ProcessGuildNormalMessage(data *dto.WSMessageData) error { // 将 onebotMsg 结构体转换为 map[string]interface{} msgMap := structToMap(onebotMsg) - // 使用 wsclient 发送消息 - err = p.Wsclient.SendMessage(msgMap) - if err != nil { - return fmt.Errorf("error sending message via wsclient: %v", err) + var errors []string + + // 遍历每一个 wsclient 并发送消息 + for _, client := range p.Wsclient { + err = client.SendMessage(msgMap) + if err != nil { + // 记录错误但不立即返回 + errors = append(errors, fmt.Sprintf("error sending message via wsclient: %v", err)) + } } + // 在循环结束后处理记录的错误 + if len(errors) > 0 { + // 使用strings.Join来合并所有的错误信息 + return fmt.Errorf(strings.Join(errors, "; ")) + } } else { // GlobalChannelToGroup为true时的处理逻辑 //将频道转化为一个群 @@ -111,7 +122,7 @@ func (p *Processor) ProcessGuildNormalMessage(data *dto.WSMessageData) error { //将channelid写入ini,可取出guild_id todo 比ini更好的储存方式 idmap.WriteConfigv2(data.ChannelID, "guild_id", data.GuildID) //转换at - messageText := handlers.RevertTransformedText(data.Content) + messageText := handlers.RevertTransformedText(data) //转换appid AppIDString := strconv.FormatUint(p.Settings.AppID, 10) //构造echo @@ -191,11 +202,23 @@ func (p *Processor) ProcessGuildNormalMessage(data *dto.WSMessageData) error { // Convert OnebotGroupMessage to map and send groupMsgMap := structToMap(groupMsg) - err = p.Wsclient.SendMessage(groupMsgMap) - if err != nil { - return fmt.Errorf("error sending group message via wsclient: %v", err) + + var errors []string + + // 遍历每一个 wsclient 并发送消息 + for _, client := range p.Wsclient { + err = client.SendMessage(groupMsgMap) + if err != nil { + // 记录错误但不立即返回 + errors = append(errors, fmt.Sprintf("error sending group message via wsclient: %v", err)) + } } + // 在循环结束后处理记录的错误 + if len(errors) > 0 { + // 使用strings.Join来合并所有的错误信息 + return fmt.Errorf(strings.Join(errors, "; ")) + } } return nil diff --git a/Processor/Processor.go b/Processor/Processor.go index ceada563..b0b91915 100644 --- a/Processor/Processor.go +++ b/Processor/Processor.go @@ -15,11 +15,11 @@ import ( ) // Processor 结构体用于处理消息 -type Processor struct { - Api openapi.OpenAPI // API 类型 - Apiv2 openapi.OpenAPI //群的API - Settings *config.Settings // 使用指针 - Wsclient *wsclient.WebSocketClient // 使用指针 +type Processors struct { + Api openapi.OpenAPI // API 类型 + Apiv2 openapi.OpenAPI //群的API + Settings *config.Settings // 使用指针 + Wsclient []*wsclient.WebSocketClient // 指针的切片 } type Sender struct { @@ -95,7 +95,7 @@ func FoxTimestamp() int64 { } // ProcessInlineSearch 处理内联查询 -func (p *Processor) ProcessInlineSearch(data *dto.WSInteractionData) error { +func (p *Processors) ProcessInlineSearch(data *dto.WSInteractionData) error { //ctx := context.Background() // 或从更高级别传递一个上下文 // 在这里处理内联查询 @@ -173,3 +173,13 @@ func structToMap(obj interface{}) map[string]interface{} { json.Unmarshal(j, &out) return out } + +// 修改函数的返回类型为 *Processor +func NewProcessor(api openapi.OpenAPI, apiv2 openapi.OpenAPI, settings *config.Settings, wsclient []*wsclient.WebSocketClient) *Processors { + return &Processors{ + Api: api, + Apiv2: apiv2, + Settings: settings, + Wsclient: wsclient, + } +} diff --git a/callapi/callapi.go b/callapi/callapi.go index 51116fd4..ec9221f1 100644 --- a/callapi/callapi.go +++ b/callapi/callapi.go @@ -111,11 +111,9 @@ type Message struct { Echo interface{} `json:"echo,omitempty"` } -// 这是一个接口,在wsclient传入client但不需要引用wsclient包,避免循环引用 +// 这是一个接口,在wsclient传入client但不需要引用wsclient包,避免循环引用,复用wsserver和client逻辑 type Client interface { SendMessage(message map[string]interface{}) error - GetAppID() uint64 - GetAppIDStr() string } // 根据action订阅handler处理api diff --git a/config/config.go b/config/config.go index edbc57da..2e27f98c 100644 --- a/config/config.go +++ b/config/config.go @@ -3,6 +3,7 @@ package config import ( + "fmt" "log" "os" "sync" @@ -21,7 +22,7 @@ type Config struct { } type Settings struct { - WsAddress string `yaml:"ws_address"` + WsAddress []string `yaml:"ws_address"` AppID uint64 `yaml:"app_id"` Token string `yaml:"token"` ClientSecret string `yaml:"client_secret"` @@ -32,12 +33,9 @@ type Settings struct { Server_dir string `yaml:"server_dir"` Lotus bool `yaml:"lotus"` Port string `yaml:"port"` - - // 连接wss时使用,不是wss可留空 - WsToken string `yaml:"ws_token,omitempty"` - - // 如果需要在群权限判断是管理员是,将user_id填入这里,master_id是一个文本数组 - MasterID []string `yaml:"master_id,omitempty"` + WsToken []string `yaml:"ws_token,omitempty"` // 连接wss时使用,不是wss可留空 一一对应 + MasterID []string `yaml:"master_id,omitempty"` // 如果需要在群权限判断是管理员是,将user_id填入这里,master_id是一个文本数组 + EnableWsServer bool `yaml:"enable_ws_server,omitempty"` //正向ws开关 } // LoadConfig 从文件中加载配置并初始化单例配置 @@ -62,14 +60,14 @@ func LoadConfig(path string) (*Config, error) { return conf, nil } -// 获取ws地址 -func GetWsAddress() string { +// 获取ws地址数组 +func GetWsAddress() []string { mu.Lock() defer mu.Unlock() if instance != nil { return instance.Settings.WsAddress } - return "" + return nil // 返回nil,如果instance为nil } // 获取gensokyo服务的地址 @@ -130,14 +128,24 @@ func GetAppID() uint64 { return 0 // or whatever default value you'd like to return if instance is nil } +// 获取AppID String +func GetAppIDStr() string { + mu.Lock() + defer mu.Unlock() + if instance != nil { + return fmt.Sprintf("%d", instance.Settings.AppID) + } + return "0" +} + // 获取WsToken -func GetWsToken() string { +func GetWsToken() []string { mu.Lock() defer mu.Unlock() if instance != nil { return instance.Settings.WsToken } - return "" // 返回空字符串,如果instance为nil + return nil // 返回nil,如果instance为nil } // 获取MasterID数组 @@ -149,3 +157,15 @@ func GetMasterID() []string { } return nil // 返回nil,如果instance为nil } + +// 获取port的值 +func GetEnableWsServer() bool { + mu.Lock() + defer mu.Unlock() + + if instance == nil { + log.Println("Warning: instance is nil when trying to get port value.") + return false + } + return instance.Settings.EnableWsServer +} diff --git a/config_template.go b/config_template.go index 96238c8a..6200655e 100644 --- a/config_template.go +++ b/config_template.go @@ -3,7 +3,7 @@ package main const configTemplate = ` version: 1 settings: - ws_address: "ws://:" # WebSocket服务的地址 + ws_address: ["ws://:"] # WebSocket服务的地址 支持多个["","",""] app_id: # 你的应用ID token: "" # 你的应用令牌 client_secret: "" # 你的客户端密钥 @@ -16,21 +16,28 @@ settings: # - "GuildEventHandler" # 频道事件 # - "MemberEventHandler" # 频道成员新增 # - "ChannelEventHandler" # 频道事件 - # - "CreateMessageHandler" # 频道不at信息 + # - "CreateMessageHandler" # 频道不at信息 私域机器人需要开启 公域机器人开启会连接失败 # - "InteractionHandler" # 添加频道互动回应 # - "GroupATMessageEventHandler" # 群at信息 仅频道机器人时候需要注释 # - "C2CMessageEventHandler" # 群私聊 仅频道机器人时候需要注释 # - "ThreadEventHandler" # 发帖事件 (当前版本已禁用) - global_channel_to_group: false # 是否将频道转换成群 - global_private_to_channel: false # 是否将私聊转换成频道 + + global_channel_to_group: true # 是否将频道转换成群 默认true + global_private_to_channel: false # 是否将私聊转换成频道 如果是群场景 会将私聊转为群(方便提审\测试) array: false + server_dir: "" # 提供图片上传服务的服务器(图床)需要带端口号. 如果需要发base64图,需为公网ip,且开放对应端口 - port: "" # idmaps和图床对外开放的端口号 + port: "" # idmaps和图床对外开放的端口号 + + lotus: false # lotus特性默认为false,当为true时,将会连接到另一个lotus为false的gensokyo。 # 使用它提供的图床和idmaps服务(场景:同一个机器人在不同服务器运行,或内网需要发送base64图)。 # 如果需要发送base64图片,需要设置正确的公网server_dir和开放对应的port - ws_token: "" #连接wss地址时服务器所需的token,如果是ws,可留空 + + + ws_token: ["","",""] #连接wss地址时服务器所需的token,如果是ws,可留空,按顺序一一对应 master_id : ["1","2"] #群场景尚未开放获取管理员和列表能力,手动从日志中获取需要设置为管理,的user_id并填入(适用插件有权限判断场景) + enable_ws_server: true #是否启用正向ws服务器 监听server_dir:port/ws ` diff --git a/config_template.yml b/config_template.yml index d06b677d..6ea4cb5c 100644 --- a/config_template.yml +++ b/config_template.yml @@ -28,5 +28,6 @@ settings: lotus: false # lotus特性默认为false,当为true时,将会连接到另一个lotus为false的gensokyo。 # 使用它提供的图床和idmaps服务(场景:同一个机器人在不同服务器运行,或内网需要发送base64图)。 # 如果需要发送base64图片,需要设置正确的公网server_dir和开放对应的port - ws_token: "" #连接wss地址时服务器所需的token,如果是ws,可留空 - master_id : ["1","2"] #群场景尚未开放获取管理员和列表能力,手动从日志中获取需要设置为管理,的user_id并填入(适用插件有权限判断场景) \ No newline at end of file + ws_token: ["",""] #连接wss地址时服务器所需的token,如果是ws,可留空,按顺序一一对应 + master_id : ["1","2"] #群场景尚未开放获取管理员和列表能力,手动从日志中获取需要设置为管理,的user_id并填入(适用插件有权限判断场景) + enable_ws_server: true #是否启用正向ws服务器 监听server_dir:port/ws \ No newline at end of file diff --git a/handlers/get_status.go b/handlers/get_status.go new file mode 100644 index 00000000..fc74ace7 --- /dev/null +++ b/handlers/get_status.go @@ -0,0 +1,78 @@ +package handlers + +import ( + "log" + + "github.com/hoshinonyaruko/gensokyo/callapi" + "github.com/tencent-connect/botgo/openapi" +) + +type GetStatusResponse struct { + Data StatusData `json:"data"` + Message string `json:"message"` + RetCode int `json:"retcode"` + Status string `json:"status"` + Echo interface{} `json:"echo"` +} + +type StatusData struct { + AppInitialized bool `json:"app_initialized"` + AppEnabled bool `json:"app_enabled"` + PluginsGood bool `json:"plugins_good"` + AppGood bool `json:"app_good"` + Online bool `json:"online"` + Good bool `json:"good"` + Stat Statistics `json:"stat"` +} + +type Statistics struct { + PacketReceived uint64 `json:"packet_received"` + PacketSent uint64 `json:"packet_sent"` + PacketLost uint32 `json:"packet_lost"` + MessageReceived uint64 `json:"message_received"` + MessageSent uint64 `json:"message_sent"` + DisconnectTimes uint32 `json:"disconnect_times"` + LostTimes uint32 `json:"lost_times"` + LastMessageTime int64 `json:"last_message_time"` +} + +func init() { + callapi.RegisterHandler("get_status", getStatus) +} + +func getStatus(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.OpenAPI, message callapi.ActionMessage) { + + var response GetStatusResponse + + response.Data = StatusData{ + AppInitialized: true, + AppEnabled: true, + PluginsGood: true, + AppGood: true, + Online: true, //测试数据 + Good: true, //测试数据 + Stat: Statistics{ + PacketReceived: 1000, //测试数据 + PacketSent: 950, //测试数据 + PacketLost: 50, //测试数据 + MessageReceived: 500, //测试数据 + MessageSent: 490, //测试数据 + DisconnectTimes: 5, //测试数据 + LostTimes: 2, //测试数据 + LastMessageTime: 1677721600, //测试数据 + }, + } + response.Message = "" + response.RetCode = 0 + response.Status = "ok" + response.Echo = string(message.Echo) // Directly assign the string value + + outputMap := structToMap(response) + + log.Printf("get_status: %+v\n", outputMap) + + err := client.SendMessage(outputMap) + if err != nil { + log.Printf("Error sending message via client: %v", err) + } +} diff --git a/handlers/message_parser.go b/handlers/message_parser.go index 3c98a62d..c2368bf3 100644 --- a/handlers/message_parser.go +++ b/handlers/message_parser.go @@ -3,6 +3,7 @@ package handlers import ( "fmt" "log" + "path/filepath" "regexp" "strings" @@ -13,6 +14,48 @@ import ( var BotID string var AppID string +// 定义响应结构体 +type ServerResponse struct { + Data struct { + MessageID int `json:"message_id"` + } `json:"data"` + Message string `json:"message"` + RetCode int `json:"retcode"` + Status string `json:"status"` + Echo string `json:"echo"` +} + +// 发送成功回执 todo 返回可互转的messageid +func SendResponse(client callapi.Client, err error, message *callapi.ActionMessage) error { + // 设置响应值 + response := ServerResponse{} + response.Data.MessageID = 0 // todo 实现messageid转换 + response.Echo = string(message.Echo) + if err != nil { + response.Message = err.Error() // 可选:在响应中添加错误消息 + //response.RetCode = -1 // 可以是任何非零值,表示出错 + //response.Status = "failed" + response.RetCode = 0 //官方api审核异步的 审核中默认返回失败,但其实信息发送成功了 + response.Status = "ok" + } else { + response.Message = "" + response.RetCode = 0 + response.Status = "ok" + } + + // 转化为map并发送 + outputMap := structToMap(response) + + sendErr := client.SendMessage(outputMap) + if sendErr != nil { + log.Printf("Error sending message via client: %v", sendErr) + return sendErr + } + + log.Printf("发送成功回执: %+v", outputMap) + return nil +} + func parseMessageContent(paramsMessage callapi.ParamsContent) (string, map[string][]string) { messageText := "" @@ -118,9 +161,23 @@ func transformMessageText(messageText string) string { } // 处理at和其他定形文到onebotv11格式(cq码) -func RevertTransformedText(messageText string) string { - // Trim leading and trailing spaces - messageText = strings.TrimSpace(messageText) +func RevertTransformedText(data interface{}) string { + var msg *dto.Message + switch v := data.(type) { + case *dto.WSGroupATMessageData: + msg = (*dto.Message)(v) + case *dto.WSATMessageData: + msg = (*dto.Message)(v) + case *dto.WSMessageData: + msg = (*dto.Message)(v) + case *dto.WSDirectMessageData: + msg = (*dto.Message)(v) + case *dto.WSC2CMessageData: + msg = (*dto.Message)(v) + default: + return "" + } + messageText := strings.TrimSpace(msg.Content) // 将messageText里的BotID替换成AppID messageText = strings.ReplaceAll(messageText, BotID, AppID) @@ -128,13 +185,26 @@ func RevertTransformedText(messageText string) string { // 使用正则表达式来查找所有<@!数字>的模式 re := regexp.MustCompile(`<@!(\d+)>`) // 使用正则表达式来替换找到的模式为[CQ:at,qq=数字] - return re.ReplaceAllStringFunc(messageText, func(m string) string { + messageText = re.ReplaceAllStringFunc(messageText, func(m string) string { submatches := re.FindStringSubmatch(m) if len(submatches) > 1 { return "[CQ:at,qq=" + submatches[1] + "]" } return m }) + + // 处理图片附件 + for _, attachment := range msg.Attachments { + if strings.HasPrefix(attachment.ContentType, "image/") { + // 获取文件的后缀名 + ext := filepath.Ext(attachment.FileName) + md5name := strings.TrimSuffix(attachment.FileName, ext) + imageCQ := "[CQ:image,file=" + md5name + ".image,subType=0,url=" + attachment.URL + "]" + messageText += imageCQ + } + } + + return messageText } // 将收到的data.content转换为message segment todo,群场景不支持受图片,频道场景的图片可以拼一下 diff --git a/handlers/send_group_msg.go b/handlers/send_group_msg.go index 76091c21..a548c89b 100644 --- a/handlers/send_group_msg.go +++ b/handlers/send_group_msg.go @@ -10,6 +10,7 @@ import ( "time" "github.com/hoshinonyaruko/gensokyo/callapi" + "github.com/hoshinonyaruko/gensokyo/config" "github.com/hoshinonyaruko/gensokyo/echo" "github.com/hoshinonyaruko/gensokyo/idmap" "github.com/hoshinonyaruko/gensokyo/server" @@ -27,12 +28,12 @@ func handleSendGroupMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openap //如果获取不到 就用user_id获取信息类型 if msgType == "" { - msgType = GetMessageTypeByUserid(client.GetAppIDStr(), message.Params.UserID) + msgType = GetMessageTypeByUserid(config.GetAppIDStr(), message.Params.UserID) } //如果获取不到 就用group_id获取信息类型 if msgType == "" { - msgType = GetMessageTypeByGroupid(client.GetAppIDStr(), message.Params.GroupID) + msgType = GetMessageTypeByGroupid(config.GetAppIDStr(), message.Params.GroupID) } switch msgType { @@ -50,7 +51,7 @@ func handleSendGroupMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openap log.Println("foundItems:", foundItems) // 如果messageID为空,通过函数获取 if messageID == "" { - messageID = GetMessageIDByUseridOrGroupid(client.GetAppIDStr(), message.Params.GroupID) + messageID = GetMessageIDByUseridOrGroupid(config.GetAppIDStr(), message.Params.GroupID) log.Println("通过GetMessageIDByUserid函数获取的message_id:", messageID) } @@ -74,10 +75,13 @@ func handleSendGroupMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openap } groupMessage.Timestamp = time.Now().Unix() // 设置时间戳 - _, err := apiv2.PostGroupMessage(context.TODO(), message.Params.GroupID.(string), groupMessage) + //重新为err赋值 + _, err = apiv2.PostGroupMessage(context.TODO(), message.Params.GroupID.(string), groupMessage) if err != nil { log.Printf("发送文本群组信息失败: %v", err) } + //发送成功回执 + SendResponse(client, err, &message) } // 遍历foundItems并发送每种信息 @@ -98,6 +102,8 @@ func handleSendGroupMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openap if err != nil { log.Printf("发送 %s 信息失败_send_group_msg: %v", key, err) } + //发送成功回执 + SendResponse(client, err, &message) } case "guild": //用GroupID给ChannelID赋值,因为我们是把频道虚拟成了群 diff --git a/handlers/send_guild_channel_msg.go b/handlers/send_guild_channel_msg.go index 71e47fe4..892aed6c 100644 --- a/handlers/send_guild_channel_msg.go +++ b/handlers/send_guild_channel_msg.go @@ -8,6 +8,7 @@ import ( "os" "github.com/hoshinonyaruko/gensokyo/callapi" + "github.com/hoshinonyaruko/gensokyo/config" "github.com/hoshinonyaruko/gensokyo/echo" @@ -25,12 +26,12 @@ func handleSendGuildChannelMsg(client callapi.Client, api openapi.OpenAPI, apiv2 //如果获取不到 就用user_id获取信息类型 if msgType == "" { - msgType = GetMessageTypeByUserid(client.GetAppIDStr(), message.Params.UserID) + msgType = GetMessageTypeByUserid(config.GetAppIDStr(), message.Params.UserID) } //如果获取不到 就用group_id获取信息类型 if msgType == "" { - appID := client.GetAppIDStr() + appID := config.GetAppIDStr() groupID := message.Params.GroupID fmt.Printf("appID: %s, GroupID: %v\n", appID, groupID) @@ -53,12 +54,15 @@ func handleSendGuildChannelMsg(client callapi.Client, api openapi.OpenAPI, apiv2 log.Println("频道发信息对应的message_id:", messageID) log.Println("频道发信息messageText:", messageText) log.Println("foundItems:", foundItems) + var err error // 优先发送文本信息 if messageText != "" { textMsg, _ := generateReplyMessage(messageID, nil, messageText) - if _, err := api.PostMessage(context.TODO(), channelID, textMsg); err != nil { + if _, err = api.PostMessage(context.TODO(), channelID, textMsg); err != nil { log.Printf("发送文本信息失败: %v", err) } + //发送成功回执 + SendResponse(client, err, &message) } // 遍历foundItems并发送每种信息 @@ -80,13 +84,17 @@ func handleSendGuildChannelMsg(client callapi.Client, api openapi.OpenAPI, apiv2 reply.Content = "" // 使用Multipart方法发送 - if _, err := api.PostMessageMultipart(context.TODO(), channelID, reply, fileImageData); err != nil { + if _, err = api.PostMessageMultipart(context.TODO(), channelID, reply, fileImageData); err != nil { log.Printf("使用multipart发送 %s 信息失败: %v message_id %v", key, err, messageID) } + //发送成功回执 + SendResponse(client, err, &message) } else { - if _, err := api.PostMessage(context.TODO(), channelID, reply); err != nil { + if _, err = api.PostMessage(context.TODO(), channelID, reply); err != nil { log.Printf("发送 %s 信息失败: %v", key, err) } + //发送成功回执 + SendResponse(client, err, &message) } } diff --git a/handlers/send_msg.go b/handlers/send_msg.go index e20753d4..844ac1d0 100644 --- a/handlers/send_msg.go +++ b/handlers/send_msg.go @@ -10,6 +10,7 @@ import ( "time" "github.com/hoshinonyaruko/gensokyo/callapi" + "github.com/hoshinonyaruko/gensokyo/config" "github.com/hoshinonyaruko/gensokyo/echo" "github.com/hoshinonyaruko/gensokyo/idmap" "github.com/hoshinonyaruko/gensokyo/server" @@ -27,7 +28,7 @@ func handleSendMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.Ope //如果获取不到 就用group_id获取信息类型 if msgType == "" { - appID := client.GetAppIDStr() + appID := config.GetAppIDStr() groupID := message.Params.GroupID fmt.Printf("appID: %s, GroupID: %v\n", appID, groupID) @@ -37,7 +38,7 @@ func handleSendMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.Ope //如果获取不到 就用user_id获取信息类型 if msgType == "" { - msgType = GetMessageTypeByUserid(client.GetAppIDStr(), message.Params.UserID) + msgType = GetMessageTypeByUserid(config.GetAppIDStr(), message.Params.UserID) } switch msgType { @@ -55,7 +56,7 @@ func handleSendMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.Ope log.Println("foundItems:", foundItems) // 如果messageID为空,通过函数获取 if messageID == "" { - messageID = GetMessageIDByUseridOrGroupid(client.GetAppIDStr(), message.Params.GroupID) + messageID = GetMessageIDByUseridOrGroupid(config.GetAppIDStr(), message.Params.GroupID) log.Println("通过GetMessageIDByUserid函数获取的message_id:", messageID) } @@ -79,10 +80,12 @@ func handleSendMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.Ope } groupMessage.Timestamp = time.Now().Unix() // 设置时间戳 - _, err := apiv2.PostGroupMessage(context.TODO(), message.Params.GroupID.(string), groupMessage) + _, err = apiv2.PostGroupMessage(context.TODO(), message.Params.GroupID.(string), groupMessage) if err != nil { log.Printf("发送文本群组信息失败: %v", err) } + //发送成功回执 + SendResponse(client, err, &message) } // 遍历foundItems并发送每种信息 @@ -100,10 +103,12 @@ func handleSendMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.Ope } fmt.Printf("richMediaMessage: %+v\n", richMediaMessage) - _, err := apiv2.PostGroupMessage(context.TODO(), message.Params.GroupID.(string), richMediaMessage) + _, err = apiv2.PostGroupMessage(context.TODO(), message.Params.GroupID.(string), richMediaMessage) if err != nil { log.Printf("发送 %s 信息失败_send_msg: %v", key, err) } + //发送成功回执 + SendResponse(client, err, &message) } case "guild": //用GroupID给ChannelID赋值,因为我们是把频道虚拟成了群 @@ -175,10 +180,12 @@ func handleSendMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.Ope } groupMessage.Timestamp = time.Now().Unix() // 设置时间戳 - _, err := apiv2.PostC2CMessage(context.TODO(), UserID, groupMessage) + _, err = apiv2.PostC2CMessage(context.TODO(), UserID, groupMessage) if err != nil { log.Printf("发送文本私聊信息失败: %v", err) } + //发送成功回执 + SendResponse(client, err, &message) } // 遍历 foundItems 并发送每种信息 @@ -194,10 +201,12 @@ func handleSendMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.Ope log.Printf("Error: Expected RichMediaMessage type for key %s.", key) continue } - _, err := apiv2.PostC2CMessage(context.TODO(), UserID, richMediaMessage) + _, err = apiv2.PostC2CMessage(context.TODO(), UserID, richMediaMessage) if err != nil { log.Printf("发送 %s 私聊信息失败: %v", key, err) } + //发送成功回执 + SendResponse(client, err, &message) } default: log.Printf("1Unknown message type: %s", msgType) diff --git a/handlers/send_private_msg.go b/handlers/send_private_msg.go index 68423f61..6798eb92 100644 --- a/handlers/send_private_msg.go +++ b/handlers/send_private_msg.go @@ -9,6 +9,7 @@ import ( "time" "github.com/hoshinonyaruko/gensokyo/callapi" + "github.com/hoshinonyaruko/gensokyo/config" "github.com/hoshinonyaruko/gensokyo/echo" "github.com/hoshinonyaruko/gensokyo/idmap" "github.com/tencent-connect/botgo/dto" @@ -149,7 +150,7 @@ func handleSendGuildChannelPrivateMsg(client callapi.Client, api openapi.OpenAPI log.Println("foundItems:", foundItems) // 如果messageID为空,通过函数获取 if messageID == "" { - messageID = GetMessageIDByUseridOrGroupid(client.GetAppIDStr(), message.Params.UserID) + messageID = GetMessageIDByUseridOrGroupid(config.GetAppIDStr(), message.Params.UserID) log.Println("通过GetMessageIDByUserid函数获取的message_id:", messageID) } @@ -166,9 +167,11 @@ func handleSendGuildChannelPrivateMsg(client callapi.Client, api openapi.OpenAPI // 优先发送文本信息 if messageText != "" { textMsg, _ := generateReplyMessage(messageID, nil, messageText) - if _, err := apiv2.PostDirectMessage(context.TODO(), dm, textMsg); err != nil { + if _, err = apiv2.PostDirectMessage(context.TODO(), dm, textMsg); err != nil { log.Printf("发送文本信息失败: %v", err) } + //发送成功回执 + SendResponse(client, err, &message) } // 遍历foundItems并发送每种信息 @@ -190,13 +193,17 @@ func handleSendGuildChannelPrivateMsg(client callapi.Client, api openapi.OpenAPI reply.Content = "" // 使用Multipart方法发送 - if _, err := api.PostDirectMessageMultipart(context.TODO(), dm, reply, fileImageData); err != nil { + if _, err = api.PostDirectMessageMultipart(context.TODO(), dm, reply, fileImageData); err != nil { log.Printf("使用multipart发送 %s 信息失败: %v message_id %v", key, err, messageID) } + //发送成功回执 + SendResponse(client, err, &message) } else { - if _, err := api.PostDirectMessage(context.TODO(), dm, reply); err != nil { + if _, err = api.PostDirectMessage(context.TODO(), dm, reply); err != nil { log.Printf("发送 %s 信息失败: %v", key, err) } + //发送成功回执 + SendResponse(client, err, &message) } } diff --git a/main.go b/main.go index b95be7a7..66bd3eb3 100644 --- a/main.go +++ b/main.go @@ -28,17 +28,7 @@ import ( ) // 消息处理器,持有 openapi 对象 -var processor *Processor.Processor - -// 修改函数的返回类型为 *Processor -func NewProcessor(api openapi.OpenAPI, apiv2 openapi.OpenAPI, settings *config.Settings, wsclient *wsclient.WebSocketClient) *Processor.Processor { - return &Processor.Processor{ - Api: api, - Apiv2: apiv2, - Settings: settings, - Wsclient: wsclient, - } -} +var p *Processor.Processors func main() { if _, err := os.Stat("config.yml"); os.IsNotExist(err) { @@ -134,30 +124,40 @@ func main() { } }() - // 创建一个通道来传递 WebSocketClient - wsClientChan := make(chan *wsclient.WebSocketClient) + // 启动多个WebSocket客户端 + wsClients := []*wsclient.WebSocketClient{} + wsClientChan := make(chan *wsclient.WebSocketClient, len(conf.Settings.WsAddress)) + errorChan := make(chan error, len(conf.Settings.WsAddress)) + + for _, wsAddr := range conf.Settings.WsAddress { + go func(address string) { + wsClient, err := wsclient.NewWebSocketClient(address, conf.Settings.AppID, api, apiV2) + if err != nil { + fmt.Printf("Error creating WebSocketClient for address %s: %v\n", address, err) + errorChan <- err + return + } + wsClientChan <- wsClient + }(wsAddr) + } - // 在新的 go 函数中初始化 wsClient - go func() { - wsClient, err := wsclient.NewWebSocketClient(conf.Settings.WsAddress, conf.Settings.AppID, api, apiV2) - if err != nil { - fmt.Printf("Error creating WebSocketClient: %v\n", err) - close(wsClientChan) // 关闭通道表示不再发送值 - return + // Collect results + for i := 0; i < len(conf.Settings.WsAddress); i++ { + select { + case wsClient := <-wsClientChan: + wsClients = append(wsClients, wsClient) + case err := <-errorChan: + fmt.Printf("Error encountered while initializing WebSocketClient: %v\n", err) } - wsClientChan <- wsClient // 将 wsClient 发送到通道 - }() - - // 从通道中接收 wsClient 的值 - wsClient := <-wsClientChan + } - // 确保 wsClient 不为 nil,然后创建 Processor - if wsClient != nil { - fmt.Println("wsClient is successfully initialized.") - processor = NewProcessor(api, apiV2, &conf.Settings, wsClient) + // 确保所有wsClients都已初始化 + if len(wsClients) != len(conf.Settings.WsAddress) { + fmt.Println("Error: Not all wsClients are initialized!") + log.Fatalln("Failed to initialize all WebSocketClients.") } else { - fmt.Println("Error: wsClient is nil!") - log.Fatalln("Failed to initialize WebSocketClient.") + fmt.Println("All wsClients are successfully initialized.") + p = Processor.NewProcessor(api, apiV2, &conf.Settings, wsClients) } //创建idmap服务器 @@ -166,14 +166,16 @@ func main() { //图片上传 调用次数限制 rateLimiter := server.NewRateLimiter() - + //是否启动服务器 + shouldStartServer := !conf.Settings.Lotus || conf.Settings.EnableWsServer //如果连接到其他gensokyo,则不需要启动服务器 - if !conf.Settings.Lotus { + if shouldStartServer { r := gin.Default() r.GET("/getid", server.GetIDHandler) r.POST("/uploadpic", server.UploadBase64ImageHandler(rateLimiter)) r.Static("/channel_temp", "./channel_temp") - r.Run("0.0.0.0:" + conf.Settings.Port) // 注意,这里我更改了端口为你提供的Port,并监听0.0.0.0地址 + r.GET("/ws", server.WsHandlerWithDependencies(api, apiV2)) + r.Run("0.0.0.0:" + conf.Settings.Port) // 监听0.0.0.0地址的Port端口 } // 使用通道来等待信号 @@ -184,9 +186,12 @@ func main() { <-sigCh // 关闭 WebSocket 连接 - err = wsClient.Close() - if err != nil { - fmt.Printf("Error closing WebSocket connection: %v\n", err) + // wsClients 是一个 *wsclient.WebSocketClient 的切片 + for _, client := range wsClients { + err := client.Close() + if err != nil { + fmt.Printf("Error closing WebSocket connection: %v\n", err) + } } } @@ -207,7 +212,7 @@ func ErrorNotifyHandler() event.ErrorNotifyHandler { // ATMessageEventHandler 实现处理 频道at 消息的回调 func ATMessageEventHandler() event.ATMessageEventHandler { return func(event *dto.WSPayload, data *dto.WSATMessageData) error { - return processor.ProcessGuildATMessage(data) + return p.ProcessGuildATMessage(data) } } @@ -238,7 +243,7 @@ func MemberEventHandler() event.GuildMemberEventHandler { // DirectMessageHandler 处理私信事件 func DirectMessageHandler() event.DirectMessageEventHandler { return func(event *dto.WSPayload, data *dto.WSDirectMessageData) error { - return processor.ProcessChannelDirectMessage(data) + return p.ProcessChannelDirectMessage(data) } } @@ -246,7 +251,7 @@ func DirectMessageHandler() event.DirectMessageEventHandler { func CreateMessageHandler() event.MessageEventHandler { return func(event *dto.WSPayload, data *dto.WSMessageData) error { fmt.Println("收到私域信息", data) - return processor.ProcessGuildNormalMessage(data) + return p.ProcessGuildNormalMessage(data) } } @@ -254,22 +259,21 @@ func CreateMessageHandler() event.MessageEventHandler { func InteractionHandler() event.InteractionEventHandler { return func(event *dto.WSPayload, data *dto.WSInteractionData) error { fmt.Println(data) - return processor.ProcessInlineSearch(data) + return p.ProcessInlineSearch(data) } } // GroupATMessageEventHandler 实现处理 群at 消息的回调 func GroupATMessageEventHandler() event.GroupATMessageEventHandler { return func(event *dto.WSPayload, data *dto.WSGroupATMessageData) error { - return processor.ProcessGroupMessage(data) + return p.ProcessGroupMessage(data) } } // C2CMessageEventHandler 实现处理 群私聊 消息的回调 func C2CMessageEventHandler() event.C2CMessageEventHandler { return func(event *dto.WSPayload, data *dto.WSC2CMessageData) error { - log.Print("1111") - return processor.ProcessC2CMessage(data) + return p.ProcessC2CMessage(data) } } @@ -293,7 +297,7 @@ func getHandlerByName(handlerName string) (interface{}, bool) { return CreateMessageHandler(), true case "InteractionHandler": //添加频道互动回应 return InteractionHandler(), true - case "ThreadEventHandler": //发帖事件 + case "ThreadEventHandler": //发帖事件 暂不支持 return nil, false //return ThreadEventHandler(), true case "GroupATMessageEventHandler": //群at信息 diff --git a/server/uploadpic.go b/server/uploadpic.go index 78c419d0..606a76a0 100644 --- a/server/uploadpic.go +++ b/server/uploadpic.go @@ -43,7 +43,7 @@ func NewRateLimiter() *RateLimiter { } } -// 网页后端,图床逻辑,基于gin和www静态文件的简易图床 +// 闭包,网页后端,图床逻辑,基于gin和www静态文件的简易图床 func UploadBase64ImageHandler(rateLimiter *RateLimiter) gin.HandlerFunc { return func(c *gin.Context) { ipAddress := c.ClientIP() diff --git a/server/wsserver.go b/server/wsserver.go new file mode 100644 index 00000000..30d9d2d2 --- /dev/null +++ b/server/wsserver.go @@ -0,0 +1,89 @@ +package server + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/gorilla/websocket" + "github.com/hoshinonyaruko/gensokyo/callapi" + "github.com/hoshinonyaruko/gensokyo/wsclient" + "github.com/tencent-connect/botgo/openapi" +) + +type WebSocketServerClient struct { + Conn *websocket.Conn + API openapi.OpenAPI + APIv2 openapi.OpenAPI +} + +var upgrader = websocket.Upgrader{ + CheckOrigin: func(r *http.Request) bool { + return true + }, +} + +// 使用闭包结构 因为gin需要c *gin.Context固定签名 +func WsHandlerWithDependencies(api openapi.OpenAPI, apiV2 openapi.OpenAPI) gin.HandlerFunc { + return func(c *gin.Context) { + wsHandler(api, apiV2, c) + } +} + +func wsHandler(api openapi.OpenAPI, apiV2 openapi.OpenAPI, c *gin.Context) { + conn, err := upgrader.Upgrade(c.Writer, c.Request, nil) + if err != nil { + log.Printf("Failed to set websocket upgrade: %+v", err) + return + } + + clientIP := c.ClientIP() + headers := c.Request.Header + log.Printf("WebSocket client connected. IP: %s, Headers: %v", clientIP, headers) + + // 创建WebSocketServerClient实例 + client := &WebSocketServerClient{ + Conn: conn, + API: api, + APIv2: apiV2, + } + + defer conn.Close() + + for { + messageType, p, err := conn.ReadMessage() + if err != nil { + log.Printf("Error reading message: %v", err) + return + } + + if messageType == websocket.TextMessage { + processWSMessage(client, p) // 使用WebSocketServerClient而不是直接使用连接 + } + } +} + +func processWSMessage(client *WebSocketServerClient, msg []byte) { + var message callapi.ActionMessage + err := json.Unmarshal(msg, &message) + if err != nil { + log.Printf("Error unmarshalling message: %v, Original message: %s", err, string(msg)) + return + } + + fmt.Println("Received from WebSocket onebotv11 client:", wsclient.TruncateMessage(message, 500)) + // 调用callapi + callapi.CallAPIFromDict(client, client.API, client.APIv2, message) +} + +// 发信息给client +func (c *WebSocketServerClient) SendMessage(message map[string]interface{}) error { + msgBytes, err := json.Marshal(message) + if err != nil { + log.Println("Error marshalling message:", err) + return err + } + return c.Conn.WriteMessage(websocket.TextMessage, msgBytes) +} diff --git a/wsclient/ws.go b/wsclient/ws.go index 4255b1a3..5db10e70 100644 --- a/wsclient/ws.go +++ b/wsclient/ws.go @@ -6,6 +6,7 @@ import ( "fmt" "log" "net/http" + "net/url" "time" "github.com/gorilla/websocket" @@ -18,17 +19,6 @@ type WebSocketClient struct { conn *websocket.Conn api openapi.OpenAPI apiv2 openapi.OpenAPI - appid uint64 -} - -// 获取appid -func (c *WebSocketClient) GetAppID() uint64 { - return c.appid -} - -// 获取appid的字符串形式 -func (c *WebSocketClient) GetAppIDStr() string { - return fmt.Sprintf("%d", c.appid) } // 发送json信息给onebot应用端 @@ -70,13 +60,13 @@ func (c *WebSocketClient) recvMessage(msg []byte) { return } - fmt.Println("Received from onebotv11:", truncateMessage(message, 500)) + fmt.Println("Received from onebotv11 server:", TruncateMessage(message, 500)) // 调用callapi callapi.CallAPIFromDict(c, c.api, c.apiv2, message) } // 截断信息 -func truncateMessage(message callapi.ActionMessage, maxLength int) string { +func TruncateMessage(message callapi.ActionMessage, maxLength int) string { paramsStr, err := json.Marshal(message.Params) if err != nil { return "Error marshalling Params for truncation." @@ -112,7 +102,22 @@ func (c *WebSocketClient) sendHeartbeat(ctx context.Context, botID uint64) { // NewWebSocketClient 创建 WebSocketClient 实例,接受 WebSocket URL、botID 和 openapi.OpenAPI 实例 func NewWebSocketClient(urlStr string, botID uint64, api openapi.OpenAPI, apiv2 openapi.OpenAPI) (*WebSocketClient, error) { - token := config.GetWsToken() // 从配置中获取 token + addresses := config.GetWsAddress() + tokens := config.GetWsToken() + + var token string + for index, address := range addresses { + if address == urlStr && index < len(tokens) { + token = tokens[index] + break + } + } + + // 检查URL中是否有access_token参数 + mp := getParamsFromURI(urlStr) + if val, ok := mp["access_token"]; ok { + token = val + } headers := http.Header{ "User-Agent": []string{"CQHttp/4.15.0"}, @@ -120,11 +125,10 @@ func NewWebSocketClient(urlStr string, botID uint64, api openapi.OpenAPI, apiv2 "X-Self-ID": []string{fmt.Sprintf("%d", botID)}, } - // 如果 token 不为空,将其添加到 headers 中 if token != "" { headers["Authorization"] = []string{"Token " + token} } - + fmt.Printf("准备使用token[%s]连接到[%s]\n", token, urlStr) dialer := websocket.Dialer{ Proxy: http.ProxyFromEnvironment, HandshakeTimeout: 45 * time.Second, @@ -146,7 +150,7 @@ func NewWebSocketClient(urlStr string, botID uint64, api openapi.OpenAPI, apiv2 } } - client := &WebSocketClient{conn: conn, api: api, apiv2: apiv2, appid: botID} + client := &WebSocketClient{conn: conn, api: api, apiv2: apiv2} // Sending initial message similar to your setupB function message := map[string]interface{}{ @@ -177,3 +181,23 @@ func NewWebSocketClient(urlStr string, botID uint64, api openapi.OpenAPI, apiv2 func (ws *WebSocketClient) Close() error { return ws.conn.Close() } + +// getParamsFromURI 解析给定URI中的查询参数,并返回一个映射(map) +func getParamsFromURI(uriStr string) map[string]string { + params := make(map[string]string) + + u, err := url.Parse(uriStr) + if err != nil { + fmt.Printf("Error parsing the URL: %v\n", err) + return params + } + + // 遍历查询参数并将其添加到返回的映射中 + for key, values := range u.Query() { + if len(values) > 0 { + params[key] = values[0] // 如果一个参数有多个值,这里只选择第一个。可以根据需求进行调整。 + } + } + + return params +} From 794346690612cda472efe671d67d624bb5cd06c3 Mon Sep 17 00:00:00 2001 From: cosmo Date: Sat, 28 Oct 2023 13:30:22 +0800 Subject: [PATCH 19/27] add ws server token --- Processor/ProcessC2CMessage.go | 36 ++-------- Processor/ProcessChannelDirectMessage.go | 52 ++------------- Processor/ProcessGroupMessage.go | 18 +---- Processor/ProcessGuildATMessage.go | 35 ++-------- Processor/ProcessGuildNormalMessage.go | 37 ++--------- Processor/Processor.go | 58 ++++++++++++++-- callapi/callapi.go | 5 ++ config/config.go | 13 ++++ config_template.go | 1 + config_template.yml | 3 +- go.mod | 2 + go.sum | 4 ++ handlers/send_group_msg.go | 6 +- handlers/send_msg.go | 6 +- images/upload_api.go | 85 ++++++++++++++++++++++++ main.go | 2 +- readme.md | 2 +- server/uploadpic.go | 76 --------------------- server/wsserver.go | 38 +++++++++-- 19 files changed, 225 insertions(+), 254 deletions(-) create mode 100644 images/upload_api.go diff --git a/Processor/ProcessC2CMessage.go b/Processor/ProcessC2CMessage.go index 50f097e3..41947f98 100644 --- a/Processor/ProcessC2CMessage.go +++ b/Processor/ProcessC2CMessage.go @@ -2,10 +2,8 @@ package Processor import ( - "fmt" "log" "strconv" - "strings" "time" "github.com/hoshinonyaruko/gensokyo/config" @@ -90,21 +88,8 @@ func (p *Processors) ProcessC2CMessage(data *dto.WSC2CMessageData) error { // Convert OnebotGroupMessage to map and send privateMsgMap := structToMap(privateMsg) - var errors []string - - for _, client := range p.Wsclient { - err = client.SendMessage(privateMsgMap) - if err != nil { - // 记录错误信息,但不立即返回 - errors = append(errors, fmt.Sprintf("error sending private message via wsclient: %v", err)) - } - } - - // 在循环结束后处理记录的错误 - if len(errors) > 0 { - // 使用strings.Join合并所有的错误信息 - return fmt.Errorf(strings.Join(errors, "; ")) - } + //上报信息到onebotv11应用端(正反ws) + p.BroadcastMessageToAll(privateMsgMap) } else { //将私聊信息转化为群信息(特殊需求情况下) @@ -177,21 +162,8 @@ func (p *Processors) ProcessC2CMessage(data *dto.WSC2CMessageData) error { // Convert OnebotGroupMessage to map and send groupMsgMap := structToMap(groupMsg) - var errors []string - - for _, client := range p.Wsclient { - err = client.SendMessage(groupMsgMap) - if err != nil { - // 记录错误信息,但不立即返回 - errors = append(errors, fmt.Sprintf("error sending group message via wsclient: %v", err)) - } - } - - // 在循环结束后处理记录的错误 - if len(errors) > 0 { - // 使用strings.Join合并所有的错误信息 - log.Println("Encountered errors while sending to wsclients:", strings.Join(errors, "; ")) - } + //上报信息到onebotv11应用端(正反ws) + p.BroadcastMessageToAll(groupMsgMap) } return nil } diff --git a/Processor/ProcessChannelDirectMessage.go b/Processor/ProcessChannelDirectMessage.go index 52b31940..3c49ffc2 100644 --- a/Processor/ProcessChannelDirectMessage.go +++ b/Processor/ProcessChannelDirectMessage.go @@ -5,7 +5,6 @@ import ( "fmt" "log" "strconv" - "strings" "time" "github.com/hoshinonyaruko/gensokyo/config" @@ -94,21 +93,8 @@ func (p *Processors) ProcessChannelDirectMessage(data *dto.WSDirectMessageData) // Convert OnebotGroupMessage to map and send privateMsgMap := structToMap(privateMsg) - var errors []string - - for _, client := range p.Wsclient { - err = client.SendMessage(privateMsgMap) - if err != nil { - // 记录错误信息,但不立即返回 - errors = append(errors, fmt.Sprintf("error sending private message via wsclient: %v", err)) - } - } - - // 在循环结束后处理记录的错误 - if len(errors) > 0 { - // 使用strings.Join合并所有的错误信息 - return fmt.Errorf(strings.Join(errors, "; ")) - } + //上报信息到onebotv11应用端(正反ws) + p.BroadcastMessageToAll(privateMsgMap) } else { if !p.Settings.GlobalChannelToGroup { //将频道私信作为普通频道信息 @@ -187,21 +173,8 @@ func (p *Processors) ProcessChannelDirectMessage(data *dto.WSDirectMessageData) // 将 onebotMsg 结构体转换为 map[string]interface{} msgMap := structToMap(onebotMsg) - var errors []string - - for _, client := range p.Wsclient { - err = client.SendMessage(msgMap) - if err != nil { - // 记录错误信息,但不立即返回 - errors = append(errors, fmt.Sprintf("error sending message via wsclient: %v", err)) - } - } - - // 在循环结束后处理记录的错误 - if len(errors) > 0 { - // 使用strings.Join合并所有的错误信息 - return fmt.Errorf(strings.Join(errors, "; ")) - } + //上报信息到onebotv11应用端(正反ws) + p.BroadcastMessageToAll(msgMap) } else { //将频道信息转化为群信息(特殊需求情况下) //将channelid写入ini,可取出guild_id @@ -287,21 +260,8 @@ func (p *Processors) ProcessChannelDirectMessage(data *dto.WSDirectMessageData) // Convert OnebotGroupMessage to map and send groupMsgMap := structToMap(groupMsg) - var errors []string - - for _, client := range p.Wsclient { - err = client.SendMessage(groupMsgMap) - if err != nil { - // 记录错误信息,但不立即返回 - errors = append(errors, fmt.Sprintf("error sending group message via wsclient: %v", err)) - } - } - - // 在循环结束后处理记录的错误 - if len(errors) > 0 { - // 使用strings.Join合并所有的错误信息 - return fmt.Errorf(strings.Join(errors, "; ")) - } + //上报信息到onebotv11应用端(正反ws) + p.BroadcastMessageToAll(groupMsgMap) } } diff --git a/Processor/ProcessGroupMessage.go b/Processor/ProcessGroupMessage.go index bb057694..78d31566 100644 --- a/Processor/ProcessGroupMessage.go +++ b/Processor/ProcessGroupMessage.go @@ -5,7 +5,6 @@ import ( "fmt" "log" "strconv" - "strings" "time" "github.com/hoshinonyaruko/gensokyo/config" @@ -108,20 +107,7 @@ func (p *Processors) ProcessGroupMessage(data *dto.WSGroupATMessageData) error { // Convert OnebotGroupMessage to map and send groupMsgMap := structToMap(groupMsg) - var errors []string - - for _, client := range p.Wsclient { - err = client.SendMessage(groupMsgMap) - if err != nil { - // 记录错误信息,但不立即返回 - errors = append(errors, fmt.Sprintf("error sending group message via wsclient: %v", err)) - } - } - - // 在循环结束后处理记录的错误 - if len(errors) > 0 { - // 使用strings.Join合并所有的错误信息 - return fmt.Errorf(strings.Join(errors, "; ")) - } + //上报信息到onebotv11应用端(正反ws) + p.BroadcastMessageToAll(groupMsgMap) return nil } diff --git a/Processor/ProcessGuildATMessage.go b/Processor/ProcessGuildATMessage.go index ea4e5998..70ffb2b7 100644 --- a/Processor/ProcessGuildATMessage.go +++ b/Processor/ProcessGuildATMessage.go @@ -5,7 +5,6 @@ import ( "fmt" "log" "strconv" - "strings" "time" "github.com/hoshinonyaruko/gensokyo/config" @@ -99,21 +98,8 @@ func (p *Processors) ProcessGuildATMessage(data *dto.WSATMessageData) error { // 将 onebotMsg 结构体转换为 map[string]interface{} msgMap := structToMap(onebotMsg) - var errors []string - - for _, client := range p.Wsclient { - err = client.SendMessage(msgMap) - if err != nil { - // 记录错误信息,但不立即返回 - errors = append(errors, fmt.Sprintf("error sending message via wsclient: %v", err)) - } - } - - // 在循环结束后处理记录的错误 - if len(errors) > 0 { - // 使用strings.Join合并所有的错误信息 - return fmt.Errorf(strings.Join(errors, "; ")) - } + //上报信息到onebotv11应用端(正反ws) + p.BroadcastMessageToAll(msgMap) } else { // GlobalChannelToGroup为true时的处理逻辑 //将频道转化为一个群 @@ -202,21 +188,8 @@ func (p *Processors) ProcessGuildATMessage(data *dto.WSATMessageData) error { // Convert OnebotGroupMessage to map and send groupMsgMap := structToMap(groupMsg) - var errors []string - - for _, client := range p.Wsclient { - err = client.SendMessage(groupMsgMap) - if err != nil { - // 记录错误信息,但不立即返回 - errors = append(errors, fmt.Sprintf("error sending group message via wsclient: %v", err)) - } - } - - // 在循环结束后处理记录的错误 - if len(errors) > 0 { - // 使用strings.Join合并所有的错误信息 - return fmt.Errorf(strings.Join(errors, "; ")) - } + //上报信息到onebotv11应用端(正反ws) + p.BroadcastMessageToAll(groupMsgMap) } diff --git a/Processor/ProcessGuildNormalMessage.go b/Processor/ProcessGuildNormalMessage.go index 5eeb84a2..370a0178 100644 --- a/Processor/ProcessGuildNormalMessage.go +++ b/Processor/ProcessGuildNormalMessage.go @@ -5,7 +5,6 @@ import ( "fmt" "log" "strconv" - "strings" "time" "github.com/hoshinonyaruko/gensokyo/config" @@ -98,22 +97,8 @@ func (p *Processors) ProcessGuildNormalMessage(data *dto.WSMessageData) error { // 将 onebotMsg 结构体转换为 map[string]interface{} msgMap := structToMap(onebotMsg) - var errors []string - - // 遍历每一个 wsclient 并发送消息 - for _, client := range p.Wsclient { - err = client.SendMessage(msgMap) - if err != nil { - // 记录错误但不立即返回 - errors = append(errors, fmt.Sprintf("error sending message via wsclient: %v", err)) - } - } - - // 在循环结束后处理记录的错误 - if len(errors) > 0 { - // 使用strings.Join来合并所有的错误信息 - return fmt.Errorf(strings.Join(errors, "; ")) - } + //上报信息到onebotv11应用端(正反ws) + p.BroadcastMessageToAll(msgMap) } else { // GlobalChannelToGroup为true时的处理逻辑 //将频道转化为一个群 @@ -203,22 +188,8 @@ func (p *Processors) ProcessGuildNormalMessage(data *dto.WSMessageData) error { // Convert OnebotGroupMessage to map and send groupMsgMap := structToMap(groupMsg) - var errors []string - - // 遍历每一个 wsclient 并发送消息 - for _, client := range p.Wsclient { - err = client.SendMessage(groupMsgMap) - if err != nil { - // 记录错误但不立即返回 - errors = append(errors, fmt.Sprintf("error sending group message via wsclient: %v", err)) - } - } - - // 在循环结束后处理记录的错误 - if len(errors) > 0 { - // 使用strings.Join来合并所有的错误信息 - return fmt.Errorf(strings.Join(errors, "; ")) - } + //上报信息到onebotv11应用端(正反ws) + p.BroadcastMessageToAll(groupMsgMap) } return nil diff --git a/Processor/Processor.go b/Processor/Processor.go index b0b91915..b66e2d8b 100644 --- a/Processor/Processor.go +++ b/Processor/Processor.go @@ -5,21 +5,24 @@ import ( "encoding/json" "fmt" "reflect" + "strings" "time" + "github.com/hashicorp/go-multierror" + "github.com/hoshinonyaruko/gensokyo/callapi" "github.com/hoshinonyaruko/gensokyo/config" "github.com/hoshinonyaruko/gensokyo/wsclient" - "github.com/tencent-connect/botgo/dto" "github.com/tencent-connect/botgo/openapi" ) // Processor 结构体用于处理消息 type Processors struct { - Api openapi.OpenAPI // API 类型 - Apiv2 openapi.OpenAPI //群的API - Settings *config.Settings // 使用指针 - Wsclient []*wsclient.WebSocketClient // 指针的切片 + Api openapi.OpenAPI // API 类型 + Apiv2 openapi.OpenAPI //群的API + Settings *config.Settings // 使用指针 + Wsclient []*wsclient.WebSocketClient // 指针的切片 + WsServerClients []callapi.WebSocketServerClienter //ws server被连接的客户端 } type Sender struct { @@ -183,3 +186,48 @@ func NewProcessor(api openapi.OpenAPI, apiv2 openapi.OpenAPI, settings *config.S Wsclient: wsclient, } } + +// 发信息给所有连接正向ws的客户端 +func (p *Processors) SendMessageToAllClients(message map[string]interface{}) error { + var result *multierror.Error + + for _, client := range p.WsServerClients { + // 使用接口的方法 + err := client.SendMessage(message) + if err != nil { + // Append the error to our result + result = multierror.Append(result, fmt.Errorf("failed to send to client: %w", err)) + } + } + + // This will return nil if no errors were added + return result.ErrorOrNil() +} + +// 方便快捷的发信息函数 +func (p *Processors) BroadcastMessageToAll(message map[string]interface{}) error { + var errors []string + + // 发送到我们作为客户端的Wsclient + for _, client := range p.Wsclient { + err := client.SendMessage(message) + if err != nil { + errors = append(errors, fmt.Sprintf("error sending private message via wsclient: %v", err)) + } + } + + // 发送到我们作为服务器连接到我们的WsServerClients + for _, serverClient := range p.WsServerClients { + err := serverClient.SendMessage(message) + if err != nil { + errors = append(errors, fmt.Sprintf("error sending private message via WsServerClient: %v", err)) + } + } + + // 在循环结束后处理记录的错误 + if len(errors) > 0 { + return fmt.Errorf(strings.Join(errors, "; ")) + } + + return nil +} diff --git a/callapi/callapi.go b/callapi/callapi.go index ec9221f1..7c39a2d7 100644 --- a/callapi/callapi.go +++ b/callapi/callapi.go @@ -116,6 +116,11 @@ type Client interface { SendMessage(message map[string]interface{}) error } +// 为了解决processor和server循环依赖设计的接口 +type WebSocketServerClienter interface { + SendMessage(message map[string]interface{}) error +} + // 根据action订阅handler处理api type HandlerFunc func(client Client, api openapi.OpenAPI, apiv2 openapi.OpenAPI, messgae ActionMessage) diff --git a/config/config.go b/config/config.go index 2e27f98c..bc1bb8cd 100644 --- a/config/config.go +++ b/config/config.go @@ -36,6 +36,7 @@ type Settings struct { WsToken []string `yaml:"ws_token,omitempty"` // 连接wss时使用,不是wss可留空 一一对应 MasterID []string `yaml:"master_id,omitempty"` // 如果需要在群权限判断是管理员是,将user_id填入这里,master_id是一个文本数组 EnableWsServer bool `yaml:"enable_ws_server,omitempty"` //正向ws开关 + WsServerToken string `yaml:"ws_server_token,omitempty"` //正向ws token } // LoadConfig 从文件中加载配置并初始化单例配置 @@ -169,3 +170,15 @@ func GetEnableWsServer() bool { } return instance.Settings.EnableWsServer } + +// 获取WsServerToken的值 +func GetWsServerToken() string { + mu.Lock() + defer mu.Unlock() + + if instance == nil { + log.Println("Warning: instance is nil when trying to get WsServerToken value.") + return "" + } + return instance.Settings.WsServerToken +} diff --git a/config_template.go b/config_template.go index 6200655e..5d762815 100644 --- a/config_template.go +++ b/config_template.go @@ -40,4 +40,5 @@ settings: ws_token: ["","",""] #连接wss地址时服务器所需的token,如果是ws,可留空,按顺序一一对应 master_id : ["1","2"] #群场景尚未开放获取管理员和列表能力,手动从日志中获取需要设置为管理,的user_id并填入(适用插件有权限判断场景) enable_ws_server: true #是否启用正向ws服务器 监听server_dir:port/ws + ws_server_token : "12345" #正向ws的token 不启动正向ws可忽略 ` diff --git a/config_template.yml b/config_template.yml index 6ea4cb5c..2006abc0 100644 --- a/config_template.yml +++ b/config_template.yml @@ -30,4 +30,5 @@ settings: # 如果需要发送base64图片,需要设置正确的公网server_dir和开放对应的port ws_token: ["",""] #连接wss地址时服务器所需的token,如果是ws,可留空,按顺序一一对应 master_id : ["1","2"] #群场景尚未开放获取管理员和列表能力,手动从日志中获取需要设置为管理,的user_id并填入(适用插件有权限判断场景) - enable_ws_server: true #是否启用正向ws服务器 监听server_dir:port/ws \ No newline at end of file + enable_ws_server: true #是否启用正向ws服务器 监听server_dir:port/ws + ws_server_token : "12345" #正向ws的token 不启动正向ws可忽略 \ No newline at end of file diff --git a/go.mod b/go.mod index ae3a48a8..59a71915 100644 --- a/go.mod +++ b/go.mod @@ -24,6 +24,8 @@ require ( github.com/go-playground/validator/v10 v10.14.0 // indirect github.com/go-resty/resty/v2 v2.6.0 // indirect github.com/goccy/go-json v0.10.2 // indirect + github.com/hashicorp/errwrap v1.0.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.4 // indirect github.com/leodido/go-urn v1.2.4 // indirect diff --git a/go.sum b/go.sum index 1bed39fa..b72202cc 100644 --- a/go.sum +++ b/go.sum @@ -57,6 +57,10 @@ github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLe github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= diff --git a/handlers/send_group_msg.go b/handlers/send_group_msg.go index a548c89b..309cbb01 100644 --- a/handlers/send_group_msg.go +++ b/handlers/send_group_msg.go @@ -13,7 +13,7 @@ import ( "github.com/hoshinonyaruko/gensokyo/config" "github.com/hoshinonyaruko/gensokyo/echo" "github.com/hoshinonyaruko/gensokyo/idmap" - "github.com/hoshinonyaruko/gensokyo/server" + "github.com/hoshinonyaruko/gensokyo/images" "github.com/tencent-connect/botgo/dto" "github.com/tencent-connect/botgo/openapi" ) @@ -150,7 +150,7 @@ func generateGroupMessage(id string, foundItems map[string][]string, messageText base64Encoded := base64.StdEncoding.EncodeToString(imageData) // 上传base64编码的图片并获取其URL - imageURL, err := server.UploadBase64ImageToServer(base64Encoded) + imageURL, err := images.UploadBase64ImageToServer(base64Encoded) if err != nil { log.Printf("Error uploading base64 encoded image: %v", err) return nil @@ -187,7 +187,7 @@ func generateGroupMessage(id string, foundItems map[string][]string, messageText return nil } // 将解码的图片数据转换回base64格式并上传 - imageURL, err := server.UploadBase64ImageToServer(base64.StdEncoding.EncodeToString(fileImageData)) + imageURL, err := images.UploadBase64ImageToServer(base64.StdEncoding.EncodeToString(fileImageData)) if err != nil { log.Printf("failed to upload base64 image: %v", err) return nil diff --git a/handlers/send_msg.go b/handlers/send_msg.go index 844ac1d0..3c118480 100644 --- a/handlers/send_msg.go +++ b/handlers/send_msg.go @@ -13,7 +13,7 @@ import ( "github.com/hoshinonyaruko/gensokyo/config" "github.com/hoshinonyaruko/gensokyo/echo" "github.com/hoshinonyaruko/gensokyo/idmap" - "github.com/hoshinonyaruko/gensokyo/server" + "github.com/hoshinonyaruko/gensokyo/images" "github.com/tencent-connect/botgo/dto" "github.com/tencent-connect/botgo/openapi" ) @@ -227,7 +227,7 @@ func generateMessage(id string, foundItems map[string][]string, messageText stri base64Encoded := base64.StdEncoding.EncodeToString(imageData) // 上传base64编码的图片并获取其URL - imageURL, err := server.UploadBase64ImageToServer(base64Encoded) + imageURL, err := images.UploadBase64ImageToServer(base64Encoded) if err != nil { log.Printf("Error uploading base64 encoded image: %v", err) return nil @@ -262,7 +262,7 @@ func generateMessage(id string, foundItems map[string][]string, messageText stri return nil } // 将解码的图片数据转换回base64格式并上传 - imageURL, err := server.UploadBase64ImageToServer(base64.StdEncoding.EncodeToString(fileImageData)) + imageURL, err := images.UploadBase64ImageToServer(base64.StdEncoding.EncodeToString(fileImageData)) if err != nil { log.Printf("failed to upload base64 image: %v", err) return nil diff --git a/images/upload_api.go b/images/upload_api.go new file mode 100644 index 00000000..e2b287ad --- /dev/null +++ b/images/upload_api.go @@ -0,0 +1,85 @@ +package images + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "net" + "net/http" + "net/url" + "strings" + + "github.com/hoshinonyaruko/gensokyo/config" +) + +// 将base64图片通过lotus转换成url +func UploadBase64ImageToServer(base64Image string) (string, error) { + if config.GetLotusValue() { + serverDir := config.GetServer_dir() + serverPort := config.GetPortValue() + url := fmt.Sprintf("http://%s:%s/uploadpic", serverDir, serverPort) + + resp, err := postImageToServer(base64Image, url) + if err != nil { + return "", err + } + return resp, nil + } + + serverDir := config.GetServer_dir() + if isPublicAddress(serverDir) { + url := fmt.Sprintf("http://127.0.0.1:%s/uploadpic", config.GetPortValue()) + + resp, err := postImageToServer(base64Image, url) + if err != nil { + return "", err + } + return resp, nil + } + return "", errors.New("local server uses a private address; image upload failed") +} + +// 请求图床api(图床就是lolus为false的gensokyo) +func postImageToServer(base64Image, targetURL string) (string, error) { + data := url.Values{} + data.Set("base64Image", base64Image) // 修改字段名以与服务器匹配 + + resp, err := http.PostForm(targetURL, data) + if err != nil { + return "", fmt.Errorf("failed to send request: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("error response from server: %s", resp.Status) + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", fmt.Errorf("failed to read response body: %v", err) + } + + var responseMap map[string]interface{} + if err := json.Unmarshal(body, &responseMap); err != nil { + return "", fmt.Errorf("failed to unmarshal response: %v", err) + } + + if value, ok := responseMap["url"]; ok { + return fmt.Sprintf("%v", value), nil + } + + return "", fmt.Errorf("URL not found in response") +} + +// 判断是否公网ip 填写域名也会被认为是公网,但需要用户自己确保域名正确解析到gensokyo所在的ip地址 +func isPublicAddress(addr string) bool { + if strings.Contains(addr, "localhost") || strings.HasPrefix(addr, "127.") || strings.HasPrefix(addr, "192.168.") { + return false + } + if net.ParseIP(addr) != nil { + return true + } + // If it's not a recognized IP address format, consider it a domain name (public). + return true +} diff --git a/main.go b/main.go index 66bd3eb3..adf1f270 100644 --- a/main.go +++ b/main.go @@ -174,7 +174,7 @@ func main() { r.GET("/getid", server.GetIDHandler) r.POST("/uploadpic", server.UploadBase64ImageHandler(rateLimiter)) r.Static("/channel_temp", "./channel_temp") - r.GET("/ws", server.WsHandlerWithDependencies(api, apiV2)) + r.GET("/ws", server.WsHandlerWithDependencies(api, apiV2, p)) r.Run("0.0.0.0:" + conf.Settings.Port) // 监听0.0.0.0地址的Port端口 } diff --git a/readme.md b/readme.md index f0eb76a7..af0cfeb1 100644 --- a/readme.md +++ b/readme.md @@ -57,7 +57,7 @@ gensokyo兼容 [OneBot-v11](https://github.com/botuniverse/onebot-11) ,并在 - [ ] HTTP API - [ ] 反向 HTTP POST -- [ ] 正向 WebSocket +- [x] 正向 WebSocket - [x] 反向 WebSocket ### 拓展支持 diff --git a/server/uploadpic.go b/server/uploadpic.go index 606a76a0..1295fe87 100644 --- a/server/uploadpic.go +++ b/server/uploadpic.go @@ -6,19 +6,14 @@ import ( "crypto/rand" "encoding/base64" "encoding/hex" - "encoding/json" "errors" "fmt" "image" _ "image/gif" _ "image/jpeg" _ "image/png" - "io/ioutil" - "net" "net/http" - "net/url" "os" - "strings" "time" "github.com/gin-gonic/gin" @@ -165,74 +160,3 @@ func generateRandomMd5() string { md5Hash := md5.Sum(randomBytes) return hex.EncodeToString(md5Hash[:]) } - -// 将base64图片通过lotus转换成url -func UploadBase64ImageToServer(base64Image string) (string, error) { - if config.GetLotusValue() { - serverDir := config.GetServer_dir() - serverPort := config.GetPortValue() - url := fmt.Sprintf("http://%s:%s/uploadpic", serverDir, serverPort) - - resp, err := postImageToServer(base64Image, url) - if err != nil { - return "", err - } - return resp, nil - } - - serverDir := config.GetServer_dir() - if isPublicAddress(serverDir) { - url := fmt.Sprintf("http://127.0.0.1:%s/uploadpic", config.GetPortValue()) - - resp, err := postImageToServer(base64Image, url) - if err != nil { - return "", err - } - return resp, nil - } - return "", errors.New("local server uses a private address; image upload failed") -} - -// 请求图床api(图床就是lolus为false的gensokyo) -func postImageToServer(base64Image, targetURL string) (string, error) { - data := url.Values{} - data.Set("base64Image", base64Image) // 修改字段名以与服务器匹配 - - resp, err := http.PostForm(targetURL, data) - if err != nil { - return "", fmt.Errorf("failed to send request: %v", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return "", fmt.Errorf("error response from server: %s", resp.Status) - } - - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return "", fmt.Errorf("failed to read response body: %v", err) - } - - var responseMap map[string]interface{} - if err := json.Unmarshal(body, &responseMap); err != nil { - return "", fmt.Errorf("failed to unmarshal response: %v", err) - } - - if value, ok := responseMap["url"]; ok { - return fmt.Sprintf("%v", value), nil - } - - return "", fmt.Errorf("URL not found in response") -} - -// 判断是否公网ip 填写域名也会被认为是公网,但需要用户自己确保域名正确解析到gensokyo所在的ip地址 -func isPublicAddress(addr string) bool { - if strings.Contains(addr, "localhost") || strings.HasPrefix(addr, "127.") || strings.HasPrefix(addr, "192.168.") { - return false - } - if net.ParseIP(addr) != nil { - return true - } - // If it's not a recognized IP address format, consider it a domain name (public). - return true -} diff --git a/server/wsserver.go b/server/wsserver.go index 30d9d2d2..5fafaa12 100644 --- a/server/wsserver.go +++ b/server/wsserver.go @@ -5,10 +5,13 @@ import ( "fmt" "log" "net/http" + "strings" "github.com/gin-gonic/gin" "github.com/gorilla/websocket" + "github.com/hoshinonyaruko/gensokyo/Processor" "github.com/hoshinonyaruko/gensokyo/callapi" + "github.com/hoshinonyaruko/gensokyo/config" "github.com/hoshinonyaruko/gensokyo/wsclient" "github.com/tencent-connect/botgo/openapi" ) @@ -25,14 +28,36 @@ var upgrader = websocket.Upgrader{ }, } +// 确保WebSocketServerClient实现了interfaces.WebSocketServerClienter接口 +var _ callapi.WebSocketServerClienter = &WebSocketServerClient{} + // 使用闭包结构 因为gin需要c *gin.Context固定签名 -func WsHandlerWithDependencies(api openapi.OpenAPI, apiV2 openapi.OpenAPI) gin.HandlerFunc { +func WsHandlerWithDependencies(api openapi.OpenAPI, apiV2 openapi.OpenAPI, p *Processor.Processors) gin.HandlerFunc { return func(c *gin.Context) { - wsHandler(api, apiV2, c) + wsHandler(api, apiV2, p, c) } } -func wsHandler(api openapi.OpenAPI, apiV2 openapi.OpenAPI, c *gin.Context) { +func wsHandler(api openapi.OpenAPI, apiV2 openapi.OpenAPI, p *Processor.Processors, c *gin.Context) { + // 先获取请求头中的token + tokenFromHeader := c.Request.Header.Get("Authorization") + if tokenFromHeader == "" || !strings.HasPrefix(tokenFromHeader, "Token ") { + log.Printf("Connection failed due to missing or invalid token. Headers: %v, Provided token: %s", c.Request.Header, tokenFromHeader) + c.JSON(http.StatusUnauthorized, gin.H{"error": "Missing or invalid token"}) + return + } + + // 从 "Token " 后面提取真正的token值 + token := strings.TrimPrefix(tokenFromHeader, "Token ") + + // 使用GetWsServerToken()来获取有效的token + validToken := config.GetWsServerToken() + if token != validToken { + log.Printf("Connection failed due to incorrect token. Headers: %v, Provided token: %s", c.Request.Header, tokenFromHeader) + c.JSON(http.StatusForbidden, gin.H{"error": "Incorrect token"}) + return + } + conn, err := upgrader.Upgrade(c.Writer, c.Request, nil) if err != nil { log.Printf("Failed to set websocket upgrade: %+v", err) @@ -40,8 +65,7 @@ func wsHandler(api openapi.OpenAPI, apiV2 openapi.OpenAPI, c *gin.Context) { } clientIP := c.ClientIP() - headers := c.Request.Header - log.Printf("WebSocket client connected. IP: %s, Headers: %v", clientIP, headers) + log.Printf("WebSocket client connected. IP: %s", clientIP) // 创建WebSocketServerClient实例 client := &WebSocketServerClient{ @@ -49,6 +73,8 @@ func wsHandler(api openapi.OpenAPI, apiV2 openapi.OpenAPI, c *gin.Context) { API: api, APIv2: apiV2, } + // 将此客户端添加到Processor的WsServerClients列表中 + p.WsServerClients = append(p.WsServerClients, client) defer conn.Close() @@ -60,7 +86,7 @@ func wsHandler(api openapi.OpenAPI, apiV2 openapi.OpenAPI, c *gin.Context) { } if messageType == websocket.TextMessage { - processWSMessage(client, p) // 使用WebSocketServerClient而不是直接使用连接 + processWSMessage(client, p) } } } From 2a1026a83bb38c7e3aca7b5b6a49b8e81bbf42e3 Mon Sep 17 00:00:00 2001 From: cosmo Date: Sat, 28 Oct 2023 14:52:19 +0800 Subject: [PATCH 20/27] bugifx --- callapi/callapi.go | 77 +++++++++++++++------------ handlers/get_group_member_list.go | 9 ++-- handlers/get_guild_list.go | 2 +- handlers/get_guild_service_profile.go | 2 +- handlers/get_login_info.go | 2 +- handlers/get_online_clients.go | 2 +- handlers/get_status.go | 2 +- handlers/get_version_info.go | 2 +- handlers/message_parser.go | 10 ++-- handlers/send_group_msg.go | 16 +++--- handlers/send_guild_channel_msg.go | 18 ++++--- handlers/send_msg.go | 26 +++++---- handlers/send_private_msg.go | 26 +++++---- main.go | 2 +- server/wsserver.go | 43 ++++++++++++--- wsclient/ws.go | 75 ++++++++++++++++++++++---- 16 files changed, 215 insertions(+), 99 deletions(-) diff --git a/callapi/callapi.go b/callapi/callapi.go index 7c39a2d7..4140047c 100644 --- a/callapi/callapi.go +++ b/callapi/callapi.go @@ -4,55 +4,66 @@ import ( "encoding/json" "fmt" "log" - "strconv" "github.com/tencent-connect/botgo/openapi" ) -type EchoData struct { - Seq int `json:"seq"` +// onebot发来的action调用信息 +type ActionMessage struct { + Action string `json:"action"` + Params ParamsContent `json:"params"` + Echo interface{} `json:"echo,omitempty"` } -type EchoContent string +func (a *ActionMessage) UnmarshalJSON(data []byte) error { + type Alias ActionMessage -func (e *EchoContent) UnmarshalJSON(data []byte) error { - // 尝试解析为字符串 - var strVal string - if err := json.Unmarshal(data, &strVal); err == nil { - *e = EchoContent(strVal) - return nil + var rawEcho json.RawMessage + temp := &struct { + *Alias + Echo *json.RawMessage `json:"echo,omitempty"` + }{ + Alias: (*Alias)(a), + Echo: &rawEcho, } - // 尝试解析为整数 - var intVal int - if err := json.Unmarshal(data, &intVal); err == nil { - *e = EchoContent(strconv.Itoa(intVal)) - return nil + if err := json.Unmarshal(data, &temp); err != nil { + return err } - // 尝试解析为EchoData结构体 - var echoData EchoData - if err := json.Unmarshal(data, &echoData); err == nil { - *e = EchoContent(strconv.Itoa(echoData.Seq)) - return nil + if rawEcho != nil { + var lastErr error + + var intValue int + if lastErr = json.Unmarshal(rawEcho, &intValue); lastErr == nil { + a.Echo = intValue + return nil + } + + var strValue string + if lastErr = json.Unmarshal(rawEcho, &strValue); lastErr == nil { + a.Echo = strValue + return nil + } + + var arrValue []interface{} + if lastErr = json.Unmarshal(rawEcho, &arrValue); lastErr == nil { + a.Echo = arrValue + return nil + } + + var objValue map[string]interface{} + if lastErr = json.Unmarshal(rawEcho, &objValue); lastErr == nil { + a.Echo = objValue + return nil + } + + return fmt.Errorf("unable to unmarshal echo: %v", lastErr) } - // 如果都不符合预期,设置为空字符串 - *e = "" return nil } -// func (e EchoContent) String() string { -// return string(e) -// } - -// onebot发来的action调用信息 -type ActionMessage struct { - Action string `json:"action"` - Params ParamsContent `json:"params"` - Echo EchoContent `json:"echo,omitempty"` -} - // params类型 type ParamsContent struct { BotQQ string `json:"botqq"` diff --git a/handlers/get_group_member_list.go b/handlers/get_group_member_list.go index 7117c708..cdc553ef 100644 --- a/handlers/get_group_member_list.go +++ b/handlers/get_group_member_list.go @@ -146,9 +146,12 @@ func buildResponse(members []MemberList, echoValue interface{}) map[string]inter case string: log.Printf("Setting echo as string: %s", v) response["echo"] = v - case callapi.EchoContent: - log.Printf("Setting echo from EchoContent: %s", string(v)) - response["echo"] = string(v) + case []interface{}: + log.Printf("Setting echo as array: %v", v) + response["echo"] = v + case map[string]interface{}: + log.Printf("Setting echo as object: %v", v) + response["echo"] = v default: log.Printf("Unknown type for echo: %T", v) } diff --git a/handlers/get_guild_list.go b/handlers/get_guild_list.go index 97036ae7..5019997e 100644 --- a/handlers/get_guild_list.go +++ b/handlers/get_guild_list.go @@ -44,7 +44,7 @@ func getGuildList(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.Open response.Message = "" response.RetCode = 0 response.Status = "ok" - response.Echo = string(message.Echo) // Directly assign the string value + response.Echo = message.Echo // Convert the members slice to a map outputMap := structToMap(response) diff --git a/handlers/get_guild_service_profile.go b/handlers/get_guild_service_profile.go index b4305e09..afd20911 100644 --- a/handlers/get_guild_service_profile.go +++ b/handlers/get_guild_service_profile.go @@ -35,7 +35,7 @@ func getGuildServiceProfile(client callapi.Client, api openapi.OpenAPI, apiv2 op response.Message = "" response.RetCode = 0 response.Status = "ok" - response.Echo = string(message.Echo) // Directly assign the string value + response.Echo = message.Echo // Convert the members slice to a map outputMap := structToMap(response) diff --git a/handlers/get_login_info.go b/handlers/get_login_info.go index 1ac51b61..aaa09704 100644 --- a/handlers/get_login_info.go +++ b/handlers/get_login_info.go @@ -41,7 +41,7 @@ func getLoginInfo(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.Open response.Message = "" response.RetCode = 0 response.Status = "ok" - response.Echo = string(message.Echo) + response.Echo = message.Echo // Convert the members slice to a map outputMap := structToMap(response) diff --git a/handlers/get_online_clients.go b/handlers/get_online_clients.go index 6e82aeae..306c3079 100644 --- a/handlers/get_online_clients.go +++ b/handlers/get_online_clients.go @@ -35,7 +35,7 @@ func getOnlineClients(client callapi.Client, api openapi.OpenAPI, apiv2 openapi. response.Message = "" response.RetCode = 0 response.Status = "ok" - response.Echo = string(message.Echo) + response.Echo = message.Echo // Convert the members slice to a map outputMap := structToMap(response) diff --git a/handlers/get_status.go b/handlers/get_status.go index fc74ace7..91b2edd4 100644 --- a/handlers/get_status.go +++ b/handlers/get_status.go @@ -65,7 +65,7 @@ func getStatus(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.OpenAPI response.Message = "" response.RetCode = 0 response.Status = "ok" - response.Echo = string(message.Echo) // Directly assign the string value + response.Echo = message.Echo outputMap := structToMap(response) diff --git a/handlers/get_version_info.go b/handlers/get_version_info.go index 4431a6b2..407b1345 100644 --- a/handlers/get_version_info.go +++ b/handlers/get_version_info.go @@ -59,7 +59,7 @@ func getVersionInfo(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.Op response.Message = "" response.RetCode = 0 response.Status = "ok" - response.Echo = string(message.Echo) // Directly assign the string value + response.Echo = message.Echo // Convert the members slice to a map outputMap := structToMap(response) diff --git a/handlers/message_parser.go b/handlers/message_parser.go index c2368bf3..25ed7b3f 100644 --- a/handlers/message_parser.go +++ b/handlers/message_parser.go @@ -19,10 +19,10 @@ type ServerResponse struct { Data struct { MessageID int `json:"message_id"` } `json:"data"` - Message string `json:"message"` - RetCode int `json:"retcode"` - Status string `json:"status"` - Echo string `json:"echo"` + Message string `json:"message"` + RetCode int `json:"retcode"` + Status string `json:"status"` + Echo interface{} `json:"echo"` } // 发送成功回执 todo 返回可互转的messageid @@ -30,7 +30,7 @@ func SendResponse(client callapi.Client, err error, message *callapi.ActionMessa // 设置响应值 response := ServerResponse{} response.Data.MessageID = 0 // todo 实现messageid转换 - response.Echo = string(message.Echo) + response.Echo = message.Echo if err != nil { response.Message = err.Error() // 可选:在响应中添加错误消息 //response.RetCode = -1 // 可以是任何非零值,表示出错 diff --git a/handlers/send_group_msg.go b/handlers/send_group_msg.go index 309cbb01..2f11c57b 100644 --- a/handlers/send_group_msg.go +++ b/handlers/send_group_msg.go @@ -24,7 +24,11 @@ func init() { func handleSendGroupMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.OpenAPI, message callapi.ActionMessage) { // 使用 message.Echo 作为key来获取消息类型 - msgType := echo.GetMsgTypeByKey(string(message.Echo)) + var msgType string + if echoStr, ok := message.Echo.(string); ok { + // 当 message.Echo 是字符串类型时执行此块 + msgType = echo.GetMsgTypeByKey(echoStr) + } //如果获取不到 就用user_id获取信息类型 if msgType == "" { @@ -41,12 +45,12 @@ func handleSendGroupMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openap // 解析消息内容 messageText, foundItems := parseMessageContent(message.Params) - // 获取 echo 的值 - echostr := string(message.Echo) - // 使用 echo 获取消息ID - messageID := echo.GetMsgIDByKey(echostr) - log.Println("群组发信息对应的message_id:", messageID) + var messageID string + if echoStr, ok := message.Echo.(string); ok { + messageID = echo.GetMsgIDByKey(echoStr) + log.Println("echo取群组发信息对应的message_id:", messageID) + } log.Println("群组发信息messageText:", messageText) log.Println("foundItems:", foundItems) // 如果messageID为空,通过函数获取 diff --git a/handlers/send_guild_channel_msg.go b/handlers/send_guild_channel_msg.go index 892aed6c..fc61b4d7 100644 --- a/handlers/send_guild_channel_msg.go +++ b/handlers/send_guild_channel_msg.go @@ -22,7 +22,11 @@ func init() { func handleSendGuildChannelMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.OpenAPI, message callapi.ActionMessage) { // 使用 message.Echo 作为key来获取消息类型 - msgType := echo.GetMsgTypeByKey(string(message.Echo)) + var msgType string + if echoStr, ok := message.Echo.(string); ok { + // 当 message.Echo 是字符串类型时执行此块 + msgType = echo.GetMsgTypeByKey(echoStr) + } //如果获取不到 就用user_id获取信息类型 if msgType == "" { @@ -46,12 +50,12 @@ func handleSendGuildChannelMsg(client callapi.Client, api openapi.OpenAPI, apiv2 messageText, foundItems := parseMessageContent(params) channelID := params.ChannelID - // 获取 echo 的值 - echostr := string(message.Echo) - - //messageType := echo.GetMsgTypeByKey(echostr) - messageID := echo.GetMsgIDByKey(echostr) - log.Println("频道发信息对应的message_id:", messageID) + // 使用 echo 获取消息ID + var messageID string + if echoStr, ok := message.Echo.(string); ok { + messageID = echo.GetMsgIDByKey(echoStr) + log.Println("echo取频道发信息对应的message_id:", messageID) + } log.Println("频道发信息messageText:", messageText) log.Println("foundItems:", foundItems) var err error diff --git a/handlers/send_msg.go b/handlers/send_msg.go index 3c118480..734a4673 100644 --- a/handlers/send_msg.go +++ b/handlers/send_msg.go @@ -24,7 +24,11 @@ func init() { func handleSendMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.OpenAPI, message callapi.ActionMessage) { // 使用 message.Echo 作为key来获取消息类型 - msgType := echo.GetMsgTypeByKey(string(message.Echo)) + var msgType string + if echoStr, ok := message.Echo.(string); ok { + // 当 message.Echo 是字符串类型时执行此块 + msgType = echo.GetMsgTypeByKey(echoStr) + } //如果获取不到 就用group_id获取信息类型 if msgType == "" { @@ -46,12 +50,12 @@ func handleSendMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.Ope // 解析消息内容 messageText, foundItems := parseMessageContent(message.Params) - // 获取 echo 的值 - echostr := string(message.Echo) - // 使用 echo 获取消息ID - messageID := echo.GetMsgIDByKey(echostr) - log.Println("群组发信息对应的message_id:", messageID) + var messageID string + if echoStr, ok := message.Echo.(string); ok { + messageID = echo.GetMsgIDByKey(echoStr) + log.Println("echo取群组发信息对应的message_id:", messageID) + } log.Println("群组发信息messageText:", messageText) log.Println("foundItems:", foundItems) // 如果messageID为空,通过函数获取 @@ -159,12 +163,12 @@ func handleSendMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.Ope // 解析消息内容 messageText, foundItems := parseMessageContent(message.Params) - // 获取 echo 的值 - echostr := string(message.Echo) - // 使用 echo 获取消息ID - messageID := echo.GetMsgIDByKey(echostr) - log.Println("私聊发信息对应的message_id:", messageID) + var messageID string + if echoStr, ok := message.Echo.(string); ok { + messageID = echo.GetMsgIDByKey(echoStr) + log.Println("echo取私聊发信息对应的message_id:", messageID) + } log.Println("私聊发信息messageText:", messageText) log.Println("foundItems:", foundItems) diff --git a/handlers/send_private_msg.go b/handlers/send_private_msg.go index 6798eb92..91e32218 100644 --- a/handlers/send_private_msg.go +++ b/handlers/send_private_msg.go @@ -22,7 +22,11 @@ func init() { func handleSendPrivateMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.OpenAPI, message callapi.ActionMessage) { // 使用 message.Echo 作为key来获取消息类型 - msgType := echo.GetMsgTypeByKey(string(message.Echo)) + var msgType string + if echoStr, ok := message.Echo.(string); ok { + // 当 message.Echo 是字符串类型时执行此块 + msgType = echo.GetMsgTypeByKey(echoStr) + } switch msgType { case "group_private": @@ -37,12 +41,12 @@ func handleSendPrivateMsg(client callapi.Client, api openapi.OpenAPI, apiv2 open // 解析消息内容 messageText, foundItems := parseMessageContent(message.Params) - // 获取 echo 的值 - echostr := string(message.Echo) - // 使用 echo 获取消息ID - messageID := echo.GetMsgIDByKey(echostr) - log.Println("私聊发信息对应的message_id:", messageID) + var messageID string + if echoStr, ok := message.Echo.(string); ok { + messageID = echo.GetMsgIDByKey(echoStr) + log.Println("echo取私聊发信息对应的message_id:", messageID) + } log.Println("私聊发信息messageText:", messageText) log.Println("foundItems:", foundItems) @@ -142,10 +146,12 @@ func handleSendGuildChannelPrivateMsg(client callapi.Client, api openapi.OpenAPI } } - // 获取 echo 的值 - echostr := string(message.Echo) - messageID := echo.GetMsgIDByKey(echostr) - log.Println("私聊信息对应的message_id:", messageID) + // 使用 echo 获取消息ID + var messageID string + if echoStr, ok := message.Echo.(string); ok { + messageID = echo.GetMsgIDByKey(echoStr) + log.Println("echo取私聊发信息对应的message_id:", messageID) + } log.Println("私聊信息messageText:", messageText) log.Println("foundItems:", foundItems) // 如果messageID为空,通过函数获取 diff --git a/main.go b/main.go index ad01012a..727f508e 100644 --- a/main.go +++ b/main.go @@ -141,7 +141,7 @@ func main() { }(wsAddr) } - // Collect results + // 获取连接成功后的wsClient for i := 0; i < len(conf.Settings.WsAddress); i++ { select { case wsClient := <-wsClientChan: diff --git a/server/wsserver.go b/server/wsserver.go index 5fafaa12..fd714c72 100644 --- a/server/wsserver.go +++ b/server/wsserver.go @@ -6,6 +6,7 @@ import ( "log" "net/http" "strings" + "time" "github.com/gin-gonic/gin" "github.com/gorilla/websocket" @@ -38,17 +39,29 @@ func WsHandlerWithDependencies(api openapi.OpenAPI, apiV2 openapi.OpenAPI, p *Pr } } +// 处理正向ws客户端的连接 func wsHandler(api openapi.OpenAPI, apiV2 openapi.OpenAPI, p *Processor.Processors, c *gin.Context) { - // 先获取请求头中的token + // 先从请求头中尝试获取token tokenFromHeader := c.Request.Header.Get("Authorization") - if tokenFromHeader == "" || !strings.HasPrefix(tokenFromHeader, "Token ") { - log.Printf("Connection failed due to missing or invalid token. Headers: %v, Provided token: %s", c.Request.Header, tokenFromHeader) - c.JSON(http.StatusUnauthorized, gin.H{"error": "Missing or invalid token"}) - return + token := "" + if tokenFromHeader != "" { + if strings.HasPrefix(tokenFromHeader, "Token ") { + // 从 "Token " 后面提取真正的token值 + token = strings.TrimPrefix(tokenFromHeader, "Token ") + } else if strings.HasPrefix(tokenFromHeader, "Bearer ") { + // 从 "Bearer " 后面提取真正的token值 + token = strings.TrimPrefix(tokenFromHeader, "Bearer ") + } + } else { + // 如果请求头中没有token,则从URL参数中获取 + token = c.Query("access_token") } - // 从 "Token " 后面提取真正的token值 - token := strings.TrimPrefix(tokenFromHeader, "Token ") + if token == "" { + log.Printf("Connection failed due to missing token. Headers: %v", c.Request.Header) + c.JSON(http.StatusUnauthorized, gin.H{"error": "Missing token"}) + return + } // 使用GetWsServerToken()来获取有效的token validToken := config.GetWsServerToken() @@ -76,6 +89,22 @@ func wsHandler(api openapi.OpenAPI, apiV2 openapi.OpenAPI, p *Processor.Processo // 将此客户端添加到Processor的WsServerClients列表中 p.WsServerClients = append(p.WsServerClients, client) + // 获取botID + botID := config.GetAppID() + + // 发送连接成功的消息 + message := map[string]interface{}{ + "meta_event_type": "lifecycle", + "post_type": "meta_event", + "self_id": botID, + "sub_type": "connect", + "time": int(time.Now().Unix()), + } + err = client.SendMessage(message) + if err != nil { + log.Printf("Error sending connection success message: %v\n", err) + } + defer conn.Close() for { diff --git a/wsclient/ws.go b/wsclient/ws.go index 5db10e70..f8c77708 100644 --- a/wsclient/ws.go +++ b/wsclient/ws.go @@ -7,6 +7,7 @@ import ( "log" "net/http" "net/url" + "sync" "time" "github.com/gorilla/websocket" @@ -16,9 +17,14 @@ import ( ) type WebSocketClient struct { - conn *websocket.Conn - api openapi.OpenAPI - apiv2 openapi.OpenAPI + conn *websocket.Conn + api openapi.OpenAPI + apiv2 openapi.OpenAPI + botID uint64 + urlStr string + cancel context.CancelFunc // Add this + mutex sync.Mutex // Mutex for reconnecting + isReconnecting bool } // 发送json信息给onebot应用端 @@ -33,7 +39,6 @@ func (c *WebSocketClient) SendMessage(message map[string]interface{}) error { log.Println("Error sending message:", err) return err } - return nil } @@ -44,13 +49,48 @@ func (c *WebSocketClient) handleIncomingMessages(ctx context.Context, cancel con if err != nil { log.Println("WebSocket connection closed:", err) cancel() // cancel heartbeat goroutine - break + + if !c.isReconnecting { + go c.Reconnect() + } + return } go c.recvMessage(msg) } } +// 断线重连 +func (client *WebSocketClient) Reconnect() { + client.mutex.Lock() + defer client.mutex.Unlock() + + if client.cancel != nil { + client.cancel() // Stop current goroutines + } + + client.isReconnecting = true + defer func() { + client.isReconnecting = false + }() + + for { + time.Sleep(5 * time.Second) + + newClient, err := NewWebSocketClient(client.urlStr, client.botID, client.api, client.apiv2) + if err == nil && newClient != nil { + client.conn = newClient.conn + client.api = newClient.api + client.apiv2 = newClient.apiv2 + client.cancel = newClient.cancel // Update cancel function + + log.Println("Successfully reconnected to WebSocket.") + return + } + log.Println("Failed to reconnect to WebSocket. Retrying in 5 seconds...") + } +} + // 处理信息,调用腾讯api func (c *WebSocketClient) recvMessage(msg []byte) { var message callapi.ActionMessage @@ -100,6 +140,8 @@ func (c *WebSocketClient) sendHeartbeat(ctx context.Context, botID uint64) { } } +const maxRetryAttempts = 5 + // NewWebSocketClient 创建 WebSocketClient 实例,接受 WebSocket URL、botID 和 openapi.OpenAPI 实例 func NewWebSocketClient(urlStr string, botID uint64, api openapi.OpenAPI, apiv2 openapi.OpenAPI) (*WebSocketClient, error) { addresses := config.GetWsAddress() @@ -137,20 +179,31 @@ func NewWebSocketClient(urlStr string, botID uint64, api openapi.OpenAPI, apiv2 var conn *websocket.Conn var err error - // Retry mechanism + retryCount := 0 for { fmt.Println("Dialing URL:", urlStr) conn, _, err = dialer.Dial(urlStr, headers) if err != nil { + retryCount++ + if retryCount > maxRetryAttempts { + log.Printf("Exceeded maximum retry attempts for WebSocket[%v]: %v\n", urlStr, err) + return nil, err + } fmt.Printf("Failed to connect to WebSocket[%v]: %v, retrying in 5 seconds...\n", urlStr, err) time.Sleep(5 * time.Second) // sleep for 5 seconds before retrying } else { - fmt.Printf("成功连接到 %s.\n", urlStr) // 输出连接成功提示 - break // successfully connected, break the loop + fmt.Printf("Successfully connected to %s.\n", urlStr) // 输出连接成功提示 + break // successfully connected, break the loop } } - client := &WebSocketClient{conn: conn, api: api, apiv2: apiv2} + client := &WebSocketClient{ + conn: conn, + api: api, + apiv2: apiv2, + botID: botID, + urlStr: urlStr, + } // Sending initial message similar to your setupB function message := map[string]interface{}{ @@ -172,8 +225,10 @@ func NewWebSocketClient(urlStr string, botID uint64, api openapi.OpenAPI, apiv2 // Starting goroutine for heartbeats and another for listening to messages ctx, cancel := context.WithCancel(context.Background()) + client.cancel = cancel + go client.sendHeartbeat(ctx, botID) - go client.handleIncomingMessages(ctx, cancel) //包含收到信息,调用api部分的代码 + go client.handleIncomingMessages(ctx, cancel) return client, nil } From 2f268286b950d26d0a5ac8ea0b97295756e38cb8 Mon Sep 17 00:00:00 2001 From: cosmo Date: Sat, 28 Oct 2023 15:17:10 +0800 Subject: [PATCH 21/27] fixat --- handlers/message_parser.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/handlers/message_parser.go b/handlers/message_parser.go index 25ed7b3f..935931ab 100644 --- a/handlers/message_parser.go +++ b/handlers/message_parser.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/hoshinonyaruko/gensokyo/callapi" + "github.com/hoshinonyaruko/gensokyo/idmap" "github.com/tencent-connect/botgo/dto" ) @@ -154,7 +155,12 @@ func transformMessageText(messageText string) string { return re.ReplaceAllStringFunc(messageText, func(m string) string { submatches := re.FindStringSubmatch(m) if len(submatches) > 1 { - return "<@!" + submatches[1] + ">" + realUserID, err := idmap.RetrieveRowByIDv2(submatches[1]) + if err != nil { + log.Printf("Error retrieving user ID: %v", err) + return m // 如果出错,返回原始匹配 + } + return "<@!" + realUserID + ">" } return m }) From 007e1af184a109e5a3308b1ac860e70a452c2719 Mon Sep 17 00:00:00 2001 From: cosmo Date: Sat, 28 Oct 2023 15:34:18 +0800 Subject: [PATCH 22/27] bugfix --- handlers/message_parser.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handlers/message_parser.go b/handlers/message_parser.go index 935931ab..5009a1d1 100644 --- a/handlers/message_parser.go +++ b/handlers/message_parser.go @@ -205,7 +205,7 @@ func RevertTransformedText(data interface{}) string { // 获取文件的后缀名 ext := filepath.Ext(attachment.FileName) md5name := strings.TrimSuffix(attachment.FileName, ext) - imageCQ := "[CQ:image,file=" + md5name + ".image,subType=0,url=" + attachment.URL + "]" + imageCQ := "[CQ:image,file=" + md5name + ".image,subType=0,url=" + "http://" + attachment.URL + "]" messageText += imageCQ } } From c41fd778ce029c0659038952dd5f587a24a282ad Mon Sep 17 00:00:00 2001 From: cosmo Date: Sat, 28 Oct 2023 16:33:13 +0800 Subject: [PATCH 23/27] bugfix --- handlers/message_parser.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/handlers/message_parser.go b/handlers/message_parser.go index 5009a1d1..00609383 100644 --- a/handlers/message_parser.go +++ b/handlers/message_parser.go @@ -5,6 +5,7 @@ import ( "log" "path/filepath" "regexp" + "runtime" "strings" "github.com/hoshinonyaruko/gensokyo/callapi" @@ -117,8 +118,15 @@ func parseMessageContent(paramsMessage callapi.ParamsContent) (string, map[strin } // 正则表达式部分 - localImagePattern := regexp.MustCompile(`\[CQ:image,file=file:///([^\]]+?)\]`) - urlImagePattern := regexp.MustCompile(`\[CQ:image,file=http://(.+)\]`) + var localImagePattern *regexp.Regexp + + if runtime.GOOS == "windows" { + localImagePattern = regexp.MustCompile(`\[CQ:image,file=file:///([^\]]+?)\]`) + } else { + localImagePattern = regexp.MustCompile(`\[CQ:image,file=file://([^\]]+?)\]`) + } + + urlImagePattern := regexp.MustCompile(`\[CQ:image,file=https?://(.+)\]`) base64ImagePattern := regexp.MustCompile(`\[CQ:image,file=base64://(.+)\]`) base64RecordPattern := regexp.MustCompile(`\[CQ:record,file=base64://(.+)\]`) From d8354b7f40a87126295ce56023f0e747a255f54d Mon Sep 17 00:00:00 2001 From: cosmo Date: Sat, 28 Oct 2023 16:58:35 +0800 Subject: [PATCH 24/27] test --- echo/echo.go | 6 +++--- handlers/send_guild_channel_msg.go | 5 +++++ handlers/send_msg.go | 5 +++++ handlers/send_private_msg.go | 10 ++++++++++ 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/echo/echo.go b/echo/echo.go index 218e0379..9f820e56 100644 --- a/echo/echo.go +++ b/echo/echo.go @@ -16,13 +16,13 @@ var globalEchoMapping = &EchoMapping{ msgIDMapping: make(map[string]string), } -func (e *EchoMapping) generateKey(appid string, s int64) string { +func (e *EchoMapping) GenerateKey(appid string, s int64) string { return appid + "_" + strconv.FormatInt(s, 10) } // 添加echo对应的类型 func AddMsgType(appid string, s int64, msgType string) { - key := globalEchoMapping.generateKey(appid, s) + key := globalEchoMapping.GenerateKey(appid, s) globalEchoMapping.mu.Lock() defer globalEchoMapping.mu.Unlock() globalEchoMapping.msgTypeMapping[key] = msgType @@ -30,7 +30,7 @@ func AddMsgType(appid string, s int64, msgType string) { // 添加echo对应的messageid func AddMsgID(appid string, s int64, msgID string) { - key := globalEchoMapping.generateKey(appid, s) + key := globalEchoMapping.GenerateKey(appid, s) globalEchoMapping.mu.Lock() defer globalEchoMapping.mu.Unlock() globalEchoMapping.msgIDMapping[key] = msgID diff --git a/handlers/send_guild_channel_msg.go b/handlers/send_guild_channel_msg.go index fc61b4d7..5b6f46ab 100644 --- a/handlers/send_guild_channel_msg.go +++ b/handlers/send_guild_channel_msg.go @@ -56,6 +56,11 @@ func handleSendGuildChannelMsg(client callapi.Client, api openapi.OpenAPI, apiv2 messageID = echo.GetMsgIDByKey(echoStr) log.Println("echo取频道发信息对应的message_id:", messageID) } + // 如果messageID为空,通过函数获取 + if messageID == "" { + messageID = GetMessageIDByUseridOrGroupid(config.GetAppIDStr(), channelID) + log.Println("通过GetMessageIDByUserid函数获取的message_id:", messageID) + } log.Println("频道发信息messageText:", messageText) log.Println("foundItems:", foundItems) var err error diff --git a/handlers/send_msg.go b/handlers/send_msg.go index 734a4673..38716dbb 100644 --- a/handlers/send_msg.go +++ b/handlers/send_msg.go @@ -169,6 +169,11 @@ func handleSendMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.Ope messageID = echo.GetMsgIDByKey(echoStr) log.Println("echo取私聊发信息对应的message_id:", messageID) } + // 如果messageID为空,通过函数获取 + if messageID == "" { + messageID = GetMessageIDByUseridOrGroupid(config.GetAppIDStr(), UserID) + log.Println("通过GetMessageIDByUserid函数获取的message_id:", messageID) + } log.Println("私聊发信息messageText:", messageText) log.Println("foundItems:", foundItems) diff --git a/handlers/send_private_msg.go b/handlers/send_private_msg.go index 91e32218..de98dda0 100644 --- a/handlers/send_private_msg.go +++ b/handlers/send_private_msg.go @@ -47,6 +47,12 @@ func handleSendPrivateMsg(client callapi.Client, api openapi.OpenAPI, apiv2 open messageID = echo.GetMsgIDByKey(echoStr) log.Println("echo取私聊发信息对应的message_id:", messageID) } + // 如果messageID仍然为空,尝试使用config.GetAppID和UserID的组合来获取messageID + // 如果messageID为空,通过函数获取 + if messageID == "" { + messageID = GetMessageIDByUseridOrGroupid(config.GetAppIDStr(), UserID) + log.Println("通过GetMessageIDByUserid函数获取的message_id:", messageID) + } log.Println("私聊发信息messageText:", messageText) log.Println("foundItems:", foundItems) @@ -66,6 +72,8 @@ func handleSendPrivateMsg(client callapi.Client, api openapi.OpenAPI, apiv2 open if err != nil { log.Printf("发送文本私聊信息失败: %v", err) } + //发送成功回执 + SendResponse(client, err, &message) } // 遍历 foundItems 并发送每种信息 @@ -85,6 +93,8 @@ func handleSendPrivateMsg(client callapi.Client, api openapi.OpenAPI, apiv2 open if err != nil { log.Printf("发送 %s 私聊信息失败: %v", key, err) } + //发送成功回执 + SendResponse(client, err, &message) } case "guild_private": //当收到发私信调用 并且来源是频道 From f08d9d7cc8f8c8a797eb4883e659d571301b562c Mon Sep 17 00:00:00 2001 From: cosmo Date: Sat, 28 Oct 2023 17:14:12 +0800 Subject: [PATCH 25/27] test2 --- handlers/get_group_info.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/handlers/get_group_info.go b/handlers/get_group_info.go index 4e89d21d..86b3d383 100644 --- a/handlers/get_group_info.go +++ b/handlers/get_group_info.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "log" - "strconv" "github.com/hoshinonyaruko/gensokyo/callapi" "github.com/hoshinonyaruko/gensokyo/idmap" @@ -28,11 +27,13 @@ type OnebotGroupInfo struct { } func ConvertGuildToGroupInfo(guild *dto.Guild, GroupId string) *OnebotGroupInfo { - groupidstr, err := strconv.ParseInt(GroupId, 10, 64) + // 使用idmap.StoreIDv2映射GroupId到一个int64的值 + groupid64, err := idmap.StoreIDv2(GroupId) if err != nil { - log.Printf("groupidstr: %v", err) + log.Printf("Error storing GroupID: %v", err) return nil } + ts, err := guild.JoinedAt.Time() if err != nil { log.Printf("转换JoinedAt失败: %v", err) @@ -41,7 +42,7 @@ func ConvertGuildToGroupInfo(guild *dto.Guild, GroupId string) *OnebotGroupInfo groupCreateTime := uint32(ts.Unix()) return &OnebotGroupInfo{ - GroupID: groupidstr, + GroupID: groupid64, GroupName: guild.Name, GroupMemo: guild.Desc, GroupCreateTime: groupCreateTime, From 4dd6e159dd650fd7b5ea8cdfe7ab10df53c90787 Mon Sep 17 00:00:00 2001 From: cosmo Date: Tue, 31 Oct 2023 17:11:53 +0800 Subject: [PATCH 26/27] add url service --- callapi/callapi.go | 1 + config/config.go | 39 +++++ config_template.go | 4 + config_template.yml | 6 +- gensokyo.db | Bin 0 -> 32768 bytes go.mod | 2 + go.sum | 4 + handlers/message_parser.go | 15 +- idmap/cMapping.go | 40 ----- main.go | 58 +++++- server/wsserver.go | 15 ++ url/shorturl.go | 349 +++++++++++++++++++++++++++++++++++++ 12 files changed, 489 insertions(+), 44 deletions(-) create mode 100644 gensokyo.db delete mode 100644 idmap/cMapping.go create mode 100644 url/shorturl.go diff --git a/callapi/callapi.go b/callapi/callapi.go index 4140047c..df0f5cb8 100644 --- a/callapi/callapi.go +++ b/callapi/callapi.go @@ -130,6 +130,7 @@ type Client interface { // 为了解决processor和server循环依赖设计的接口 type WebSocketServerClienter interface { SendMessage(message map[string]interface{}) error + Close() error } // 根据action订阅handler处理api diff --git a/config/config.go b/config/config.go index bc1bb8cd..9a3f5ee7 100644 --- a/config/config.go +++ b/config/config.go @@ -37,6 +37,9 @@ type Settings struct { MasterID []string `yaml:"master_id,omitempty"` // 如果需要在群权限判断是管理员是,将user_id填入这里,master_id是一个文本数组 EnableWsServer bool `yaml:"enable_ws_server,omitempty"` //正向ws开关 WsServerToken string `yaml:"ws_server_token,omitempty"` //正向ws token + IdentifyFile bool `yaml:"identify_file"` // 域名校验文件 + Crt string `yaml:"crt"` + Key string `yaml:"key"` } // LoadConfig 从文件中加载配置并初始化单例配置 @@ -182,3 +185,39 @@ func GetWsServerToken() string { } return instance.Settings.WsServerToken } + +// 获取identify_file的值 +func GetIdentifyFile() bool { + mu.Lock() + defer mu.Unlock() + + if instance == nil { + log.Println("Warning: instance is nil when trying to get identify file name.") + return false + } + return instance.Settings.IdentifyFile +} + +// 获取crt路径 +func GetCrtPath() string { + mu.Lock() + defer mu.Unlock() + + if instance == nil { + log.Println("Warning: instance is nil when trying to get crt path.") + return "" + } + return instance.Settings.Crt +} + +// 获取key路径 +func GetKeyPath() string { + mu.Lock() + defer mu.Unlock() + + if instance == nil { + log.Println("Warning: instance is nil when trying to get key path.") + return "" + } + return instance.Settings.Key +} diff --git a/config_template.go b/config_template.go index 5d762815..09f0a64a 100644 --- a/config_template.go +++ b/config_template.go @@ -41,4 +41,8 @@ settings: master_id : ["1","2"] #群场景尚未开放获取管理员和列表能力,手动从日志中获取需要设置为管理,的user_id并填入(适用插件有权限判断场景) enable_ws_server: true #是否启用正向ws服务器 监听server_dir:port/ws ws_server_token : "12345" #正向ws的token 不启动正向ws可忽略 + ws_server_token : "12345" #正向ws的token 不启动正向ws可忽略 + identify_file: true #自动生成域名校验文件,在q.qq.com配置信息URL,在server_dir填入自己已备案域名,正确解析到机器人所在服务器ip地址,机器人即可发送链接 + crt: "" #证书路径 从你的域名服务商或云服务商申请签发SSL证书(qq要求SSL) + key: "" #密钥路径 Apache(crt文件、key文件)示例: "C:\\123.key" \需要双写成\\ ` diff --git a/config_template.yml b/config_template.yml index 2006abc0..7f8129e8 100644 --- a/config_template.yml +++ b/config_template.yml @@ -31,4 +31,8 @@ settings: ws_token: ["",""] #连接wss地址时服务器所需的token,如果是ws,可留空,按顺序一一对应 master_id : ["1","2"] #群场景尚未开放获取管理员和列表能力,手动从日志中获取需要设置为管理,的user_id并填入(适用插件有权限判断场景) enable_ws_server: true #是否启用正向ws服务器 监听server_dir:port/ws - ws_server_token : "12345" #正向ws的token 不启动正向ws可忽略 \ No newline at end of file + ws_server_token : "12345" #正向ws的token 不启动正向ws可忽略 + ws_server_token : "12345" #正向ws的token 不启动正向ws可忽略 + identify_file: true #自动生成域名校验文件,在q.qq.com配置信息URL,在server_dir填入自己已备案域名,正确解析到机器人所在服务器ip地址,机器人即可发送链接 + crt: "" #证书路径 从你的域名服务商或云服务商申请签发SSL证书(qq要求SSL) + key: "" #密钥路径 Apache(crt文件、key文件)示例: "C:\\123.key" \需要双写成\\ \ No newline at end of file diff --git a/gensokyo.db b/gensokyo.db new file mode 100644 index 0000000000000000000000000000000000000000..5ab9bd33a79ac7e2f391150744b1351ca707bf7a GIT binary patch literal 32768 zcmeI(F>b;z6aY{cS_BdUM?k$nkHFpui3>2XApzn9+@lg3cj&^(*pZPvcOW7DNFaue zK|D*zx13nAU#4$6Db?jY$Ku#8T2*~;+nyh%cCK`C?B;mdkBaqnbBOmu0t5&UAV7cs z0RjXF5FkLHIRbBaDF4m>FKYv3pUD4vx&P<%_h+~H`fJY2jD!FI0t5&UAV7cs0RjXF z5D0VYXSra5FkK+ z009C72oNAZ;3^R3{#&X36QH_Rzy4R}`iJGUoXr=j$@qI&)*qHwFnIV-BLM;g2oNAZ zfB*pk1PBly&;)_l=fB_ok8=S{7^5)|AV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly dK!5-N0t5&UAV7cs0RjXF5FkK+0D)H*I0M&06H5R9 literal 0 HcmV?d00001 diff --git a/go.mod b/go.mod index 59a71915..565bbfda 100644 --- a/go.mod +++ b/go.mod @@ -45,4 +45,6 @@ require ( golang.org/x/sys v0.8.0 // indirect golang.org/x/text v0.9.0 // indirect google.golang.org/protobuf v1.30.0 // indirect + mvdan.cc/xurls v1.1.0 + mvdan.cc/xurls/v2 v2.5.0 // indirect ) diff --git a/go.sum b/go.sum index b72202cc..977f04d5 100644 --- a/go.sum +++ b/go.sum @@ -193,4 +193,8 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +mvdan.cc/xurls v1.1.0 h1:kj0j2lonKseISJCiq1Tfk+iTv65dDGCl0rTbanXJGGc= +mvdan.cc/xurls v1.1.0/go.mod h1:TNWuhvo+IqbUCmtUIb/3LJSQdrzel8loVpgFm0HikbI= +mvdan.cc/xurls/v2 v2.5.0 h1:lyBNOm8Wo71UknhUs4QTFUNNMyxy2JEIaKKo0RWOh+8= +mvdan.cc/xurls/v2 v2.5.0/go.mod h1:yQgaGQ1rFtJUzkmKiHYSSfuQxqfYmd//X6PxvholpeE= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/handlers/message_parser.go b/handlers/message_parser.go index 00609383..074cf026 100644 --- a/handlers/message_parser.go +++ b/handlers/message_parser.go @@ -10,7 +10,9 @@ import ( "github.com/hoshinonyaruko/gensokyo/callapi" "github.com/hoshinonyaruko/gensokyo/idmap" + "github.com/hoshinonyaruko/gensokyo/url" "github.com/tencent-connect/botgo/dto" + "mvdan.cc/xurls" //xurls是一个从文本提取url的库 适用于多种场景 ) var BotID string @@ -58,6 +60,7 @@ func SendResponse(client callapi.Client, err error, message *callapi.ActionMessa return nil } +// 信息处理函数 func parseMessageContent(paramsMessage callapi.ParamsContent) (string, map[string][]string) { messageText := "" @@ -156,11 +159,11 @@ func parseMessageContent(paramsMessage callapi.ParamsContent) (string, map[strin return messageText, foundItems } +// at处理和链接处理 func transformMessageText(messageText string) string { // 使用正则表达式来查找所有[CQ:at,qq=数字]的模式 re := regexp.MustCompile(`\[CQ:at,qq=(\d+)\]`) - // 使用正则表达式来替换找到的模式为<@!数字> - return re.ReplaceAllStringFunc(messageText, func(m string) string { + messageText = re.ReplaceAllStringFunc(messageText, func(m string) string { submatches := re.FindStringSubmatch(m) if len(submatches) > 1 { realUserID, err := idmap.RetrieveRowByIDv2(submatches[1]) @@ -172,6 +175,14 @@ func transformMessageText(messageText string) string { } return m }) + + // 使用xurls来查找和替换所有的URL + messageText = xurls.Relaxed.ReplaceAllStringFunc(messageText, func(originalURL string) string { + shortURL := url.GenerateShortURL(originalURL) + // 使用getBaseURL函数来获取baseUrl并与shortURL组合 + return url.GetBaseURL() + "/url/" + shortURL + }) + return messageText } // 处理at和其他定形文到onebotv11格式(cq码) diff --git a/idmap/cMapping.go b/idmap/cMapping.go deleted file mode 100644 index 7975f940..00000000 --- a/idmap/cMapping.go +++ /dev/null @@ -1,40 +0,0 @@ -// 访问idmaps -package idmap - -import ( - "encoding/json" - "fmt" - "net/http" -) - -type CompatibilityMapping struct{} - -// SetID 对外部API进行写操作,返回映射的值 -func (c *CompatibilityMapping) SetID(id string) (int, error) { - return c.getIDByType(id, "1") -} - -// GetOriginalID 使用映射值获取原始值 -func (c *CompatibilityMapping) GetOriginalID(mappedID string) (int, error) { - return c.getIDByType(mappedID, "2") -} - -func (c *CompatibilityMapping) getIDByType(id, typeVal string) (int, error) { - resp, err := http.Get(fmt.Sprintf("http://127.0.0.1:15817/getid?id=%s&type=%s", id, typeVal)) - if err != nil { - return 0, err - } - defer resp.Body.Close() - - var result map[string]int - if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { - return 0, err - } - - value, ok := result["row"] - if !ok { - return 0, fmt.Errorf("row not found in the response") - } - - return value, nil -} diff --git a/main.go b/main.go index 727f508e..969e9d9b 100644 --- a/main.go +++ b/main.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "log" + "net/http" "os" "os/signal" "syscall" @@ -16,6 +17,7 @@ import ( "github.com/hoshinonyaruko/gensokyo/handlers" "github.com/hoshinonyaruko/gensokyo/idmap" "github.com/hoshinonyaruko/gensokyo/server" + "github.com/hoshinonyaruko/gensokyo/url" "github.com/hoshinonyaruko/gensokyo/wsclient" "github.com/gin-gonic/gin" @@ -169,13 +171,49 @@ func main() { //是否启动服务器 shouldStartServer := !conf.Settings.Lotus || conf.Settings.EnableWsServer //如果连接到其他gensokyo,则不需要启动服务器 + var httpServer *http.Server if shouldStartServer { r := gin.Default() r.GET("/getid", server.GetIDHandler) r.POST("/uploadpic", server.UploadBase64ImageHandler(rateLimiter)) r.Static("/channel_temp", "./channel_temp") r.GET("/ws", server.WsHandlerWithDependencies(api, apiV2, p)) - r.Run("0.0.0.0:" + conf.Settings.Port) // 监听0.0.0.0地址的Port端口 + r.POST("/url", url.CreateShortURLHandler) + r.GET("/url/:shortURL", url.RedirectFromShortURLHandler) + if config.GetIdentifyFile() { + appIDStr := config.GetAppIDStr() + fileName := appIDStr + ".json" + r.GET("/"+fileName, func(c *gin.Context) { + content := fmt.Sprintf(`{"bot_appid":%d}`, config.GetAppID()) + c.Header("Content-Type", "application/json") + c.String(200, content) + }) + } + // 创建一个http.Server实例 + httpServer = &http.Server{ + Addr: "0.0.0.0:" + conf.Settings.Port, + Handler: r, + } + // 在一个新的goroutine中启动Gin服务器 + go func() { + if conf.Settings.Port == "443" { + // 使用HTTPS + crtPath := config.GetCrtPath() + keyPath := config.GetKeyPath() + if crtPath == "" || keyPath == "" { + log.Fatalf("crt or key path is missing for HTTPS") + return + } + if err := httpServer.ListenAndServeTLS(crtPath, keyPath); err != nil && err != http.ErrServerClosed { + log.Fatalf("listen (HTTPS): %s\n", err) + } + } else { + // 使用HTTP + if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Fatalf("listen: %s\n", err) + } + } + }() } // 使用通道来等待信号 @@ -193,6 +231,24 @@ func main() { fmt.Printf("Error closing WebSocket connection: %v\n", err) } } + + // 关闭BoltDB数据库 + url.CloseDB() + idmap.CloseDB() + + // 在关闭WebSocket客户端之前 + for _, wsClient := range p.WsServerClients { + if err := wsClient.Close(); err != nil { + log.Printf("Error closing WebSocket server client: %v\n", err) + } + } + + // 使用一个5秒的超时优雅地关闭Gin服务器 + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if err := httpServer.Shutdown(ctx); err != nil { + log.Fatal("Server forced to shutdown:", err) + } } // ReadyHandler 自定义 ReadyHandler 感知连接成功事件 diff --git a/server/wsserver.go b/server/wsserver.go index fd714c72..55b89bd3 100644 --- a/server/wsserver.go +++ b/server/wsserver.go @@ -105,6 +105,17 @@ func wsHandler(api openapi.OpenAPI, apiV2 openapi.OpenAPI, p *Processor.Processo log.Printf("Error sending connection success message: %v\n", err) } + // 在defer语句之前运行 + defer func() { + // 移除客户端从WsServerClients + for i, wsClient := range p.WsServerClients { + if wsClient == client { + p.WsServerClients = append(p.WsServerClients[:i], p.WsServerClients[i+1:]...) + break + } + } + }() + //退出时候的清理 defer conn.Close() for { @@ -142,3 +153,7 @@ func (c *WebSocketServerClient) SendMessage(message map[string]interface{}) erro } return c.Conn.WriteMessage(websocket.TextMessage, msgBytes) } + +func (client *WebSocketServerClient) Close() error { + return client.Conn.Close() +} diff --git a/url/shorturl.go b/url/shorturl.go new file mode 100644 index 00000000..83105a50 --- /dev/null +++ b/url/shorturl.go @@ -0,0 +1,349 @@ +package url + +import ( + "bytes" + "crypto/sha256" + "encoding/base64" + "encoding/hex" + "encoding/json" + "fmt" + "log" + "math/rand" + "net" + "net/http" + "net/url" + "strings" + "time" + + "github.com/boltdb/bolt" + "github.com/gin-gonic/gin" + "github.com/hoshinonyaruko/gensokyo/config" +) + +const ( + bucketName = "shortURLs" +) + +var ( + db *bolt.DB +) + +const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" +const length = 6 + +func generateRandomString() string { + rand.Seed(time.Now().UnixNano()) + result := make([]byte, length) + for i := range result { + result[i] = charset[rand.Intn(len(charset))] + } + return string(result) +} + +func generateHashedString(url string) string { + hash := sha256.Sum256([]byte(url)) + return hex.EncodeToString(hash[:3]) // 取前3个字节,得到6个字符的16进制表示 +} + +func init() { + var err error + db, err = bolt.Open("gensokyo.db", 0600, nil) + if err != nil { + panic(err) + } + + // Ensure bucket exists + err = db.Update(func(tx *bolt.Tx) error { + _, err := tx.CreateBucketIfNotExists([]byte(bucketName)) + if err != nil { + return fmt.Errorf("failed to create or get the bucket: %v", err) + } + return nil + }) + if err != nil { + panic(fmt.Sprintf("Error initializing the database: %v", err)) + } +} + +// 验证链接是否合法 +func isValidURL(toTest string) bool { + parsedURL, err := url.ParseRequestURI(toTest) + if err != nil { + return false + } + if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" { + return false + } + + // 阻止localhost和本地IP地址 + host := parsedURL.Hostname() + localHostnames := []string{"localhost", "127.0.0.1", "::1"} + for _, localHost := range localHostnames { + if host == localHost { + return false + } + } + + // 检查是否是私有IP地址 + return !isPrivateIP(host) +} + +// 检查是否是私有IP地址 +func isPrivateIP(ipStr string) bool { + privateIPBlocks := []string{ + "10.0.0.0/8", + "172.16.0.0/12", + "192.168.0.0/16", + } + ip := net.ParseIP(ipStr) + for _, block := range privateIPBlocks { + _, ipnet, err := net.ParseCIDR(block) + if err != nil { + continue + } + if ipnet.Contains(ip) { + return true + } + } + return false +} + +// 检查和解码可能的Base64编码的URL +func decodeBase64IfNeeded(input string) string { + if len(input)%4 == 0 { // 一个简单的检查来看它是否可能是Base64 + decoded, err := base64.StdEncoding.DecodeString(input) + if err == nil { + return string(decoded) + } + } + return input +} + +// 生成短链接 +func GenerateShortURL(longURL string) string { + if config.GetLotusValue() { + serverDir := config.GetServer_dir() + portValue := config.GetPortValue() + url := fmt.Sprintf("http://%s:%s/url", serverDir, portValue) + + payload := map[string]string{"longURL": longURL} + jsonPayload, err := json.Marshal(payload) + if err != nil { + log.Printf("Error marshaling payload: %v", err) + return "" + } + + resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonPayload)) + if err != nil { + log.Printf("Error while generating short URL: %v", err) + return "" + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + log.Printf("Received non-200 status code: %d from server: %v", resp.StatusCode, url) + return "" + } + + var response map[string]interface{} + err = json.NewDecoder(resp.Body).Decode(&response) + if err != nil { + log.Println("Error decoding response") + return "" + } + + shortURL, ok := response["shortURL"].(string) + if !ok { + log.Println("shortURL not found or not a string in the response") + return "" + } + + return shortURL + + } else { + shortURL := generateHashedString(longURL) + + exists, err := existsInDB(shortURL) + if err != nil { + log.Printf("Error checking if shortURL exists in DB: %v", err) + return "" // 如果有错误, 返回空的短链接 + } + if exists { + for { + shortURL = generateRandomString() + exists, err := existsInDB(shortURL) + if err != nil { + log.Printf("Error checking if shortURL exists in DB: %v", err) + return "" // 如果有错误, 返回空的短链接 + } + if !exists { + break + } + } + } + + // 存储短URL和对应的长URL + err = storeURL(shortURL, longURL) + if err != nil { + log.Printf("Error storing URL in DB: %v", err) + return "" + } + + return shortURL + } +} + +func existsInDB(shortURL string) (bool, error) { + exists := false + err := db.View(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte(bucketName)) + v := b.Get([]byte(shortURL)) + if v != nil { + exists = true + } + return nil + }) + if err != nil { + log.Printf("Error accessing the database: %v", err) // 记录错误 + return false, err + } + return exists, nil +} + +// 从数据库获取短链接 +func getLongURLFromDB(shortURL string) (string, error) { + if config.GetLotusValue() { + serverDir := config.GetServer_dir() + portValue := config.GetPortValue() + url := fmt.Sprintf("http://%s:%s/url/%s", serverDir, portValue, shortURL) + + resp, err := http.Get(url) + if err != nil { + return "", err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + log.Printf("Received non-200 status code: %d while fetching long URL from server: %v", resp.StatusCode, url) + return "", fmt.Errorf("error fetching long URL from remote server with status code: %d", resp.StatusCode) + } + + var response map[string]interface{} + err = json.NewDecoder(resp.Body).Decode(&response) + if err != nil { + return "", fmt.Errorf("error decoding response from server") + } + return response["longURL"].(string), nil + } else { + var longURL string + err := db.View(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte(bucketName)) + v := b.Get([]byte(shortURL)) + if v == nil { + return fmt.Errorf("URL not found") + } + longURL = string(v) + return nil + }) + return longURL, err + } +} + +// storeURL 存储长URL和对应的短URL +func storeURL(shortURL, longURL string) error { + return db.Update(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte(bucketName)) + return b.Put([]byte(shortURL), []byte(longURL)) + }) +} + +// 安全性检查 +func isMalicious(decoded string) bool { + lowerDecoded := strings.ToLower(decoded) + + // 检查javascript协议,用于防止XSS + if strings.HasPrefix(lowerDecoded, "javascript:") { + return true + } + + // 检查data协议,可能被用于各种攻击 + if strings.HasPrefix(lowerDecoded, "data:") { + return true + } + + // 检查常见的HTML标签,这可能用于指示XSS攻击 + for _, tag := range []string{" Date: Tue, 31 Oct 2023 20:22:59 +0800 Subject: [PATCH 27/27] add url service --- Processor/ProcessChannelDirectMessage.go | 19 ++++++++++--------- Processor/ProcessGroupMessage.go | 2 -- Processor/ProcessGuildATMessage.go | 22 +++++++++++----------- Processor/ProcessGuildNormalMessage.go | 22 +++++++++++----------- handlers/get_group_info.go | 18 ++++++++---------- handlers/get_group_member_list.go | 7 ++++++- handlers/send_group_msg.go | 15 +++++++-------- handlers/send_msg.go | 15 +++++++-------- handlers/send_private_msg.go | 8 ++++++-- handlers/set_group_ban.go | 10 +++++++--- handlers/set_group_whole_ban.go | 8 ++++++-- idmap/service.go | 11 +++++++++-- url/shorturl.go | 21 ++------------------- 13 files changed, 90 insertions(+), 88 deletions(-) diff --git a/Processor/ProcessChannelDirectMessage.go b/Processor/ProcessChannelDirectMessage.go index 3c49ffc2..57f545ac 100644 --- a/Processor/ProcessChannelDirectMessage.go +++ b/Processor/ProcessChannelDirectMessage.go @@ -41,6 +41,7 @@ func (p *Processors) ProcessChannelDirectMessage(data *dto.WSDirectMessageData) if err != nil { log.Fatalf("Error storing ID: %v", err) } + //将真实id写入数据库,可取出ChannelID idmap.WriteConfigv2(data.Author.ID, "channel_id", data.ChannelID) //将channelid写入数据库,可取出guild_id @@ -177,20 +178,20 @@ func (p *Processors) ProcessChannelDirectMessage(data *dto.WSDirectMessageData) p.BroadcastMessageToAll(msgMap) } else { //将频道信息转化为群信息(特殊需求情况下) - //将channelid写入ini,可取出guild_id - idmap.WriteConfigv2(data.ChannelID, "guild_id", data.GuildID) + //将channelid写入bolt,可取出guild_id + ChannelID64, err := idmap.StoreIDv2(data.ChannelID) + if err != nil { + log.Printf("Error storing ID: %v", err) + return nil + } + //转成int再互转 + idmap.WriteConfigv2(fmt.Sprint(ChannelID64), "guild_id", data.GuildID) //转换at messageText := handlers.RevertTransformedText(data) //转换appid AppIDString := strconv.FormatUint(p.Settings.AppID, 10) //构造echo echostr := AppIDString + "_" + strconv.FormatInt(s, 10) - //把频道号作为群号 - channelIDInt, err := strconv.Atoi(data.ChannelID) - if err != nil { - // handle error, perhaps return it - return fmt.Errorf("failed to convert ChannelID to int: %v", err) - } //映射str的userid到int userid64, err := idmap.StoreIDv2(data.Author.ID) if err != nil { @@ -214,7 +215,7 @@ func (p *Processors) ProcessChannelDirectMessage(data *dto.WSDirectMessageData) RawMessage: messageText, Message: segmentedMessages, MessageID: messageID, - GroupID: int64(channelIDInt), + GroupID: ChannelID64, MessageType: "group", PostType: "message", SelfID: int64(p.Settings.AppID), diff --git a/Processor/ProcessGroupMessage.go b/Processor/ProcessGroupMessage.go index 78d31566..b8a2a2ea 100644 --- a/Processor/ProcessGroupMessage.go +++ b/Processor/ProcessGroupMessage.go @@ -21,8 +21,6 @@ func (p *Processors) ProcessGroupMessage(data *dto.WSGroupATMessageData) error { // 获取s s := client.GetGlobalS() - idmap.WriteConfigv2(data.ChannelID, "guild_id", data.GuildID) - // 转换at messageText := handlers.RevertTransformedText(data) diff --git a/Processor/ProcessGuildATMessage.go b/Processor/ProcessGuildATMessage.go index 70ffb2b7..5ff0a5bf 100644 --- a/Processor/ProcessGuildATMessage.go +++ b/Processor/ProcessGuildATMessage.go @@ -105,20 +105,20 @@ func (p *Processors) ProcessGuildATMessage(data *dto.WSATMessageData) error { //将频道转化为一个群 //获取s s := client.GetGlobalS() - //将channelid写入ini,可取出guild_id todo 比ini更好的储存方式 - idmap.WriteConfigv2(data.ChannelID, "guild_id", data.GuildID) + //将channelid写入ini,可取出guild_id + ChannelID64, err := idmap.StoreIDv2(data.ChannelID) + if err != nil { + log.Printf("Error storing ID: %v", err) + return nil + } + //转成int再互转 + idmap.WriteConfigv2(fmt.Sprint(ChannelID64), "guild_id", data.GuildID) //转换at和图片 messageText := handlers.RevertTransformedText(data) //转换appid AppIDString := strconv.FormatUint(p.Settings.AppID, 10) //构造echo echostr := AppIDString + "_" + strconv.FormatInt(s, 10) - //把频道号作为群号 - channelIDInt, err := strconv.Atoi(data.ChannelID) - if err != nil { - // handle error, perhaps return it - return fmt.Errorf("failed to convert ChannelID to int: %v", err) - } //映射str的userid到int userid64, err := idmap.StoreIDv2(data.Author.ID) if err != nil { @@ -142,7 +142,7 @@ func (p *Processors) ProcessGuildATMessage(data *dto.WSATMessageData) error { RawMessage: messageText, Message: segmentedMessages, MessageID: messageID, - GroupID: int64(channelIDInt), + GroupID: ChannelID64, MessageType: "group", PostType: "message", SelfID: int64(p.Settings.AppID), @@ -178,8 +178,8 @@ func (p *Processors) ProcessGuildATMessage(data *dto.WSATMessageData) error { echo.AddMsgID(AppIDString, s, data.ID) echo.AddMsgType(AppIDString, s, "guild") //为不支持双向echo的ob服务端映射 - echo.AddMsgID(AppIDString, int64(channelIDInt), data.ID) - echo.AddMsgType(AppIDString, int64(channelIDInt), "guild") + echo.AddMsgID(AppIDString, ChannelID64, data.ID) + echo.AddMsgType(AppIDString, ChannelID64, "guild") //储存当前群或频道号的类型 idmap.WriteConfigv2(data.ChannelID, "type", "guild") diff --git a/Processor/ProcessGuildNormalMessage.go b/Processor/ProcessGuildNormalMessage.go index 370a0178..a7ffe172 100644 --- a/Processor/ProcessGuildNormalMessage.go +++ b/Processor/ProcessGuildNormalMessage.go @@ -104,20 +104,20 @@ func (p *Processors) ProcessGuildNormalMessage(data *dto.WSMessageData) error { //将频道转化为一个群 //获取s s := client.GetGlobalS() - //将channelid写入ini,可取出guild_id todo 比ini更好的储存方式 - idmap.WriteConfigv2(data.ChannelID, "guild_id", data.GuildID) + //将channelid写入ini,可取出guild_id + ChannelID64, err := idmap.StoreIDv2(data.ChannelID) + if err != nil { + log.Printf("Error storing ID: %v", err) + return nil + } + //转成int再互转 + idmap.WriteConfigv2(fmt.Sprint(ChannelID64), "guild_id", data.GuildID) //转换at messageText := handlers.RevertTransformedText(data) //转换appid AppIDString := strconv.FormatUint(p.Settings.AppID, 10) //构造echo echostr := AppIDString + "_" + strconv.FormatInt(s, 10) - //把频道号作为群号 - channelIDInt, err := strconv.Atoi(data.ChannelID) - if err != nil { - // handle error, perhaps return it - return fmt.Errorf("failed to convert ChannelID to int: %v", err) - } //映射str的userid到int userid64, err := idmap.StoreIDv2(data.Author.ID) if err != nil { @@ -141,7 +141,7 @@ func (p *Processors) ProcessGuildNormalMessage(data *dto.WSMessageData) error { RawMessage: messageText, Message: segmentedMessages, MessageID: messageID, - GroupID: int64(channelIDInt), + GroupID: ChannelID64, MessageType: "group", PostType: "message", SelfID: int64(p.Settings.AppID), @@ -177,8 +177,8 @@ func (p *Processors) ProcessGuildNormalMessage(data *dto.WSMessageData) error { echo.AddMsgID(AppIDString, s, data.ID) echo.AddMsgType(AppIDString, s, "guild") //为不支持双向echo的ob服务端映射 - echo.AddMsgID(AppIDString, int64(channelIDInt), data.ID) - echo.AddMsgType(AppIDString, int64(channelIDInt), "guild") + echo.AddMsgID(AppIDString, ChannelID64, data.ID) + echo.AddMsgType(AppIDString, ChannelID64, "guild") //储存当前群或频道号的类型 idmap.WriteConfigv2(data.ChannelID, "type", "guild") diff --git a/handlers/get_group_info.go b/handlers/get_group_info.go index 86b3d383..0a0e863a 100644 --- a/handlers/get_group_info.go +++ b/handlers/get_group_info.go @@ -3,6 +3,7 @@ package handlers import ( "context" "encoding/json" + "fmt" "log" "github.com/hoshinonyaruko/gensokyo/callapi" @@ -57,8 +58,13 @@ func handleGetGroupInfo(client callapi.Client, api openapi.OpenAPI, apiv2 openap //用GroupID给ChannelID赋值,因为我们是把频道虚拟成了群 ChannelID := params.GroupID + // 使用RetrieveRowByIDv2还原真实的ChannelID + RChannelID, err := idmap.RetrieveRowByIDv2(ChannelID.(string)) + if err != nil { + fmt.Printf("error retrieving real ChannelID: %v", err) + } //读取ini 通过ChannelID取回之前储存的guild_id - value, err := idmap.ReadConfigv2(ChannelID.(string), "guild_id") + value, err := idmap.ReadConfigv2(RChannelID, "guild_id") if err != nil { log.Printf("handleGetGroupInfo:Error reading config: %v\n", err) return @@ -71,16 +77,8 @@ func handleGetGroupInfo(client callapi.Client, api openapi.OpenAPI, apiv2 openap log.Printf("获取频道信息失败: %v", err) return } - //用group_id还原出channelid 这是虚拟成群的私聊信息 - message.Params.ChannelID = message.Params.GroupID.(string) - //读取ini 通过ChannelID取回之前储存的guild_id - GroupId, err := idmap.ReadConfigv2(message.Params.ChannelID, "guild_id") - if err != nil { - log.Printf("Error reading config: %v", err) - return - } - groupInfo := ConvertGuildToGroupInfo(guild, GroupId) + groupInfo := ConvertGuildToGroupInfo(guild, guildID) groupInfoMap := structToMap(groupInfo) // 打印groupInfoMap的内容 diff --git a/handlers/get_group_member_list.go b/handlers/get_group_member_list.go index cdc553ef..c32edf78 100644 --- a/handlers/get_group_member_list.go +++ b/handlers/get_group_member_list.go @@ -52,8 +52,13 @@ func getGroupMemberList(client callapi.Client, api openapi.OpenAPI, apiv2 openap //要把group_id还原成guild_id //用group_id还原出channelid 这是虚拟成群的私聊信息 message.Params.ChannelID = message.Params.GroupID.(string) + // 使用RetrieveRowByIDv2还原真实的ChannelID + RChannelID, err := idmap.RetrieveRowByIDv2(message.Params.ChannelID) + if err != nil { + log.Printf("error retrieving real ChannelID: %v", err) + } //读取ini 通过ChannelID取回之前储存的guild_id - value, err := idmap.ReadConfigv2(message.Params.ChannelID, "guild_id") + value, err := idmap.ReadConfigv2(RChannelID, "guild_id") if err != nil { log.Printf("Error reading config: %v", err) return diff --git a/handlers/send_group_msg.go b/handlers/send_group_msg.go index 2f11c57b..a962794a 100644 --- a/handlers/send_group_msg.go +++ b/handlers/send_group_msg.go @@ -112,20 +112,19 @@ func handleSendGroupMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openap case "guild": //用GroupID给ChannelID赋值,因为我们是把频道虚拟成了群 message.Params.ChannelID = message.Params.GroupID.(string) - //读取ini 通过ChannelID取回之前储存的guild_id - // value, err := idmap.ReadConfigv2(message.Params.ChannelID, "guild_id") - // if err != nil { - // log.Printf("Error reading config: %v", err) - // return - // } - //这一句是group_private的逻辑,发频道信息用的是channelid,只有频道private需要guildid才需要这些逻辑 + //这一句是group_private的逻辑,发频道信息用的是channelid //message.Params.GroupID = value handleSendGuildChannelMsg(client, api, apiv2, message) case "guild_private": //用group_id还原出channelid 这是虚拟成群的私聊信息 message.Params.ChannelID = message.Params.GroupID.(string) + // 使用RetrieveRowByIDv2还原真实的ChannelID + RChannelID, err := idmap.RetrieveRowByIDv2(message.Params.ChannelID) + if err != nil { + log.Printf("error retrieving real ChannelID: %v", err) + } //读取ini 通过ChannelID取回之前储存的guild_id - value, err := idmap.ReadConfigv2(message.Params.ChannelID, "guild_id") + value, err := idmap.ReadConfigv2(RChannelID, "guild_id") if err != nil { log.Printf("Error reading config: %v", err) return diff --git a/handlers/send_msg.go b/handlers/send_msg.go index 38716dbb..89dc8b19 100644 --- a/handlers/send_msg.go +++ b/handlers/send_msg.go @@ -117,13 +117,6 @@ func handleSendMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.Ope case "guild": //用GroupID给ChannelID赋值,因为我们是把频道虚拟成了群 message.Params.ChannelID = message.Params.GroupID.(string) - //读取ini 通过ChannelID取回之前储存的guild_id - // value, err := idmap.ReadConfigv2(message.Params.ChannelID, "guild_id") - // if err != nil { - // log.Printf("Error reading config: %v", err) - // return - // } - // message.Params.GroupID = value handleSendGuildChannelMsg(client, api, apiv2, message) case "guild_private": //send_msg比具体的send_xxx少一层,其包含的字段类型在虚拟化场景已经失去作用 @@ -137,8 +130,14 @@ func handleSendMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.Ope // 先尝试将GroupID断言为字符串 if channelID, ok := message.Params.GroupID.(string); ok && channelID != "" { channelIDPtr = &channelID + // 使用RetrieveRowByIDv2还原真实的UserID + RChannelID, err := idmap.RetrieveRowByIDv2(*channelIDPtr) + if err != nil { + log.Printf("error retrieving real ChannelID: %v", err) + return + } // 读取bolt数据库 通过ChannelID取回之前储存的guild_id - if value, err := idmap.ReadConfigv2(channelID, "guild_id"); err == nil && value != "" { + if value, err := idmap.ReadConfigv2(RChannelID, "guild_id"); err == nil && value != "" { GuildidPtr = &value } else { log.Printf("Error reading config: %v", err) diff --git a/handlers/send_private_msg.go b/handlers/send_private_msg.go index de98dda0..b3b0cc80 100644 --- a/handlers/send_private_msg.go +++ b/handlers/send_private_msg.go @@ -251,9 +251,13 @@ func getGuildIDFromMessage(message callapi.ActionMessage) (string, string, error if err != nil { return "", "", fmt.Errorf("error reading channel_id: %v", err) } - + // 使用RetrieveRowByIDv2还原真实的ChannelID + RChannelID, err := idmap.RetrieveRowByIDv2(channelID) + if err != nil { + log.Printf("error retrieving real UserID: %v", err) + } // 使用channelID作为sectionName从数据库中获取guild_id - guildID, err := idmap.ReadConfigv2(channelID, "guild_id") + guildID, err := idmap.ReadConfigv2(RChannelID, "guild_id") if err != nil { return "", "", fmt.Errorf("error reading guild_id: %v", err) } diff --git a/handlers/set_group_ban.go b/handlers/set_group_ban.go index affb142b..93cb0e20 100644 --- a/handlers/set_group_ban.go +++ b/handlers/set_group_ban.go @@ -20,9 +20,13 @@ func setGroupBan(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.OpenA // 从message中获取group_id和UserID groupID := message.Params.GroupID.(string) receivedUserID := message.Params.UserID.(string) - - // 根据group_id读取guild_id - guildID, err := idmap.ReadConfigv2(groupID, "guild_id") + // 使用RetrieveRowByIDv2还原真实的ChannelID + RChannelID, err := idmap.RetrieveRowByIDv2(groupID) + if err != nil { + log.Printf("error retrieving real UserID: %v", err) + } + // 根据RChannelID读取guild_id + guildID, err := idmap.ReadConfigv2(RChannelID, "guild_id") if err != nil { log.Printf("Error reading config for guild_id: %v", err) return diff --git a/handlers/set_group_whole_ban.go b/handlers/set_group_whole_ban.go index 771dae55..bf010ccc 100644 --- a/handlers/set_group_whole_ban.go +++ b/handlers/set_group_whole_ban.go @@ -17,9 +17,13 @@ func init() { func setGroupWholeBan(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.OpenAPI, message callapi.ActionMessage) { // 从message中获取group_id groupID := message.Params.GroupID.(string) - + // 使用RetrieveRowByIDv2还原真实的ChannelID + RChannelID, err := idmap.RetrieveRowByIDv2(groupID) + if err != nil { + log.Printf("error retrieving real UserID: %v", err) + } // 根据group_id读取guild_id - guildID, err := idmap.ReadConfigv2(groupID, "guild_id") + guildID, err := idmap.ReadConfigv2(RChannelID, "guild_id") if err != nil { log.Printf("Error reading config for guild_id: %v", err) return diff --git a/idmap/service.go b/idmap/service.go index d4f6202f..d07ff6cc 100644 --- a/idmap/service.go +++ b/idmap/service.go @@ -175,11 +175,18 @@ func WriteConfig(sectionName, keyName, value string) error { return db.Update(func(tx *bolt.Tx) error { b, err := tx.CreateBucketIfNotExists([]byte(ConfigBucket)) if err != nil { - return err + log.Printf("Error creating or accessing bucket: %v", err) + return fmt.Errorf("failed to access or create bucket %s: %w", ConfigBucket, err) } key := joinSectionAndKey(sectionName, keyName) - return b.Put(key, []byte(value)) + err = b.Put(key, []byte(value)) + if err != nil { + log.Printf("Error putting data into bucket with key %s: %v", key, err) + return fmt.Errorf("failed to put data into bucket with key %s: %w", key, err) + } + //log.Printf("Data saved successfully with key %s,value %s", key, value) + return nil }) } diff --git a/url/shorturl.go b/url/shorturl.go index 83105a50..119e4ec3 100644 --- a/url/shorturl.go +++ b/url/shorturl.go @@ -296,15 +296,7 @@ func CreateShortURLHandler(c *gin.Context) { // Construct baseUrl serverDir := config.GetServer_dir() - portValue := config.GetPortValue() - protocol := "http" - if portValue == "443" { - protocol = "https" - } - baseUrl := protocol + "://" + serverDir - if portValue != "80" && portValue != "443" && portValue != "" { - baseUrl += ":" + portValue - } + baseUrl := "https://" + serverDir c.JSON(http.StatusOK, gin.H{"shortURL": baseUrl + "/url/" + shortURL}) } @@ -312,16 +304,7 @@ func CreateShortURLHandler(c *gin.Context) { // 短链接baseurl func GetBaseURL() string { serverDir := config.GetServer_dir() - portValue := config.GetPortValue() - protocol := "http" - if portValue == "443" { - protocol = "https" - } - baseUrl := protocol + "://" + serverDir - if portValue != "80" && portValue != "443" && portValue != "" { - baseUrl += ":" + portValue - } - return baseUrl + return "https://" + serverDir } // RedirectFromShortURLHandler