返回 AiToEarn
index.ts
1 /*
2 * @Author: nevin
3 * @Date: 2025-02-08 11:40:45
4 * @LastEditTime: 2025-03-24 23:35:16
5 * @LastEditors: nevin
6 * @Description: 小红书
7 */
8 import { PlatformBase } from '../../PlatformBase';
9 import {
10 AccountInfoTypeRV,
11 CommentData,
12 CookiesType,
13 DashboardData,
14 IAccountInfoParams,
15 IGetLocationDataParams,
16 IGetTopicsParams,
17 IGetTopicsResponse,
18 IGetUsersParams,
19 VideoCallbackType,
20 WorkData,
21 } from '../../plat.type';
22 import { PublishVideoResult } from '../../module';
23 import {
24 xiaohongshuService,
25 XSLPlatformSettingType,
26 } from '../../../../plat/xiaohongshu';
27 import { PlatType } from '../../../../../commont/AccountEnum';
28 import { AccountModel } from '../../../../db/models/account';
29 import { VisibleTypeEnum } from '../../../../../commont/publish/PublishEnum';
30 import { CookieToString } from '../../../../plat/utils';
31 import { VideoModel } from '../../../../db/models/video';
32 import { WorkDataModel } from '../../../../db/models/workData';
33 import { ImgTextModel } from '../../../../db/models/imgText';
34
35 export class Xhs extends PlatformBase {
36 constructor() {
37 super(PlatType.Xhs);
38 }
39
40 /**
41 * 登录
42 * @returns
43 */
44 async login() {
45 try {
46 const { success, data, error } =
47 await xiaohongshuService.loginOrView('login');
48 if (!success || !data) {
49 console.log('Login process failed:', error);
50 return null;
51 }
52
53 const userInfo = await xiaohongshuService.getUserInfo(data.cookie);
54
55 const loginCookie =
56 typeof data.cookie === 'string'
57 ? data.cookie
58 : JSON.stringify(data.cookie);
59
60 // 入库
61 return {
62 loginCookie,
63 type: this.type,
64 uid: userInfo.authorId,
65 account: userInfo.authorId,
66 avatar: userInfo.avatar,
67 nickname: userInfo.nickname,
68 fansCount: userInfo.fansCount,
69 };
70 } catch (error) {
71 console.error('Login process failed:', error);
72 return null;
73 }
74 }
75
76 async loginCheck(account: AccountModel) {
77 try {
78 const userInfo = await xiaohongshuService.getUserInfo(
79 JSON.parse(account.loginCookie),
80 );
81 return {
82 online: !!userInfo.authorId,
83 account: {
84 avatar: userInfo.avatar,
85 nickname: userInfo.nickname,
86 fansCount: userInfo.fansCount,
87 abnormalStatus: {
88 [PlatType.Xhs]: userInfo.diagnosis_status,
89 },
90 },
91 };
92 } catch (error) {
93 console.error(error);
94 return {
95 online: false,
96 };
97 }
98 }
99
100 async getAccountInfo(params: IAccountInfoParams): Promise<AccountInfoTypeRV> {
101 try {
102 const userInfo = await xiaohongshuService.getUserInfo(params.cookies);
103 return userInfo;
104 } catch (error) {
105 console.log('-----xhs loginCheck-- error', error);
106
107 return null;
108 }
109 }
110
111 async getStatistics(account: AccountModel) {
112 const accountInfo = await xiaohongshuService.getUserInfo(
113 JSON.parse(account.loginCookie),
114 );
115 return {
116 fansCount: accountInfo.fansCount,
117 workCount: 0, // TODO: 作品数量
118 };
119 return {};
120 }
121
122 async getDashboard(account: AccountModel, time: string[] = []) {
123 const res: DashboardData[] = [];
124 try {
125 const cookie: CookiesType = JSON.parse(account.loginCookie);
126 const ret = await xiaohongshuService.getDashboardFunc(
127 cookie,
128 time[0],
129 time[1],
130 );
131 if (!ret.success) throw new Error('获取三方平台数据失败');
132 for (const item of ret.data) {
133 res.push({
134 time: item.date,
135 fans: item.zhangfen,
136 read: item.bofang,
137 comment: item.pinglun,
138 like: item.dianzan,
139 forward: item.fenxiang,
140 collect: 0, // TODO: 获取收藏数据
141 });
142 }
143 } catch (error) {
144 console.log('------ getDashboard wxSph ---', error);
145 }
146
147 return res;
148 }
149
150 /**
151 * 搜索作品列表
152 * @param account
153 * @param pcursor
154 * @returns
155 */
156 async getsearchNodeList(account: AccountModel, qe?: string, pageInfo?: any) {
157 // console.log('------ getsearchNodeList xhs pageInfo---', pageInfo);
158 const pageNo = pageInfo ? Number.parseInt(pageInfo.pcursor) : 0;
159 // console.log('------ getsearchNodeList xhs pageNo---', pageNo);
160 const pageSize = 20;
161
162 const cookie: CookiesType = JSON.parse(account.loginCookie);
163 const res = await xiaohongshuService.getSearchNodeList(
164 CookieToString(cookie),
165 qe || '',
166 pageNo,
167 );
168
169 const list: WorkData[] = res.data.data.items.map((v: any) => ({
170 dataId: v.id,
171 readCount: v.note_card?.interact_info?.view_count,
172 likeCount: v.note_card?.interact_info?.liked_count,
173 collectCount: v.note_card?.interact_info?.collected_count,
174 commentCount: v.note_card?.interact_info?.comment_count,
175 title: v.note_card?.display_title,
176 coverUrl: v.note_card?.cover.url_default || '',
177 option: {
178 xsec_token: v.xsec_token,
179 },
180 author: {
181 name: v.note_card?.user?.nickname,
182 avatar: v.note_card?.user?.avatar,
183 },
184 data: v,
185 }));
186
187 const count = res.data.data?.tags?.[0]?.notes_count || 0;
188 const hasMore = res.data.data.has_more;
189 return {
190 list,
191 pageInfo: {
192 count,
193 hasMore,
194 pcursor: hasMore ? pageNo + 1 + '' : '',
195 },
196 };
197 }
198
199 /**
200 * 获取作品列表
201 * @param account
202 * @param pcursor
203 * @returns
204 */
205 async getWorkList(account: AccountModel, pcursor?: string) {
206 const pageNo = pcursor ? Number.parseInt(pcursor) : 0;
207
208 const pageSize = 20;
209
210 const cookie: CookiesType = JSON.parse(account.loginCookie);
211 const res = await xiaohongshuService.getWorks(
212 CookieToString(cookie),
213 pageNo,
214 );
215
216 const list: WorkData[] = res.data.data.notes.map((v) => ({
217 dataId: v.id,
218 readCount: v.view_count,
219 likeCount: v.likes,
220 collectCount: v.collected_count,
221 commentCount: v.comments_count,
222 title: v.display_title,
223 coverUrl: v.images_list[0]?.url || '',
224 option: {
225 xsec_token: v.xsec_token,
226 },
227 }));
228
229 const count = res.data.data?.tags[0]?.notes_count || 0;
230 const hasMore = count > pageSize * (pageNo + 1);
231 return {
232 list,
233 pageInfo: {
234 count,
235 hasMore,
236 pcursor: hasMore ? pageNo + 1 + '' : '',
237 },
238 };
239 }
240
241 /**
242 * TODO: 未实现
243 * @returns
244 * @param dataId
245 */
246 async getWorkData(dataId: string) {
247 return {
248 dataId: '',
249 };
250 }
251
252 async getCommentList<T>(
253 account: AccountModel,
254 data: WorkData,
255 pcursor?: string,
256 ) {
257 const cookie: CookiesType = JSON.parse(account.loginCookie);
258
259 const res = await xiaohongshuService.getCommentList(
260 cookie,
261 {
262 xsec_token: data.option!.xsec_token,
263 id: data.dataId,
264 },
265 pcursor ? Number.parseInt(pcursor) : undefined,
266 );
267
268 // 错误处理
269 if (res.status !== 200 || res.data.code) {
270 console.log('----- getCommentList xhs error---', res);
271 return {
272 list: [],
273 pageInfo: {
274 count: 0,
275 hasMore: false,
276 pcursor: '',
277 },
278 };
279 }
280
281 const list: CommentData[] = [];
282 for (const v of res.data.data.comments) {
283 const subList: CommentData[] = [];
284 for (const sub of v.sub_comments) {
285 subList.push({
286 userId: sub.user_info.user_id,
287 dataId: v.note_id,
288 commentId: sub.id,
289 parentCommentId: v.id,
290 content: sub.content,
291 likeCount: Number.parseInt(sub.like_count),
292 nikeName: sub.user_info.nickname,
293 headUrl: sub.user_info.image,
294 subCommentList: [],
295 });
296 }
297
298 list.push({
299 userId: v.user_info.user_id,
300 dataId: v.note_id,
301 commentId: v.id,
302 parentCommentId: undefined,
303 content: v.content,
304 likeCount: Number.parseInt(v.like_count),
305 nikeName: v.user_info.nickname,
306 headUrl: v.user_info.image,
307 data: v,
308 subCommentList: subList,
309 });
310 }
311
312 return {
313 list: list,
314 pageInfo: {
315 hasMore: res.data.data.has_more,
316 pcursor: res.data.data.cursor,
317 },
318 };
319 }
320
321 async getCreatorCommentListByOther(
322 account: AccountModel,
323 data: WorkData,
324 pcursor?: string,
325 ) {
326 const cookie: CookiesType = JSON.parse(account.loginCookie);
327 const res = await xiaohongshuService.getCommentList(cookie, {
328 xsec_token: data.option!.xsec_token,
329 id: data.dataId,
330 });
331
332 const list: CommentData[] = [];
333
334 for (const v of res.data.data.comments) {
335 const subList: CommentData[] = [];
336
337 for (const sub of v.sub_comments) {
338 subList.push({
339 userId: sub.user_info.user_id,
340 dataId: v.note_id,
341 commentId: sub.id,
342 parentCommentId: v.id,
343 content: sub.content,
344 likeCount: Number.parseInt(sub.like_count),
345 nikeName: sub.user_info.nickname,
346 headUrl: sub.user_info.image,
347 subCommentList: [],
348 });
349 }
350
351 list.push({
352 userId: v.user_info.user_id,
353 dataId: v.note_id,
354 commentId: v.id,
355 parentCommentId: undefined,
356 content: v.content,
357 likeCount: Number.parseInt(v.like_count),
358 nikeName: v.user_info.nickname,
359 headUrl: v.user_info.image,
360 data: v,
361 subCommentList: subList,
362 });
363 }
364
365 return {
366 list: list,
367 pageInfo: {
368 hasMore: res.data.data.has_more,
369 pcursor: res.data.data.cursor,
370 },
371 };
372 }
373
374 async getCreatorSecondCommentListByOther(
375 account: AccountModel,
376 data: WorkData,
377 root_comment_id: string,
378 pcursor?: string,
379 ) {
380 const cookie: CookiesType = JSON.parse(account.loginCookie);
381
382 const res = await xiaohongshuService.getSecondCommentList(
383 cookie,
384 data.dataId,
385 root_comment_id,
386 pcursor,
387 );
388 console.log('------ getCreatorSecondCommentListByOther xhs res---', res);
389
390 const list: CommentData[] = [];
391
392 for (const v of res.data.data.comments) {
393 list.push({
394 userId: v.user_info.user_id,
395 dataId: v.note_id,
396 commentId: v.id,
397 parentCommentId: undefined,
398 content: v.content,
399 likeCount: Number.parseInt(v.like_count),
400 nikeName: v.user_info.nickname,
401 headUrl: v.user_info.image,
402 data: v,
403 subCommentList: [],
404 });
405 }
406
407 return {
408 list: list,
409 pageInfo: {
410 hasMore: res.data.data.has_more,
411 pcursor: res.data.data.cursor,
412 },
413 };
414 }
415
416 async createCommentByOther(
417 account: AccountModel,
418 dataId: string, // 作品ID
419 content: string,
420 ) {
421 const cookie: CookiesType = JSON.parse(account.loginCookie);
422 const ret = await xiaohongshuService.commentPost(cookie, dataId, content);
423 // console.log('------ createCommentByOther xhs ---', ret);
424 return ret;
425 }
426
427 async dianzanDyOther(
428 account: AccountModel,
429 dataId: string, // 作品ID
430 ): Promise<any> {
431 console.log('------ dianzanDyOther3333', dataId);
432 const cookie: CookiesType = JSON.parse(account.loginCookie);
433 const res = await xiaohongshuService.likeNote(cookie, dataId);
434
435 console.log('------ res 小红书点赞...: ', res);
436
437 return res;
438 }
439
440 async shoucangDyOther(
441 account: AccountModel,
442 dataId: string, // 作品ID
443 ): Promise<any> {
444 console.log('------ shoucangDyOther5555', dataId);
445 const cookie: CookiesType = JSON.parse(account.loginCookie);
446 const res = await xiaohongshuService.shoucangNote(cookie, dataId);
447
448 // console.log('------ res', res);
449
450 return res;
451 }
452
453 async replyCommentByOther(
454 account: AccountModel,
455 commentId: string,
456 content: string,
457 option: {
458 dataId?: string; // 作品ID
459 comment: any; // 辅助数据,原数据
460 },
461 ) {
462 const cookie: CookiesType = JSON.parse(account.loginCookie);
463 const ret = await xiaohongshuService.commentPost(
464 cookie,
465 option.dataId!,
466 content,
467 commentId,
468 );
469
470 return ret;
471 }
472
473 async createComment(
474 account: AccountModel,
475 dataId: string, // 作品ID
476 content: string,
477 ) {
478 const cookie: CookiesType = JSON.parse(account.loginCookie);
479 const ret = await xiaohongshuService.commentPost(cookie, dataId, content);
480
481 return false;
482 }
483
484 async replyComment(
485 account: AccountModel,
486 commentId: string,
487 content: string,
488 option: {
489 dataId?: string; // 作品ID
490 comment: any; // 辅助数据,原数据
491 },
492 ) {
493 const cookie: CookiesType = JSON.parse(account.loginCookie);
494 const ret = await xiaohongshuService.commentPost(
495 cookie,
496 option.dataId!,
497 content,
498 commentId,
499 );
500
501 return false;
502 }
503 /**
504 * @param params
505 * @param callback
506 * @returns
507 */
508 async videoPublish(
509 params: VideoModel,
510 callback: VideoCallbackType,
511 ): Promise<PublishVideoResult> {
512 return new Promise(async (resolve) => {
513 const result = await xiaohongshuService
514 .publishVideoWorkApi(
515 JSON.stringify(params.cookies),
516 params.videoPath,
517 this.pubParamsParse(params),
518 callback,
519 )
520 .catch((err) => {
521 resolve({
522 code: 0,
523 msg: err,
524 });
525 });
526
527 if (!result || !result.publishId)
528 return resolve({
529 code: 0,
530 msg: '网络繁忙,请稍后重试!',
531 });
532
533 return resolve({
534 code: 1,
535 msg: '成功!',
536 dataId: result!.publishId,
537 previewVideoLink: result.shareLink,
538 });
539 });
540 }
541
542 async getUsers(params: IGetUsersParams) {
543 const usersRes = await xiaohongshuService.getUsers(
544 JSON.parse(params.account.loginCookie),
545 params.keyword,
546 params.page,
547 );
548 return {
549 status: usersRes.status,
550 data: usersRes?.data?.data?.user_info_dtos?.map((v) => {
551 return {
552 image: v.user_base_dto.image,
553 id: v.user_base_dto.red_id,
554 name: v.user_base_dto.user_nickname,
555 };
556 }),
557 };
558 }
559
560 async getTopics({
561 keyword,
562 account,
563 }: IGetTopicsParams): Promise<IGetTopicsResponse> {
564 const res = await xiaohongshuService.getTopics({
565 keyword,
566 cookies: JSON.parse(account.loginCookie),
567 });
568 return {
569 status: res.status,
570 data: res?.data?.data?.topic_info_dtos?.map((v) => {
571 return {
572 id: v.id,
573 name: v.name,
574 view_count: v.view_num,
575 };
576 }),
577 };
578 }
579
580 async getLocationData(params: IGetLocationDataParams) {
581 const locationRes = await xiaohongshuService.getLocations({
582 ...params,
583 keyword: params.keywords,
584 cookies: params.cookie!,
585 });
586 return {
587 status: locationRes.status,
588 data: locationRes?.data?.data?.poi_list?.map((v) => {
589 return {
590 name: v.name,
591 simpleAddress: v.full_address,
592 id: v.poi_id,
593 poi_type: v.poi_type,
594 latitude: v.latitude,
595 longitude: v.longitude,
596 city: v.city_name,
597 };
598 }),
599 };
600 }
601
602 pubParamsParse(params: WorkDataModel): XSLPlatformSettingType {
603 return {
604 proxy: params.proxyIp || '',
605 cover: params.coverPath || '',
606 desc: params.desc,
607 title: params.title,
608 topicsDetail:
609 params.topics?.map((v) => ({
610 topicId: v,
611 topicName: v,
612 })) || [],
613 timingTime: params.timingTime?.getTime(),
614 visibility_type:
615 params.visibleType === VisibleTypeEnum.Public
616 ? 0
617 : params.visibleType === VisibleTypeEnum.Private
618 ? 1
619 : 4,
620 // 位置
621 poiInfo: params.location
622 ? {
623 poiType: params.location.poi_type!,
624 poiId: params.location.id,
625 poiName: params.location.name,
626 poiAddress: params.location.simpleAddress,
627 }
628 : undefined,
629 // @用户
630 mentionedUserInfo: params.mentionedUserInfo
631 ? params.mentionedUserInfo.map((v) => {
632 return {
633 nickName: v.label,
634 uid: `${v.value}`,
635 };
636 })
637 : undefined,
638 };
639 }
640
641 async imgTextPublish(params: ImgTextModel): Promise<PublishVideoResult> {
642 return new Promise(async (resolve) => {
643 const result = await xiaohongshuService
644 .publishImageWorkApi(
645 JSON.stringify(params.cookies),
646 params.imagesPath,
647 this.pubParamsParse(params),
648 )
649 .catch((err) => {
650 resolve({
651 code: 0,
652 msg: err,
653 });
654 });
655
656 if (!result || !result.publishId)
657 return resolve({
658 code: 0,
659 msg: '网络繁忙,请稍后重试!',
660 });
661
662 return resolve({
663 code: 1,
664 msg: '成功!',
665 dataId: result!.publishId,
666 previewVideoLink: result.shareLink,
667 });
668 });
669 }
670 }
671
672 const xhs = new Xhs();
673 export default xhs;
674
674 lines TYPESCRIPT