diff --git a/.github/workflows/cross_compile.yml b/.github/workflows/cross_compile.yml index eeb57fce..9a4cfd6e 100644 --- a/.github/workflows/cross_compile.yml +++ b/.github/workflows/cross_compile.yml @@ -100,7 +100,7 @@ jobs: go build -ldflags="-s -w" -o output/gensokyo-${{ matrix.os }}-${{ matrix.goarch }} fi - - name: Compress executable files with UPX (except for gensokyo-android-arm64) + - name: Compress executable files with UPX (except for gensokyo-android-arm64 and macOS) run: | sudo apt-get update sudo apt-get install -y upx @@ -109,14 +109,14 @@ jobs: else FILENAME="output/gensokyo-${{ matrix.os }}-${{ matrix.goarch }}" fi - if [[ "${{ matrix.os }}" == "android" && "${{ matrix.goarch }}" == "arm64" ]]; then + if [[ "${{ matrix.os }}" == "android" && "${{ matrix.goarch }}" == "arm64" ]] || [[ "${{ matrix.os }}" == "macos" ]]; then echo "Skipping UPX compression for $FILENAME" else upx --best --lzma "$FILENAME" - fi + fi - name: Upload artifacts - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: gensokyo-${{ matrix.os }}-${{ matrix.goarch }} path: output/gensokyo-${{ matrix.os }}-${{ matrix.goarch }}${{ endsWith(matrix.os, 'windows') && '.exe' || '' }} diff --git a/Processor/ProcessGroupMessage.go b/Processor/ProcessGroupMessage.go index 92ed371c..8e42049f 100644 --- a/Processor/ProcessGroupMessage.go +++ b/Processor/ProcessGroupMessage.go @@ -318,7 +318,7 @@ func (p *Processors) ProcessGroupMessage(data *dto.WSGroupATMessageData) error { echo.AddMsgID(AppIDString, s, data.ID) echo.AddMsgType(AppIDString, s, "group") //储存当前群或频道号的类型 - //idmap.WriteConfigv2(data.GroupID, "type", "group") + go idmap.WriteConfigv2(data.GroupID, "type", "group") //懒message_id池 echo.AddLazyMessageId(data.GroupID, data.ID, time.Now()) //懒message_id池 diff --git a/handlers/get_friend_list.go b/handlers/get_friend_list.go index 70022c11..92733ce8 100644 --- a/handlers/get_friend_list.go +++ b/handlers/get_friend_list.go @@ -2,6 +2,7 @@ package handlers import ( "encoding/json" + "regexp" "github.com/hoshinonyaruko/gensokyo/callapi" "github.com/hoshinonyaruko/gensokyo/idmap" @@ -25,14 +26,27 @@ type APIOutput struct { func HandleGetFriendList(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.OpenAPI, message callapi.ActionMessage) (string, error) { var output APIOutput + // 检查字符串是否是数字 + isNumeric := func(s string) bool { + return regexp.MustCompile(`^\d+$`).MatchString(s) + } + // 从数据库获取所有用户信息 users, err := idmap.ListAllUsers() if err != nil { mylog.Errorf("Failed to list users: %v", err) } + // 过滤用户ID是数字的用户 + filteredUsers := []structs.FriendData{} // 假设 User 是你用户结构体的类型 + for _, user := range users { + if !isNumeric(user.UserID) { + filteredUsers = append(filteredUsers, user) + } + } + // 添加数据库中读取的用户数据到output.Data - output.Data = append(output.Data, users...) + output.Data = append(output.Data, filteredUsers...) output.Message = "" output.RetCode = 0 diff --git a/handlers/get_group_list.go b/handlers/get_group_list.go index f947b968..851035c8 100644 --- a/handlers/get_group_list.go +++ b/handlers/get_group_list.go @@ -3,6 +3,7 @@ package handlers import ( "context" "encoding/json" + "fmt" "regexp" "strconv" "time" @@ -189,7 +190,7 @@ func GetGroupList(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.Open for _, idStr := range groupIDs { var originalGroupID string if isNumeric(idStr) { - originalGroupID, _ = idmap.RetrieveRowByIDv2(idStr) + continue } else { originalGroupID = idStr } @@ -216,17 +217,27 @@ func GetGroupList(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.Open outputMap = structToMap(groupListString) } - //mylog.Printf("getGroupList(频道): %+v\n", outputMap) + //fmt.Printf("getGroupList(频道): %+v\n", outputMap) + fmt.Printf("getGroupList(数量): %+v\n", len(outputMap["data"].([]interface{}))) err = client.SendMessage(outputMap) if err != nil { mylog.Printf("error sending group info via wsclient: %v", err) } - result, err := json.Marshal(groupList) - if err != nil { - mylog.Printf("Error marshaling data: %v", err) - return "", nil + var result []byte + if !config.GetStringOb11() { + result, err = json.Marshal(groupList) + if err != nil { + mylog.Printf("Error marshaling data: %v", err) + return "", nil + } + } else { + result, err = json.Marshal(groupListString) + if err != nil { + mylog.Printf("Error marshaling data: %v", err) + return "", nil + } } //mylog.Printf("get_group_list: %s", result) diff --git a/handlers/message_parser.go b/handlers/message_parser.go index 7337083d..829577a4 100644 --- a/handlers/message_parser.go +++ b/handlers/message_parser.go @@ -53,6 +53,21 @@ type ServerResponse struct { Echo interface{} `json:"echo"` } +// 定义响应结构体 +type ServerResponseSB struct { + Data struct { + MessageID string `json:"message_id"` + } `json:"data"` + Message string `json:"message"` + GroupID string `json:"group_id,omitempty"` + UserID string `json:"user_id,omitempty"` + ChannelID string `json:"channel_id,omitempty"` + GuildID string `json:"guild_id,omitempty"` + RetCode int `json:"retcode"` + Status string `json:"status"` + Echo interface{} `json:"echo"` +} + // 定义了一个符合 Client 接口的 HttpAPIClient 结构体 type HttpAPIClient struct { // 可添加所需字段 @@ -248,6 +263,52 @@ func SendResponse(client callapi.Client, err error, message *callapi.ActionMessa return string(jsonResponse), nil } +// 发送成功回执 todo 返回可互转的messageid 实现群撤回api +func SendResponseSB(client callapi.Client, err error, message *callapi.ActionMessage, resp *dto.GroupMessageResponse, api openapi.OpenAPI, apiv2 openapi.OpenAPI) (string, error) { + // 设置响应值 + response := ServerResponseSB{} + if resp != nil { + + response.Data.MessageID = resp.Message.ID + + } else { + // Default ID handling + response.Data.MessageID = "" + } + + response.GroupID = message.Params.GroupID.(string) + response.Echo = 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) + // 将map转换为JSON字符串 + jsonResponse, jsonErr := json.Marshal(outputMap) + if jsonErr != nil { + log.Printf("Error marshaling response to JSON: %v", jsonErr) + return "", jsonErr + } + //发送给ws 客户端 + sendErr := client.SendMessage(outputMap) + if sendErr != nil { + mylog.Printf("Error sending message via client: %v", sendErr) + return "", sendErr + } + + mylog.Printf("发送成功回执: %+v", string(jsonResponse)) + return string(jsonResponse), nil +} + // 发送成功回执 todo 返回可互转的messageid 实现频道撤回api func SendGuildResponse(client callapi.Client, err error, message *callapi.ActionMessage, resp *dto.Message) (string, error) { var messageID64 int64 @@ -480,7 +541,33 @@ func parseMessageContent(paramsMessage callapi.ParamsContent, message callapi.Ac } case "image": fileContent, _ := segmentMap["data"].(map[string]interface{})["file"].(string) - foundItems["image"] = append(foundItems["image"], fileContent) + + // 检查是否为 Base64 图片 + if strings.HasPrefix(fileContent, "base64://") { + // 去掉 "base64://" 头部 + cleanContent := strings.TrimPrefix(fileContent, "base64://") + foundItems["base64_image"] = append(foundItems["base64_image"], cleanContent) + } else if strings.HasPrefix(fileContent, "http://") { + // HTTP 图片,去掉 "http://" 头部 + cleanContent := strings.TrimPrefix(fileContent, "http://") + foundItems["url_image"] = append(foundItems["url_image"], cleanContent) + } else if strings.HasPrefix(fileContent, "https://") { + // HTTPS 图片,去掉 "https://" 头部 + cleanContent := strings.TrimPrefix(fileContent, "https://") + foundItems["url_images"] = append(foundItems["url_images"], cleanContent) + } else if strings.HasPrefix(fileContent, "file://") { + // 本地文件,根据系统区分前缀 + var cleanContent string + if runtime.GOOS == "windows" { + cleanContent = strings.TrimPrefix(fileContent, "file:///") + } else { + cleanContent = strings.TrimPrefix(fileContent, "file://") + } + foundItems["local_image"] = append(foundItems["local_image"], cleanContent) + } else { + // 默认情况,直接将内容存储到 foundItems 中 + foundItems["unknown_image"] = append(foundItems["unknown_image"], fileContent) + } case "voice", "record": fileContent, _ := segmentMap["data"].(map[string]interface{})["file"].(string) @@ -513,7 +600,7 @@ func parseMessageContent(paramsMessage callapi.ParamsContent, message callapi.Ac mdContentEncoded = base64.StdEncoding.EncodeToString(mdContentBytes) } else if mdContentStr, isString := mdContent.(string); isString { if strings.HasPrefix(mdContentStr, "base64://") { - mdContentEncoded = mdContentStr + mdContentEncoded = strings.TrimPrefix(mdContentStr, "base64://") } else { mdContentStr = strings.ReplaceAll(mdContentStr, "&", "&") mdContentStr = strings.ReplaceAll(mdContentStr, "[", "[") @@ -561,7 +648,33 @@ func parseMessageContent(paramsMessage callapi.ParamsContent, message callapi.Ac case "image": fileContent, _ := message["data"].(map[string]interface{})["file"].(string) - foundItems["image"] = append(foundItems["image"], fileContent) + + // 检查是否为 Base64 图片 + if strings.HasPrefix(fileContent, "base64://") { + // 去掉 "base64://" 头部 + cleanContent := strings.TrimPrefix(fileContent, "base64://") + foundItems["base64_image"] = append(foundItems["base64_image"], cleanContent) + } else if strings.HasPrefix(fileContent, "http://") { + // HTTP 图片,去掉 "http://" 头部 + cleanContent := strings.TrimPrefix(fileContent, "http://") + foundItems["url_image"] = append(foundItems["url_image"], cleanContent) + } else if strings.HasPrefix(fileContent, "https://") { + // HTTPS 图片,去掉 "https://" 头部 + cleanContent := strings.TrimPrefix(fileContent, "https://") + foundItems["url_images"] = append(foundItems["url_images"], cleanContent) + } else if strings.HasPrefix(fileContent, "file://") { + // 本地文件,根据系统区分前缀 + var cleanContent string + if runtime.GOOS == "windows" { + cleanContent = strings.TrimPrefix(fileContent, "file:///") + } else { + cleanContent = strings.TrimPrefix(fileContent, "file://") + } + foundItems["local_image"] = append(foundItems["local_image"], cleanContent) + } else { + // 默认情况,直接将内容存储到 foundItems 中 + foundItems["unknown_image"] = append(foundItems["unknown_image"], fileContent) + } case "voice", "record": fileContent, _ := message["data"].(map[string]interface{})["file"].(string) @@ -593,7 +706,7 @@ func parseMessageContent(paramsMessage callapi.ParamsContent, message callapi.Ac mdContentEncoded = base64.StdEncoding.EncodeToString(mdContentBytes) } else if mdContentStr, isString := mdContent.(string); isString { if strings.HasPrefix(mdContentStr, "base64://") { - mdContentEncoded = mdContentStr + mdContentEncoded = strings.TrimPrefix(mdContentStr, "base64://") } else { mdContentStr = strings.ReplaceAll(mdContentStr, "&", "&") mdContentStr = strings.ReplaceAll(mdContentStr, "[", "[") diff --git a/handlers/send_group_msg.go b/handlers/send_group_msg.go index 241cfda9..10ca8e60 100644 --- a/handlers/send_group_msg.go +++ b/handlers/send_group_msg.go @@ -380,10 +380,20 @@ func HandleSendGroupMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openap if !config.GetNoRetMsg() { if config.GetThreadsRetMsg() { - go SendResponse(client, err, &message, resp, api, apiv2) + if !config.GetStringOb11() { + go SendResponse(client, err, &message, resp, api, apiv2) + } else { + go SendResponseSB(client, err, &message, resp, api, apiv2) + } } else { - // 发送成功回执 - retmsg, _ = SendResponse(client, err, &message, resp, api, apiv2) + if !config.GetStringOb11() { + // 发送成功回执 + retmsg, _ = SendResponse(client, err, &message, resp, api, apiv2) + } else { + // 发送成功回执 + retmsg, _ = SendResponseSB(client, err, &message, resp, api, apiv2) + } + } } @@ -442,9 +452,19 @@ func HandleSendGroupMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openap if !config.GetNoRetMsg() { //发送成功回执 if config.GetThreadsRetMsg() { - go SendResponse(client, err, &message, resp, api, apiv2) + if !config.GetStringOb11() { + go SendResponse(client, err, &message, resp, api, apiv2) + } else { + go SendResponseSB(client, err, &message, resp, api, apiv2) + } + } else { - retmsg, _ = SendResponse(client, err, &message, resp, api, apiv2) + if !config.GetStringOb11() { + retmsg, _ = SendResponse(client, err, &message, resp, api, apiv2) + } else { + retmsg, _ = SendResponseSB(client, err, &message, resp, api, apiv2) + } + } } @@ -520,9 +540,19 @@ func HandleSendGroupMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openap if !config.GetNoRetMsg() { //发送成功回执 if config.GetThreadsRetMsg() { - go SendResponse(client, err, &message, resp, api, apiv2) + if !config.GetStringOb11() { + go SendResponse(client, err, &message, resp, api, apiv2) + } else { + go SendResponseSB(client, err, &message, resp, api, apiv2) + } + } else { - retmsg, _ = SendResponse(client, err, &message, resp, api, apiv2) + if !config.GetStringOb11() { + retmsg, _ = SendResponse(client, err, &message, resp, api, apiv2) + } else { + retmsg, _ = SendResponseSB(client, err, &message, resp, api, apiv2) + } + } } } @@ -617,9 +647,19 @@ func HandleSendGroupMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openap if !config.GetNoRetMsg() { //发送成功回执 if config.GetThreadsRetMsg() { - go SendResponse(client, err, &message, resp, api, apiv2) + if !config.GetStringOb11() { + go SendResponse(client, err, &message, resp, api, apiv2) + } else { + go SendResponseSB(client, err, &message, resp, api, apiv2) + } + } else { - retmsg, _ = SendResponse(client, err, &message, resp, api, apiv2) + if !config.GetStringOb11() { + retmsg, _ = SendResponse(client, err, &message, resp, api, apiv2) + } else { + retmsg, _ = SendResponseSB(client, err, &message, resp, api, apiv2) + } + } } diff --git a/httpapi/httpapi.go b/httpapi/httpapi.go index bce146ca..c63e62de 100644 --- a/httpapi/httpapi.go +++ b/httpapi/httpapi.go @@ -44,7 +44,11 @@ func CombinedMiddleware(api openapi.OpenAPI, apiV2 openapi.OpenAPI) gin.HandlerF return } if c.Request.URL.Path == "/send_private_msg" { - handleSendPrivateMessage(c, api, apiV2) + if config.GetStringOb11() { + handleSendPrivateMessageSP(c, api, apiV2) + } else { + handleSendPrivateMessage(c, api, apiV2) + } return } if c.Request.URL.Path == "/send_private_msg_sse" { @@ -294,6 +298,56 @@ func handleSendPrivateMessage(c *gin.Context, api openapi.OpenAPI, apiV2 openapi c.String(http.StatusOK, retmsg) } +// handleSendPrivateMessage 处理发送私聊消息的请求 +func handleSendPrivateMessageSP(c *gin.Context, api openapi.OpenAPI, apiV2 openapi.OpenAPI) { + var retmsg string + var req struct { + GroupID string `json:"group_id" form:"group_id"` + UserID string `json:"user_id" form:"user_id"` + Message string `json:"message" form:"message"` + AutoEscape bool `json:"auto_escape" form:"auto_escape"` + } + + // 根据请求方法解析参数 + if c.Request.Method == http.MethodGet { + // 从URL查询参数解析 + if err := c.ShouldBindQuery(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + } else { + // 从JSON或表单数据解析 + if err := c.ShouldBind(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + } + + // 使用解析后的参数处理请求 + // TODO: 添加请求处理逻辑 + // 例如:api.SendGroupMessage(req.GroupID, req.Message, req.AutoEscape) + client := &HttpAPIClient{} + // 创建 ActionMessage 实例 + message := callapi.ActionMessage{ + Action: "send_private_msg", + Params: callapi.ParamsContent{ + GroupID: req.GroupID, + UserID: req.UserID, + Message: req.Message, + }, + } + // 调用处理函数 + retmsg, err := handlers.HandleSendPrivateMsg(client, api, apiV2, message) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + // 返回处理结果 + c.Header("Content-Type", "application/json") + c.String(http.StatusOK, retmsg) +} + // handleSendPrivateMessageSSE 处理发送私聊SSE消息的请求 func handleSendPrivateMessageSSESP(c *gin.Context, api openapi.OpenAPI, apiV2 openapi.OpenAPI) { // 根据请求方法解析参数 @@ -372,6 +426,71 @@ func handleSendPrivateMessageSSESP(c *gin.Context, api openapi.OpenAPI, apiV2 op } +// handleSendGuildChannelMessage 处理发送消频道息的请求 +func handleSendGuildChannelMessage(c *gin.Context, api openapi.OpenAPI, apiV2 openapi.OpenAPI) { + var retmsg string + var req struct { + GuildID string `json:"guild_id" form:"guild_id"` + ChannelID string `json:"channel_id" form:"channel_id"` + Message string `json:"message" form:"message"` + AutoEscape bool `json:"auto_escape" form:"auto_escape"` + } + + // 根据请求方法解析参数 + if c.Request.Method == http.MethodGet { + // 从URL查询参数解析 + if err := c.ShouldBindQuery(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + } else { + // 从JSON或表单数据解析 + if err := c.ShouldBind(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + } + + // 使用解析后的参数处理请求 + // TODO: 添加请求处理逻辑 + // 例如:api.SendGroupMessage(req.GroupID, req.Message, req.AutoEscape) + client := &HttpAPIClient{} + // 创建 ActionMessage 实例 + message := callapi.ActionMessage{ + Action: "send_guild_channel_msg", + Params: callapi.ParamsContent{ + GuildID: req.GuildID, + ChannelID: req.ChannelID, // 注意这里需要转换类型,因为 GroupID 是 int64 + Message: req.Message, + }, + } + // 调用处理函数 + retmsg, err := handlers.HandleSendGuildChannelMsg(client, api, apiV2, message) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + // 返回处理结果 + c.Header("Content-Type", "application/json") + c.String(http.StatusOK, retmsg) +} + +// 定义了一个符合 Client 接口的 HttpAPIClient 结构体 +type HttpAPIClient struct { + // 可添加所需字段 +} + +// 实现 Client 接口的 SendMessage 方法 +// 假client中不执行任何操作,只是返回 nil 来符合接口要求 +func (c *HttpAPIClient) SendMessage(message map[string]interface{}) error { + // 不实际发送消息 + // log.Printf("SendMessage called with: %v", message) + + // 返回nil占位符 + return nil +} + // handleSendPrivateMessageSSE 处理发送私聊SSE消息的请求 func handleSendPrivateMessageSSE(c *gin.Context, api openapi.OpenAPI, apiV2 openapi.OpenAPI) { // 根据请求方法解析参数 @@ -450,71 +569,6 @@ func handleSendPrivateMessageSSE(c *gin.Context, api openapi.OpenAPI, apiV2 open } -// handleSendGuildChannelMessage 处理发送消频道息的请求 -func handleSendGuildChannelMessage(c *gin.Context, api openapi.OpenAPI, apiV2 openapi.OpenAPI) { - var retmsg string - var req struct { - GuildID string `json:"guild_id" form:"guild_id"` - ChannelID string `json:"channel_id" form:"channel_id"` - Message string `json:"message" form:"message"` - AutoEscape bool `json:"auto_escape" form:"auto_escape"` - } - - // 根据请求方法解析参数 - if c.Request.Method == http.MethodGet { - // 从URL查询参数解析 - if err := c.ShouldBindQuery(&req); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - } else { - // 从JSON或表单数据解析 - if err := c.ShouldBind(&req); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - } - - // 使用解析后的参数处理请求 - // TODO: 添加请求处理逻辑 - // 例如:api.SendGroupMessage(req.GroupID, req.Message, req.AutoEscape) - client := &HttpAPIClient{} - // 创建 ActionMessage 实例 - message := callapi.ActionMessage{ - Action: "send_guild_channel_msg", - Params: callapi.ParamsContent{ - GuildID: req.GuildID, - ChannelID: req.ChannelID, // 注意这里需要转换类型,因为 GroupID 是 int64 - Message: req.Message, - }, - } - // 调用处理函数 - retmsg, err := handlers.HandleSendGuildChannelMsg(client, api, apiV2, message) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - - // 返回处理结果 - c.Header("Content-Type", "application/json") - c.String(http.StatusOK, retmsg) -} - -// 定义了一个符合 Client 接口的 HttpAPIClient 结构体 -type HttpAPIClient struct { - // 可添加所需字段 -} - -// 实现 Client 接口的 SendMessage 方法 -// 假client中不执行任何操作,只是返回 nil 来符合接口要求 -func (c *HttpAPIClient) SendMessage(message map[string]interface{}) error { - // 不实际发送消息 - // log.Printf("SendMessage called with: %v", message) - - // 返回nil占位符 - return nil -} - // handleGetGroupList 处理获取群列表 func handleGetGroupList(c *gin.Context, api openapi.OpenAPI, apiV2 openapi.OpenAPI) { var retmsg string