Skip to content

Commit 4d809f1

Browse files
xpzouyingclaude
andauthored
feat: add manual Docker release workflow (#187)
- 新增 docker-release.yml workflow,支持手动触发 Docker 镜像构建 - 从 release.yml 中移除自动 Docker 构建,避免镜像膨胀 - 使用语义化版本号策略(如 v1.0.0) - 支持多平台构建(linux/amd64, linux/arm64) - 硬编码公开信息,只需配置 DOCKERHUB_TOKEN secret - 自动标记版本号和 latest 标签 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <noreply@anthropic.com>
1 parent 85852e3 commit 4d809f1

File tree

5 files changed

+158
-114
lines changed

5 files changed

+158
-114
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: Docker Release
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
version:
7+
description: 'Version tag (e.g., v1.0.0)'
8+
required: true
9+
default: 'v1.0.0'
10+
11+
permissions:
12+
contents: read
13+
14+
jobs:
15+
docker:
16+
runs-on: ubuntu-latest
17+
18+
steps:
19+
- uses: actions/checkout@v4
20+
21+
- name: Set up Docker Buildx
22+
uses: docker/setup-buildx-action@v3
23+
24+
- name: Log in to Docker Hub
25+
uses: docker/login-action@v3
26+
with:
27+
username: xpzouying
28+
password: ${{ secrets.DOCKERHUB_TOKEN }}
29+
30+
- name: Build and push Docker image
31+
uses: docker/build-push-action@v5
32+
with:
33+
context: .
34+
push: true
35+
platforms: linux/amd64,linux/arm64
36+
tags: |
37+
xpzouying/xiaohongshu-mcp:${{ github.event.inputs.version }}
38+
xpzouying/xiaohongshu-mcp:latest
39+
cache-from: type=gha
40+
cache-to: type=gha,mode=max

.github/workflows/release.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,10 @@ jobs:
142142
./xiaohongshu-mcp-darwin-arm64
143143
```
144144
145+
### 🐳 Docker 镜像
146+
147+
Docker 镜像需要手动触发构建,请到 Actions 页面运行 "Docker Release" workflow。
148+
145149
### 📊 构建信息
146150
147151
- **Commit**: ${{ github.sha }}

mcp_handlers.go

Lines changed: 53 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -132,59 +132,59 @@ func (s *AppServer) handlePublishContent(ctx context.Context, args map[string]in
132132

133133
// handlePublishVideo 处理发布视频内容(仅本地单个视频文件)
134134
func (s *AppServer) handlePublishVideo(ctx context.Context, args map[string]interface{}) *MCPToolResult {
135-
logrus.Info("MCP: 发布视频内容(本地)")
136-
137-
title, _ := args["title"].(string)
138-
content, _ := args["content"].(string)
139-
videoPath, _ := args["video"].(string)
140-
tagsInterface, _ := args["tags"].([]interface{})
141-
142-
var tags []string
143-
for _, tag := range tagsInterface {
144-
if tagStr, ok := tag.(string); ok {
145-
tags = append(tags, tagStr)
146-
}
147-
}
148-
149-
if videoPath == "" {
150-
return &MCPToolResult{
151-
Content: []MCPContent{{
152-
Type: "text",
153-
Text: "发布失败: 缺少本地视频文件路径",
154-
}},
155-
IsError: true,
156-
}
157-
}
158-
159-
logrus.Infof("MCP: 发布视频 - 标题: %s, 标签数量: %d", title, len(tags))
160-
161-
// 构建发布请求
162-
req := &PublishVideoRequest{
163-
Title: title,
164-
Content: content,
165-
Video: videoPath,
166-
Tags: tags,
167-
}
168-
169-
// 执行发布
170-
result, err := s.xiaohongshuService.PublishVideo(ctx, req)
171-
if err != nil {
172-
return &MCPToolResult{
173-
Content: []MCPContent{{
174-
Type: "text",
175-
Text: "发布失败: " + err.Error(),
176-
}},
177-
IsError: true,
178-
}
179-
}
180-
181-
resultText := fmt.Sprintf("视频发布成功: %+v", result)
182-
return &MCPToolResult{
183-
Content: []MCPContent{{
184-
Type: "text",
185-
Text: resultText,
186-
}},
187-
}
135+
logrus.Info("MCP: 发布视频内容(本地)")
136+
137+
title, _ := args["title"].(string)
138+
content, _ := args["content"].(string)
139+
videoPath, _ := args["video"].(string)
140+
tagsInterface, _ := args["tags"].([]interface{})
141+
142+
var tags []string
143+
for _, tag := range tagsInterface {
144+
if tagStr, ok := tag.(string); ok {
145+
tags = append(tags, tagStr)
146+
}
147+
}
148+
149+
if videoPath == "" {
150+
return &MCPToolResult{
151+
Content: []MCPContent{{
152+
Type: "text",
153+
Text: "发布失败: 缺少本地视频文件路径",
154+
}},
155+
IsError: true,
156+
}
157+
}
158+
159+
logrus.Infof("MCP: 发布视频 - 标题: %s, 标签数量: %d", title, len(tags))
160+
161+
// 构建发布请求
162+
req := &PublishVideoRequest{
163+
Title: title,
164+
Content: content,
165+
Video: videoPath,
166+
Tags: tags,
167+
}
168+
169+
// 执行发布
170+
result, err := s.xiaohongshuService.PublishVideo(ctx, req)
171+
if err != nil {
172+
return &MCPToolResult{
173+
Content: []MCPContent{{
174+
Type: "text",
175+
Text: "发布失败: " + err.Error(),
176+
}},
177+
IsError: true,
178+
}
179+
}
180+
181+
resultText := fmt.Sprintf("视频发布成功: %+v", result)
182+
return &MCPToolResult{
183+
Content: []MCPContent{{
184+
Type: "text",
185+
Text: resultText,
186+
}},
187+
}
188188
}
189189

190190
// handleListFeeds 处理获取Feeds列表

mcp_server.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ type PublishContentArgs struct {
2020

2121
// PublishVideoArgs 发布视频的参数(仅支持本地单个视频文件)
2222
type PublishVideoArgs struct {
23-
Title string `json:"title" jsonschema:"内容标题(小红书限制:最多20个中文字或英文单词)"`
24-
Content string `json:"content" jsonschema:"正文内容,不包含以#开头的标签内容,所有话题标签都用tags参数来生成和提供即可"`
25-
Video string `json:"video" jsonschema:"本地视频绝对路径(仅支持单个视频文件,如:/Users/user/video.mp4)"`
26-
Tags []string `json:"tags,omitempty" jsonschema:"话题标签列表(可选参数),如 [美食, 旅行, 生活]"`
23+
Title string `json:"title" jsonschema:"内容标题(小红书限制:最多20个中文字或英文单词)"`
24+
Content string `json:"content" jsonschema:"正文内容,不包含以#开头的标签内容,所有话题标签都用tags参数来生成和提供即可"`
25+
Video string `json:"video" jsonschema:"本地视频绝对路径(仅支持单个视频文件,如:/Users/user/video.mp4)"`
26+
Tags []string `json:"tags,omitempty" jsonschema:"话题标签列表(可选参数),如 [美食, 旅行, 生活]"`
2727
}
2828

2929
// SearchFeedsArgs 搜索内容的参数

service.go

Lines changed: 57 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"context"
55
"encoding/json"
66
"fmt"
7-
"os"
87
"github.com/go-rod/rod"
98
"github.com/mattn/go-runewidth"
109
"github.com/sirupsen/logrus"
@@ -14,6 +13,7 @@ import (
1413
"github.com/xpzouying/xiaohongshu-mcp/cookies"
1514
"github.com/xpzouying/xiaohongshu-mcp/pkg/downloader"
1615
"github.com/xpzouying/xiaohongshu-mcp/xiaohongshu"
16+
"os"
1717
"time"
1818
)
1919

@@ -48,28 +48,28 @@ type LoginQrcodeResponse struct {
4848

4949
// PublishResponse 发布响应
5050
type PublishResponse struct {
51-
Title string `json:"title"`
52-
Content string `json:"content"`
53-
Images int `json:"images"`
54-
Status string `json:"status"`
55-
PostID string `json:"post_id,omitempty"`
51+
Title string `json:"title"`
52+
Content string `json:"content"`
53+
Images int `json:"images"`
54+
Status string `json:"status"`
55+
PostID string `json:"post_id,omitempty"`
5656
}
5757

5858
// PublishVideoRequest 发布视频请求(仅支持本地单个视频文件)
5959
type PublishVideoRequest struct {
60-
Title string `json:"title" binding:"required"`
61-
Content string `json:"content" binding:"required"`
62-
Video string `json:"video" binding:"required"`
63-
Tags []string `json:"tags,omitempty"`
60+
Title string `json:"title" binding:"required"`
61+
Content string `json:"content" binding:"required"`
62+
Video string `json:"video" binding:"required"`
63+
Tags []string `json:"tags,omitempty"`
6464
}
6565

6666
// PublishVideoResponse 发布视频响应
6767
type PublishVideoResponse struct {
68-
Title string `json:"title"`
69-
Content string `json:"content"`
70-
Video string `json:"video"`
71-
Status string `json:"status"`
72-
PostID string `json:"post_id,omitempty"`
68+
Title string `json:"title"`
69+
Content string `json:"content"`
70+
Video string `json:"video"`
71+
Status string `json:"status"`
72+
PostID string `json:"post_id,omitempty"`
7373
}
7474

7575
// FeedsListResponse Feeds列表响应
@@ -219,55 +219,55 @@ func (s *XiaohongshuService) publishContent(ctx context.Context, content xiaohon
219219

220220
// PublishVideo 发布视频(本地文件)
221221
func (s *XiaohongshuService) PublishVideo(ctx context.Context, req *PublishVideoRequest) (*PublishVideoResponse, error) {
222-
// 标题长度校验
223-
if titleWidth := runewidth.StringWidth(req.Title); titleWidth > 40 {
224-
return nil, fmt.Errorf("标题长度超过限制")
225-
}
226-
227-
// 本地视频文件校验
228-
if req.Video == "" {
229-
return nil, fmt.Errorf("必须提供本地视频文件")
230-
}
231-
if _, err := os.Stat(req.Video); err != nil {
232-
return nil, fmt.Errorf("视频文件不存在或不可访问: %v", err)
233-
}
234-
235-
// 构建发布内容
236-
content := xiaohongshu.PublishVideoContent{
237-
Title: req.Title,
238-
Content: req.Content,
239-
Tags: req.Tags,
240-
VideoPath: req.Video,
241-
}
242-
243-
// 执行发布
244-
if err := s.publishVideo(ctx, content); err != nil {
245-
return nil, err
246-
}
247-
248-
resp := &PublishVideoResponse{
249-
Title: req.Title,
250-
Content: req.Content,
251-
Video: req.Video,
252-
Status: "发布完成",
253-
}
254-
return resp, nil
222+
// 标题长度校验
223+
if titleWidth := runewidth.StringWidth(req.Title); titleWidth > 40 {
224+
return nil, fmt.Errorf("标题长度超过限制")
225+
}
226+
227+
// 本地视频文件校验
228+
if req.Video == "" {
229+
return nil, fmt.Errorf("必须提供本地视频文件")
230+
}
231+
if _, err := os.Stat(req.Video); err != nil {
232+
return nil, fmt.Errorf("视频文件不存在或不可访问: %v", err)
233+
}
234+
235+
// 构建发布内容
236+
content := xiaohongshu.PublishVideoContent{
237+
Title: req.Title,
238+
Content: req.Content,
239+
Tags: req.Tags,
240+
VideoPath: req.Video,
241+
}
242+
243+
// 执行发布
244+
if err := s.publishVideo(ctx, content); err != nil {
245+
return nil, err
246+
}
247+
248+
resp := &PublishVideoResponse{
249+
Title: req.Title,
250+
Content: req.Content,
251+
Video: req.Video,
252+
Status: "发布完成",
253+
}
254+
return resp, nil
255255
}
256256

257257
// publishVideo 执行视频发布
258258
func (s *XiaohongshuService) publishVideo(ctx context.Context, content xiaohongshu.PublishVideoContent) error {
259-
b := newBrowser()
260-
defer b.Close()
259+
b := newBrowser()
260+
defer b.Close()
261261

262-
page := b.NewPage()
263-
defer page.Close()
262+
page := b.NewPage()
263+
defer page.Close()
264264

265-
action, err := xiaohongshu.NewPublishVideoAction(page)
266-
if err != nil {
267-
return err
268-
}
265+
action, err := xiaohongshu.NewPublishVideoAction(page)
266+
if err != nil {
267+
return err
268+
}
269269

270-
return action.PublishVideo(ctx, content)
270+
return action.PublishVideo(ctx, content)
271271
}
272272

273273
// ListFeeds 获取Feeds列表

0 commit comments

Comments
 (0)