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..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" ) @@ -19,10 +20,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 +31,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 // 可以是任何非零值,表示出错 @@ -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 }) 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 }