fix: use common.Marshal/Unmarshal per Rule 1 + telegram null safety

- model/user.go: replace json.Unmarshal/Marshal with common.* wrapper
  functions as required by project Rule 1 (3 occurrences)
- relay/channel/claude/relay-claude.go: replace 3 json.* calls with
  common.* (tool call args unmarshal, response marshal)
- relay/channel/gemini/relay-gemini.go: replace 5 json.* calls with
  common.* (content parsing, function args, response marshal)
- adapters/telegram/index.ts: add optional chaining on callback query
  message.chat.id and null coalescing on message.text to prevent crash
  when callback message is undefined

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-17 23:09:32 +08:00
co-authored by Claude Opus 4.6
parent aa52b1265b
commit e31fe390f3
4 changed files with 13 additions and 16 deletions
+2 -2
View File
@@ -718,7 +718,7 @@ bot.on('callback_query:data', async (ctx) => {
const requestId = parts[1]!
const allowed = parts[2] === 'yes'
const chatId = String(ctx.callbackQuery.message?.chat.id)
const chatId = String(ctx.callbackQuery.message?.chat?.id)
bridge.sendPermissionResponse(chatId, requestId, allowed)
const runtime = getRuntimeState(chatId)
@@ -727,7 +727,7 @@ bot.on('callback_query:data', async (ctx) => {
const statusText = allowed ? '✅ 已允许' : '❌ 已拒绝'
try {
await ctx.editMessageText(
ctx.callbackQuery.message?.text + `\n\n${statusText}`,
(ctx.callbackQuery.message?.text ?? '') + `\n\n${statusText}`,
)
} catch { /* ignore */ }
+3 -4
View File
@@ -2,7 +2,6 @@ package model
import (
"database/sql"
"encoding/json"
"errors"
"fmt"
"strconv"
@@ -81,7 +80,7 @@ func (user *User) SetAccessToken(token string) {
func (user *User) GetSetting() dto.UserSetting {
setting := dto.UserSetting{}
if user.Setting != "" {
err := json.Unmarshal([]byte(user.Setting), &setting)
err := common.Unmarshal([]byte(user.Setting), &setting)
if err != nil {
common.SysLog("failed to unmarshal setting: " + err.Error())
}
@@ -90,7 +89,7 @@ func (user *User) GetSetting() dto.UserSetting {
}
func (user *User) SetSetting(setting dto.UserSetting) {
settingBytes, err := json.Marshal(setting)
settingBytes, err := common.Marshal(setting)
if err != nil {
common.SysLog("failed to marshal setting: " + err.Error())
return
@@ -151,7 +150,7 @@ func generateDefaultSidebarConfigForRole(userRole int) string {
// 普通用户不包含admin区域
// 转换为JSON字符串
configBytes, err := json.Marshal(defaultConfig)
configBytes, err := common.Marshal(defaultConfig)
if err != nil {
common.SysLog("生成默认边栏配置失败: " + err.Error())
return ""
+3 -3
View File
@@ -406,7 +406,7 @@ func RequestOpenAI2ClaudeMessage(c *gin.Context, textRequest dto.GeneralOpenAIRe
if message.ToolCalls != nil {
for _, toolCall := range message.ParseToolCalls() {
inputObj := make(map[string]any)
if err := json.Unmarshal([]byte(toolCall.Function.Arguments), &inputObj); err != nil {
if err := common.Unmarshal([]byte(toolCall.Function.Arguments), &inputObj); err != nil {
common.SysLog("tool call function arguments is not a map[string]any: " + fmt.Sprintf("%v", toolCall.Function.Arguments))
continue
}
@@ -540,7 +540,7 @@ func ResponseClaude2OpenAI(claudeResponse *dto.ClaudeResponse) *dto.OpenAITextRe
for _, message := range claudeResponse.Content {
switch message.Type {
case "tool_use":
args, _ := json.Marshal(message.Input)
args, _ := common.Marshal(message.Input)
tools = append(tools, dto.ToolCallResponse{
ID: message.Id,
Type: "function", // compatible with other OpenAI derivative applications
@@ -919,7 +919,7 @@ func HandleClaudeResponseData(c *gin.Context, info *relaycommon.RelayInfo, claud
case types.RelayFormatOpenAI:
openaiResponse := ResponseClaude2OpenAI(&claudeResponse)
openaiResponse.Usage = buildOpenAIStyleUsageFromClaudeUsage(claudeInfo.Usage)
responseData, err = json.Marshal(openaiResponse)
responseData, err = common.Marshal(openaiResponse)
if err != nil {
return types.NewError(err, types.ErrorCodeBadResponseBody)
}
+5 -7
View File
@@ -467,10 +467,10 @@ func CovertOpenAI2Gemini(c *gin.Context, textRequest dto.GeneralOpenAIRequest, i
contentStr := message.StringContent()
// 1. 尝试解析为 JSON 对象
if err := json.Unmarshal([]byte(contentStr), &contentMap); err != nil {
if err := common.Unmarshal([]byte(contentStr), &contentMap); err != nil {
// 2. 如果失败,尝试解析为 JSON 数组
var contentSlice []interface{}
if err := json.Unmarshal([]byte(contentStr), &contentSlice); err == nil {
if err := common.Unmarshal([]byte(contentStr), &contentSlice); err == nil {
// 如果是数组,包装成对象
contentMap = map[string]interface{}{"result": contentSlice}
} else {
@@ -502,7 +502,7 @@ func CovertOpenAI2Gemini(c *gin.Context, textRequest dto.GeneralOpenAIRequest, i
for _, call := range message.ParseToolCalls() {
args := map[string]interface{}{}
if call.Function.Arguments != "" {
if json.Unmarshal([]byte(call.Function.Arguments), &args) != nil {
if common.Unmarshal([]byte(call.Function.Arguments), &args) != nil {
return nil, fmt.Errorf("invalid arguments for function %s, args: %s", call.Function.Name, call.Function.Arguments)
}
}
@@ -994,9 +994,7 @@ func unescapeMapOrSlice(data interface{}) interface{} {
func getResponseToolCall(item *dto.GeminiPart) *dto.ToolCallResponse {
var argsBytes []byte
var err error
// 移除 unescapeMapOrSlice 调用,直接使用 json.Marshal
// JSON 序列化/反序列化已经正确处理了转义字符
argsBytes, err = json.Marshal(item.FunctionCall.Arguments)
argsBytes, err = common.Marshal(item.FunctionCall.Arguments)
if err != nil {
return nil
@@ -1569,7 +1567,7 @@ func GeminiImageHandler(c *gin.Context, info *relaycommon.RelayInfo, resp *http.
})
}
jsonResponse, jsonErr := json.Marshal(openAIResponse)
jsonResponse, jsonErr := common.Marshal(openAIResponse)
if jsonErr != nil {
return nil, types.NewError(jsonErr, types.ErrorCodeBadResponseBody)
}