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