返回 AiToEarn
service.ts
1 /*
2 * @Author: nevin
3 * @Date: 2025-01-24 17:10:35
4 * @LastEditors: nevin
5 * @Description: Reply reply
6 */
7 import { Inject, Injectable } from '../core/decorators';
8 import PQueue from 'p-queue';
9 import { AccountModel } from '../../db/models/account';
10 import platController from '../plat';
11 import { toolsApi } from '../api/tools';
12 import { AutoRunService } from '../autoRun/service';
13 import { AutoRunModel } from '../../db/models/autoRun';
14 import { sysNotice } from '../../global/notice';
15 import { AutorReplyCommentScheduleEvent } from '../../../commont/types/reply';
16 import { FindOptionsWhere, Repository } from 'typeorm';
17 import { ReplyCommentRecordModel } from '../../db/models/replyCommentRecord';
18 import { AppDataSource } from '../../db';
19 import { getUserInfo } from '../user/comment';
20 import { AutoRunRecordStatus } from '../../db/models/autoRunRecord';
21 import { WorkData } from '../plat/plat.type';
22 import { sleep } from '../../util/time';
23 import { AutoReplyCache, AutorReplyCacheStatus } from './cacheData';
24 import { logger } from '../../global/log';
25 import { backPageData, CorrectQuery } from '../../global/table';
26 import { PlatType } from '../../../commont/AccountEnum';
27
28 @Injectable()
29 export class ReplyService {
30 replyQueue: PQueue;
31 private replyCommentRecordRepository: Repository<ReplyCommentRecordModel>;
32
33 constructor() {
34 this.replyQueue = new PQueue({ concurrency: 1 });
35 this.replyCommentRecordRepository = AppDataSource.getRepository(
36 ReplyCommentRecordModel,
37 );
38 }
39
40 @Inject(AutoRunService)
41 private readonly autoRunService!: AutoRunService;
42
43 /**
44 * 创建评论回复记录
45 * @param userId
46 * @param account
47 * @param comment
48 * @returns
49 */
50 async createReplyCommentRecord(
51 userId: string,
52 account: AccountModel,
53 comment: {
54 id: string;
55 commentContent: string;
56 replyContent: string;
57 },
58 ) {
59 return await this.replyCommentRecordRepository.save({
60 userId,
61 accountId: account.id,
62 type: account.type,
63 commentId: comment.id + '',
64 commentContent: comment.commentContent,
65 replyContent: comment.replyContent,
66 });
67 }
68
69 // 获取平台的评论记录
70 async getReplyCommentRecord(
71 userId: string,
72 account: AccountModel,
73 commentId: string,
74 ) {
75 return await this.replyCommentRecordRepository.findOne({
76 where: {
77 userId,
78 accountId: account.id,
79 type: account.type,
80 commentId: commentId + '',
81 },
82 });
83 }
84
85 // 获取评论回复记录列表
86 async getReplyCommentRecordList(
87 userId: string,
88 page: CorrectQuery,
89 query: {
90 accountId?: number;
91 type?: PlatType;
92 },
93 ) {
94 const filter: FindOptionsWhere<ReplyCommentRecordModel> = {
95 userId,
96 ...(query.accountId && { accountId: query.accountId }),
97 ...(query.type && { type: query.type }),
98 };
99
100 const [list, totalCount] =
101 await this.replyCommentRecordRepository.findAndCount({
102 where: filter,
103 });
104
105 return backPageData(list, totalCount, page);
106 }
107
108 /**
109 * 自动一键评论
110 * 规则:评论所有的一级评论,已经在评论记录的不评论
111 */
112 async autorReplyComment(
113 account: AccountModel,
114 data: WorkData,
115 scheduleEvent: (data: {
116 tag: AutorReplyCommentScheduleEvent;
117 status: -1 | 0 | 1; // -1 错误 0 进行中 1 完成
118 data?: any; // 数据
119 error?: any;
120 }) => void,
121 ) {
122 const userInfo = getUserInfo();
123 let theHasMore = true;
124 let thePcursor = undefined;
125
126 // 设置缓存数据
127 const cacheData = new AutoReplyCache({
128 title: data.title || data.desc || '无',
129 dataId: data.dataId,
130 });
131
132 try {
133 scheduleEvent({
134 tag: AutorReplyCommentScheduleEvent.Start,
135 status: 0,
136 });
137
138 while (theHasMore) {
139 cacheData.extendTTL(); // 延长缓存时间
140
141 scheduleEvent({
142 tag: AutorReplyCommentScheduleEvent.GetCommentListStart,
143 status: 0,
144 });
145
146 // 1. 获取评论列表
147 const {
148 list,
149 pageInfo: { pcursor, hasMore },
150 } = await platController.getCommentList(account, data, thePcursor);
151
152 if (list.length === 0) {
153 scheduleEvent({
154 tag: AutorReplyCommentScheduleEvent.End,
155 status: 0,
156 });
157 break;
158 }
159
160 scheduleEvent({
161 tag: AutorReplyCommentScheduleEvent.GetCommentListEnd,
162 status: 0,
163 });
164
165 // 2. 循环AI回复评论
166 for (const element of list) {
167 // 判断是否已经回复
168 const oldRecord = await this.getReplyCommentRecord(
169 userInfo.id,
170 account,
171 element.commentId,
172 );
173 if (oldRecord) continue;
174
175 // 判断是否已经回复
176 let hadReply = false;
177 for (const reply of element.subCommentList) {
178 if (account.uid === reply.userId) {
179 hadReply = true;
180 break;
181 }
182 }
183 if (!!hadReply) continue;
184
185 const aiRes = await toolsApi.aiRecoverReview({
186 content: element.content,
187 });
188 // AI接口错误
189 if (!aiRes) {
190 scheduleEvent({
191 tag: AutorReplyCommentScheduleEvent.Error,
192 status: -1,
193 error: '未获得AI产出内容',
194 });
195 cacheData.updateStatus(
196 AutorReplyCacheStatus.REEOR,
197 '未获得AI产出内容',
198 );
199 return false;
200 }
201
202 scheduleEvent({
203 tag: AutorReplyCommentScheduleEvent.ReplyCommentStart,
204 data: {
205 content: element.content,
206 aiContent: aiRes,
207 },
208 status: 0,
209 });
210
211 // 进行回复
212 const replyRes = await platController.replyComment(
213 account,
214 element.commentId,
215 aiRes,
216 {
217 dataId: data.dataId,
218 comment: element,
219 },
220 );
221 // 错误处理
222 if (!replyRes) {
223 scheduleEvent({
224 tag: AutorReplyCommentScheduleEvent.ReplyCommentEnd,
225 status: -1,
226 error: '回复评论失败',
227 });
228 continue;
229 }
230
231 scheduleEvent({
232 tag: AutorReplyCommentScheduleEvent.ReplyCommentEnd,
233 status: 0,
234 });
235
236 // 创建评论记录
237 this.createReplyCommentRecord(userInfo.id, account, {
238 id: element.commentId,
239 commentContent: element.content,
240 replyContent: aiRes,
241 });
242
243 // 延迟
244 await sleep(10 * 1000);
245 }
246
247 scheduleEvent({
248 tag: AutorReplyCommentScheduleEvent.ReplyCommentEnd,
249 status: 0,
250 });
251
252 thePcursor = pcursor;
253 theHasMore = !!hasMore;
254
255 if (!theHasMore) {
256 scheduleEvent({
257 tag: AutorReplyCommentScheduleEvent.End,
258 status: 0,
259 });
260 return true;
261 }
262 }
263 } catch (error) {
264 scheduleEvent({
265 tag: AutorReplyCommentScheduleEvent.Error,
266 status: -1,
267 error,
268 });
269 logger.error(['自动一键评论发生错误', error]);
270 cacheData.updateStatus(AutorReplyCacheStatus.REEOR, '进行中发生错误');
271 return false;
272 }
273
274 // 清除缓存
275 cacheData.delete();
276 }
277
278 /**
279 * 添加作品回复评论的任务到队列
280 * @param account
281 * @param data
282 * @param autoRun
283 */
284 async addReplyQueue(
285 account: AccountModel,
286 data: WorkData,
287 autoRun: AutoRunModel,
288 ): Promise<{
289 status: 0 | 1;
290 message?: string;
291 }> {
292 // 创建任务执行记录
293 const recordData = await this.autoRunService.createAutoRunRecord(autoRun);
294
295 // 添加到队列
296 this.replyQueue.add(() => {
297 this.autorReplyComment(
298 account,
299 data,
300 (e: {
301 tag: AutorReplyCommentScheduleEvent;
302 status: -1 | 0 | 1;
303 error?: any;
304 }) => {
305 if (e.tag === AutorReplyCommentScheduleEvent.Start) {
306 sysNotice('自动评论回复任务执行开始', `任务ID:${autoRun.id}`);
307 }
308
309 if (e.tag === AutorReplyCommentScheduleEvent.End) {
310 sysNotice('自动评论回复任务执行结束', `任务ID:${autoRun.id}`);
311 this.autoRunService.updateAutoRunRecordStatus(
312 recordData.id,
313 AutoRunRecordStatus.SUCCESS,
314 );
315 }
316
317 if (e.tag === AutorReplyCommentScheduleEvent.Error) {
318 sysNotice('自动评论回复任务-错误!!!', `任务ID:${autoRun.id}`);
319 this.autoRunService.updateAutoRunRecordStatus(
320 recordData.id,
321 AutoRunRecordStatus.FAIL,
322 );
323 }
324 },
325 );
326 });
327
328 return {
329 status: 1,
330 };
331 }
332 }
333
333 lines TYPESCRIPT