fix: 修复标题长度计算不准确的问题 (#410)
使用基于 UTF-16 编码的加权算法替换 go-runewidth,与小红书实际计算规则一致: 非ASCII字符算2字节,ASCII字符算1字节,向上取整除以2。 Closes #401 Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2
go.mod
2
go.mod
@@ -7,7 +7,6 @@ require (
|
|||||||
github.com/gin-gonic/gin v1.10.1
|
github.com/gin-gonic/gin v1.10.1
|
||||||
github.com/go-rod/rod v0.116.2
|
github.com/go-rod/rod v0.116.2
|
||||||
github.com/h2non/filetype v1.1.3
|
github.com/h2non/filetype v1.1.3
|
||||||
github.com/mattn/go-runewidth v0.0.16
|
|
||||||
github.com/modelcontextprotocol/go-sdk v0.7.0
|
github.com/modelcontextprotocol/go-sdk v0.7.0
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/sirupsen/logrus v1.9.3
|
github.com/sirupsen/logrus v1.9.3
|
||||||
@@ -37,7 +36,6 @@ require (
|
|||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/rivo/uniseg v0.2.0 // indirect
|
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||||
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
|
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -49,8 +49,6 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
|||||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
|
||||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
|
||||||
github.com/modelcontextprotocol/go-sdk v0.7.0 h1:XEQfn3bDx2cAdSUKty3tYEMll5dtRgBUDX88Q65fai0=
|
github.com/modelcontextprotocol/go-sdk v0.7.0 h1:XEQfn3bDx2cAdSUKty3tYEMll5dtRgBUDX88Q65fai0=
|
||||||
github.com/modelcontextprotocol/go-sdk v0.7.0/go.mod h1:nYtYQroQ2KQiM0/SbyEPUWQ6xs4B95gJjEalc9AQyOs=
|
github.com/modelcontextprotocol/go-sdk v0.7.0/go.mod h1:nYtYQroQ2KQiM0/SbyEPUWQ6xs4B95gJjEalc9AQyOs=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
@@ -64,8 +62,6 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
|
||||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
|||||||
17
pkg/xhsutil/title.go
Normal file
17
pkg/xhsutil/title.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package xhsutil
|
||||||
|
|
||||||
|
import "unicode/utf16"
|
||||||
|
|
||||||
|
// CalcTitleLength 计算小红书标题长度
|
||||||
|
// 规则:非ASCII字符(中文、全角符号等)算2字节,ASCII字符算1字节,最终结果向上取整除以2
|
||||||
|
func CalcTitleLength(s string) int {
|
||||||
|
byteLen := 0
|
||||||
|
for _, c := range utf16.Encode([]rune(s)) {
|
||||||
|
if c > 127 {
|
||||||
|
byteLen += 2
|
||||||
|
} else {
|
||||||
|
byteLen += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (byteLen + 1) / 2
|
||||||
|
}
|
||||||
36
pkg/xhsutil/title_test.go
Normal file
36
pkg/xhsutil/title_test.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package xhsutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCalcTitleLength(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
want int
|
||||||
|
}{
|
||||||
|
{name: "空字符串", input: "", want: 0},
|
||||||
|
{name: "纯中文", input: "你好世界", want: 4},
|
||||||
|
{name: "纯英文", input: "hello", want: 3},
|
||||||
|
{name: "纯数字", input: "12345", want: 3},
|
||||||
|
{name: "中英混合-OOTD穿搭分享", input: "OOTD穿搭分享", want: 6},
|
||||||
|
{name: "20个中文字刚好上限", input: "一二三四五六七八九十一二三四五六七八九十", want: 20},
|
||||||
|
{name: "40个英文字母等于20", input: "abcdefghijklmnopqrstuvwxyzabcdefghijklmn", want: 20},
|
||||||
|
{name: "单个emoji", input: "😀", want: 2},
|
||||||
|
{name: "中文加emoji", input: "今天好开心😀", want: 7},
|
||||||
|
{name: "奇数个英文字母向上取整", input: "a", want: 1},
|
||||||
|
{name: "两个英文字母", input: "ab", want: 1},
|
||||||
|
{name: "三个英文字母", input: "abc", want: 2},
|
||||||
|
{name: "全角符号", input: "!?", want: 2},
|
||||||
|
{name: "半角符号", input: "!?", want: 1},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
assert.Equal(t, tt.want, CalcTitleLength(tt.input))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
12
service.go
12
service.go
@@ -8,13 +8,13 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-rod/rod"
|
"github.com/go-rod/rod"
|
||||||
"github.com/mattn/go-runewidth"
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/xpzouying/headless_browser"
|
"github.com/xpzouying/headless_browser"
|
||||||
"github.com/xpzouying/xiaohongshu-mcp/browser"
|
"github.com/xpzouying/xiaohongshu-mcp/browser"
|
||||||
"github.com/xpzouying/xiaohongshu-mcp/configs"
|
"github.com/xpzouying/xiaohongshu-mcp/configs"
|
||||||
"github.com/xpzouying/xiaohongshu-mcp/cookies"
|
"github.com/xpzouying/xiaohongshu-mcp/cookies"
|
||||||
"github.com/xpzouying/xiaohongshu-mcp/pkg/downloader"
|
"github.com/xpzouying/xiaohongshu-mcp/pkg/downloader"
|
||||||
|
"github.com/xpzouying/xiaohongshu-mcp/pkg/xhsutil"
|
||||||
"github.com/xpzouying/xiaohongshu-mcp/xiaohongshu"
|
"github.com/xpzouying/xiaohongshu-mcp/xiaohongshu"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -168,10 +168,8 @@ func (s *XiaohongshuService) GetLoginQrcode(ctx context.Context) (*LoginQrcodeRe
|
|||||||
|
|
||||||
// PublishContent 发布内容
|
// PublishContent 发布内容
|
||||||
func (s *XiaohongshuService) PublishContent(ctx context.Context, req *PublishRequest) (*PublishResponse, error) {
|
func (s *XiaohongshuService) PublishContent(ctx context.Context, req *PublishRequest) (*PublishResponse, error) {
|
||||||
// 验证标题长度
|
// 验证标题长度(小红书限制:最大20个字)
|
||||||
// 小红书限制:最大40个单位长度
|
if xhsutil.CalcTitleLength(req.Title) > 20 {
|
||||||
// 中文/日文/韩文占2个单位,英文/数字占1个单位
|
|
||||||
if titleWidth := runewidth.StringWidth(req.Title); titleWidth > 40 {
|
|
||||||
return nil, fmt.Errorf("标题长度超过限制")
|
return nil, fmt.Errorf("标题长度超过限制")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,8 +255,8 @@ func (s *XiaohongshuService) publishContent(ctx context.Context, content xiaohon
|
|||||||
|
|
||||||
// PublishVideo 发布视频(本地文件)
|
// PublishVideo 发布视频(本地文件)
|
||||||
func (s *XiaohongshuService) PublishVideo(ctx context.Context, req *PublishVideoRequest) (*PublishVideoResponse, error) {
|
func (s *XiaohongshuService) PublishVideo(ctx context.Context, req *PublishVideoRequest) (*PublishVideoResponse, error) {
|
||||||
// 标题长度校验
|
// 标题长度校验(小红书限制:最大20个字)
|
||||||
if titleWidth := runewidth.StringWidth(req.Title); titleWidth > 40 {
|
if xhsutil.CalcTitleLength(req.Title) > 20 {
|
||||||
return nil, fmt.Errorf("标题长度超过限制")
|
return nil, fmt.Errorf("标题长度超过限制")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user