-
Notifications
You must be signed in to change notification settings - Fork 15
Expand file tree
/
Copy pathservice.go
More file actions
266 lines (216 loc) · 7.31 KB
/
service.go
File metadata and controls
266 lines (216 loc) · 7.31 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
package main
import (
"context"
"os"
"strings"
"github.com/sirupsen/logrus"
"github.com/xpzouying/xiaohongshu-mcp/browser"
"github.com/xpzouying/xiaohongshu-mcp/configs"
"github.com/xpzouying/xiaohongshu-mcp/pkg/downloader"
"github.com/xpzouying/xiaohongshu-mcp/xiaohongshu"
)
// XiaohongshuService 小红书业务服务
type XiaohongshuService struct{}
// NewXiaohongshuService 创建小红书服务实例
func NewXiaohongshuService() *XiaohongshuService {
return &XiaohongshuService{}
}
// PublishRequest 发布请求
type PublishRequest struct {
Title string `json:"title" binding:"required"`
Content string `json:"content" binding:"required"`
Images []string `json:"images"`
}
// LoginStatusResponse 登录状态响应
type LoginStatusResponse struct {
IsLoggedIn bool `json:"is_logged_in"`
Username string `json:"username,omitempty"`
}
// PublishResponse 发布响应
type PublishResponse struct {
Title string `json:"title"`
Content string `json:"content"`
Images int `json:"images"`
Status string `json:"status"`
PostID string `json:"post_id,omitempty"`
}
// FeedsListResponse Feeds列表响应
type FeedsListResponse struct {
Feeds []xiaohongshu.Feed `json:"feeds"`
Count int `json:"count"`
}
// CheckLoginStatus 检查登录状态
func (s *XiaohongshuService) CheckLoginStatus(ctx context.Context) (*LoginStatusResponse, error) {
b := browser.NewBrowser(configs.IsHeadless())
// 注意:不再自动关闭浏览器,由浏览器管理器管理生命周期
page := b.NewPage()
defer page.Close()
loginAction := xiaohongshu.NewLogin(page)
isLoggedIn, err := loginAction.CheckLoginStatus(ctx)
if err != nil {
return nil, err
}
response := &LoginStatusResponse{
IsLoggedIn: isLoggedIn,
Username: configs.Username,
}
return response, nil
}
// PublishContent 发布内容
func (s *XiaohongshuService) PublishContent(ctx context.Context, req *PublishRequest) (*PublishResponse, error) {
logrus.Infof("开始处理发布请求: 标题=%s, 图片数量=%d", req.Title, len(req.Images))
// 检查是否有图片
if len(req.Images) == 0 {
logrus.Warn("没有提供图片,将发布纯文本内容")
return &PublishResponse{
Title: req.Title,
Content: req.Content,
Images: 0,
Status: "发布完成(纯文本)",
}, nil
}
// 检查是否是虚拟图片路径(前端生成的临时路径)
virtualImagePaths := make([]string, 0)
for _, img := range req.Images {
// 检测各种虚拟路径模式:
// 1. image_xxx_xxx.jpg 格式
// 2. 简单的文件名如 "通知.jpg" 但没有对应的本地文件
// 3. 其他明显不是真实文件路径的格式
isVirtual := false
// 模式1: image_xxx_xxx.jpg
if strings.HasPrefix(img, "image_") && strings.Contains(img, "_") {
isVirtual = true
}
// 模式2: 检查是否是简单的文件名(不包含路径分隔符)
if !isVirtual && !strings.Contains(img, "/") && !strings.Contains(img, "\\") {
// 检查文件是否存在
if _, err := os.Stat(img); os.IsNotExist(err) {
isVirtual = true
}
}
// 模式3: 检查是否是URL(以http开头)
if !isVirtual && (strings.HasPrefix(img, "http://") || strings.HasPrefix(img, "https://")) {
isVirtual = false // URL不是虚拟路径
}
if isVirtual {
virtualImagePaths = append(virtualImagePaths, img)
}
}
// 如果都是虚拟路径,说明前端没有提供真实图片,发布纯文本
if len(virtualImagePaths) == len(req.Images) {
logrus.Warnf("检测到虚拟图片路径: %v,将发布纯文本内容", virtualImagePaths)
// 构建纯文本发布内容
content := xiaohongshu.PublishImageContent{
Title: req.Title,
Content: req.Content,
ImagePaths: []string{}, // 空图片数组表示纯文本
}
// 执行纯文本发布
if err := s.publishContent(ctx, content); err != nil {
logrus.Errorf("纯文本发布失败: %v", err)
return nil, err
}
return &PublishResponse{
Title: req.Title,
Content: req.Content,
Images: 0,
Status: "发布完成(纯文本)",
}, nil
}
// 处理图片:下载URL图片或使用本地路径
imagePaths, err := s.processImages(req.Images)
if err != nil {
logrus.Errorf("图片处理失败: %v", err)
return nil, err
}
logrus.Infof("图片处理完成,有效图片数量: %d", len(imagePaths))
// 构建发布内容
content := xiaohongshu.PublishImageContent{
Title: req.Title,
Content: req.Content,
ImagePaths: imagePaths,
}
// 执行发布
if err := s.publishContent(ctx, content); err != nil {
logrus.Errorf("发布内容执行失败: %v", err)
return nil, err
}
response := &PublishResponse{
Title: req.Title,
Content: req.Content,
Images: len(imagePaths),
Status: "发布完成",
}
logrus.Infof("发布内容处理完成: %+v", response)
return response, nil
}
// processImages 处理图片列表,支持URL下载和本地路径
func (s *XiaohongshuService) processImages(images []string) ([]string, error) {
processor := downloader.NewImageProcessor()
return processor.ProcessImages(images)
}
// publishContent 执行内容发布
func (s *XiaohongshuService) publishContent(ctx context.Context, content xiaohongshu.PublishImageContent) error {
logrus.Infof("开始执行发布,使用环境变量 MCP_HEADLESS: %s", os.Getenv("MCP_HEADLESS"))
// 使用浏览器管理器的当前设置
manager := browser.GetManager()
currentHeadless := manager.IsHeadless()
b := browser.NewBrowser(currentHeadless) // 使用当前的无头模式设置
// 注意:不再自动关闭浏览器,由浏览器管理器管理生命周期
page := b.NewPage()
defer page.Close()
action, err := xiaohongshu.NewPublishImageAction(page)
if err != nil {
logrus.Errorf("创建发布action失败: %v", err)
return err
}
// 执行发布
logrus.Info("开始执行发布操作...")
if err := action.Publish(ctx, content); err != nil {
logrus.Errorf("发布操作失败: %v", err)
return err
}
logrus.Info("发布操作完成")
return nil
}
// ListFeeds 获取Feeds列表
func (s *XiaohongshuService) ListFeeds(ctx context.Context) (*FeedsListResponse, error) {
// 使用浏览器管理器的当前设置
manager := browser.GetManager()
currentHeadless := manager.IsHeadless()
b := browser.NewBrowser(currentHeadless) // 使用当前的无头模式设置
// 注意:不再自动关闭浏览器,由浏览器管理器管理生命周期
page := b.NewPage()
defer page.Close()
// 创建 Feeds 列表 action
action := xiaohongshu.NewFeedsListAction(page)
// 获取 Feeds 列表
feeds, err := action.GetFeedsList(ctx)
if err != nil {
return nil, err
}
response := &FeedsListResponse{
Feeds: feeds,
Count: len(feeds),
}
return response, nil
}
func (s *XiaohongshuService) SearchFeeds(ctx context.Context, keyword string) (*FeedsListResponse, error) {
// 使用浏览器管理器的当前设置
manager := browser.GetManager()
currentHeadless := manager.IsHeadless()
b := browser.NewBrowser(currentHeadless) // 使用当前的无头模式设置
// 注意:不再自动关闭浏览器,由浏览器管理器管理生命周期
page := b.NewPage()
defer page.Close()
action := xiaohongshu.NewSearchAction(page)
feeds, err := action.Search(ctx, keyword)
if err != nil {
return nil, err
}
response := &FeedsListResponse{
Feeds: feeds,
Count: len(feeds),
}
return response, nil
}