This commit is contained in:
熊潇 2025-05-05 00:01:36 +08:00
parent d6014b3c40
commit a412c09da0
20 changed files with 2911 additions and 102 deletions

View File

@ -128,6 +128,23 @@ class XhsClient {
} }
return {}; return {};
} }
/**
*
* @param {*} data
*/
setCookieMap(data = {}) {
const cookieDict = this.getCookieMap();
const newCookieDict = { ...cookieDict, ...data };
const cookieStr = Object.entries(newCookieDict)
.map(([key, value]) => {
const trimmedKey = key.trim();
const trimmedValue = value ? value.trim() : '';
return `${trimmedKey}=${trimmedValue}`;
})
.join('; ');
this.axiosInstance.defaults.headers.Cookie = cookieStr;
this.cookie = cookieStr;
}
/** /**
* Get X-S and X-T * Get X-S and X-T
* @param {*} url * @param {*} url

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,7 @@
import { XhsClient } from '../src/index.js';
// import { XhsClient } from '../dist/app.mjs';
import { cookie } from './common.ts';
const client = new XhsClient({ cookie } as any);
client.setCookieMap({ a1: 'thisistest' })
console.log(client.getCookieMap());
console.log(client.cookie);

View File

@ -0,0 +1,4 @@
import qs from 'querystring';
const r = qs.stringify({ a: 1, b: 2, c: 3 });
console.log(r); // a=1&b=2&c=3

View File

@ -4,7 +4,7 @@ import { XhsServices } from '@kevisual/xhs/services/xhs-services.ts';
export const app = new QueryRouterServer(); export const app = new QueryRouterServer();
export const xhsServices = new XhsServices(); export const xhsServices = new XhsServices();
const cookie = const cookie =
'a1=1969088bf22oidhober22hsb74h3qoavpucdvmrbb30000712484;abRequestId=e1b9d999-8838-528a-9933-5f3ac9134d8c;gid=yjKj8YYdSSUqyjKj8YYDi76FJJl3fxlDdFJJSDDxMDW4xfq8qAl0hh888WyJ4Y48ySjjfKfd;loadts=1746193349304;sec_poison_id=74e35006-555a-47a5-a11b-7d4a2482b933;unread={%22ub%22:%2268077ca2000000001b03bff6%22%2C%22ue%22:%22680e491b000000000b015506%22%2C%22uc%22:30};web_session=040069b2e9c511ca302098d5213a4b8556ed1d;webBuild=4.62.3;webId=bffbeec4c301c7b3dc284ee35dd742fb;websectiga=cffd9dcea65962b05ab048ac76962acee933d26157113bb213105a116241fa6c;xsecappid=xhs-pc-web;acw_tc=0a00d5b517461933472156520e55fc213ae73b04699cd042b86f1525ccab06;'; 'a1=1969a2df762vy6p46vet3jjpwfvnoce52hge24v0430000640615;abRequestId=48bccb63-a540-5533-8215-546916a6386f;gid=yjKj0JdyjKJ4yjKj0JfiWx4hKJhvKU4Khd9qk84VVUEihdq8IlSd2J888K48Ky28SSqJKYSK;loadts=1746343425888;sec_poison_id=32d8febc-7543-41e7-8a1f-c652d32a1e1a;unread={%22ub%22:%2267f73e21000000001b0384ea%22%2C%22ue%22:%226812f08300000000090166d7%22%2C%22uc%22:32};web_session=040069b6528dbc23c355705e223a4b27b6660a;webBuild=4.62.3;webId=05b45ad626037308d58668196c6af47d;websectiga=8886be45f388a1ee7bf611a69f3e174cae48f1ea02c0f8ec3256031b8be9c7ee;xsecappid=xhs-pc-web;acw_tc=0a00d14717463434250061733e8b2fcd3804e9b06020e1f339ebd8b7e80fc4;';
xhsServices.createRoot({ xhsServices.createRoot({
cookie, cookie,

View File

@ -0,0 +1,6 @@
import { XhsClient } from '../xhs.ts';
export const addNote = async function(){
const that = this as XhsClient;
//
}

View File

@ -12,7 +12,8 @@ const parseComment = (comment: CommonentInfo) => {
}; };
export class Parse { export class Parse {
static getComment(mention: Mention) { static getComment(mention: Mention) {
if (mention.type === 'mention/comment') { const typeList = ['comment/item', 'mention/comment', 'comment/comment'];
if (typeList.includes(mention.type)) {
const comment_info = mention.comment_info; const comment_info = mention.comment_info;
const comment = parseComment(comment_info); const comment = parseComment(comment_info);
const target_comment = parseComment(comment_info.target_comment); const target_comment = parseComment(comment_info.target_comment);

View File

@ -21,17 +21,21 @@ export type CommonentInfo = {
content: string; content: string;
target_comment: CommonentInfo; target_comment: CommonentInfo;
user_info?: UserInfo; user_info?: UserInfo;
image_list?: string[];
}; };
export type MentionItem = { export type MentionItem = {
id: string;
type: 'mention/item'; type: 'mention/item';
track_type: '2'; track_type: '2';
title: string; title: string;
user_info: UserInfo; user_info: UserInfo;
item_info: {} & NoteBase; item_info: {} & NoteBase;
comment_info: CommonentInfo;
}; };
export type MentionComment = { export type MentionComment = {
type: 'mention/comment'; id: string;
type: 'mention/comment' | 'comment/comment';
track_type: '8'; track_type: '8';
title: string; title: string;
item_info: {} & NoteBase; item_info: {} & NoteBase;

View File

@ -1,7 +1,7 @@
import { getApiInfo } from './xhs-api/api.ts'; import { getApiInfo } from './xhs-api/api.ts';
import { XhsClient as XhsClientBase } from '@kevisual/xhs-core'; import { XhsClient as XhsClientBase } from '@kevisual/xhs-core';
import { Mention, CommonentInfo } from './xhs-type/mention.ts'; import { Mention, CommonentInfo, ResponseMession } from './xhs-type/mention.ts';
import { pick } from 'lodash-es';
export type Result<T> = { export type Result<T> = {
code: number; // 0: success code: number; // 0: success
msg?: string; msg?: string;
@ -44,7 +44,7 @@ export const getSign = async (signInfo: SignInfo, options?: SignOptions): Promis
}, },
body: JSON.stringify({ body: JSON.stringify({
uri: uri, uri: uri,
data: data, data,
a1, a1,
web_session: web_session, web_session: web_session,
}), }),
@ -71,19 +71,21 @@ export class XhsClient extends XhsClientBase {
console.log('url', data.url); console.log('url', data.url);
console.log('status', data?.response?.status); console.log('status', data?.response?.status);
if (data.response) { if (data.response) {
console.log('data', data.response.data); // console.log('data', data.response.data);
} }
} else if (msg === 'request') { } else if (msg === 'request') {
console.log('request', data); const { method, url } = data || {};
const headers = pick(data?.headers || {}, ['Cookie', 'x-s', 'x-t', 'x-s-common']);
// console.log('request', { headers, method, url });
} else if (msg === 'html') { } else if (msg === 'html') {
// console.log('html', response); // console.log('html', response);
} }
switch (msg) { switch (msg) {
case 'get': case 'get':
console.log('get', data); // console.log('get', data);
break; break;
case 'sign': case 'sign':
console.log('sign', data); // console.log('sign', data);
break; break;
} }
} }
@ -157,7 +159,7 @@ export class XhsClient extends XhsClientBase {
* @uri /api/sns/web/v1/you/mentions * @uri /api/sns/web/v1/you/mentions
* @returns * @returns
*/ */
async getMention(num = 20): Promise<Result<Mention>> { async getMention(num = 20): Promise<Result<ResponseMession>> {
const url = '/api/sns/web/v1/you/mentions'; const url = '/api/sns/web/v1/you/mentions';
const response = await this.get( const response = await this.get(
url, url,
@ -184,6 +186,11 @@ export class XhsClient extends XhsClientBase {
const xs = _sign?.['x-s']; const xs = _sign?.['x-s'];
const xt = _sign?.['x-t']; const xt = _sign?.['x-t'];
const b1 = _sign?.['b1']; const b1 = _sign?.['b1'];
const newA1 = _sign?.['a1'];
if (a1 !== newA1) {
this.setCookieMap({ a1: newA1 });
this.printResult('cookie change', a1);
}
if (res && xs) { if (res && xs) {
headers['x-s'] = xs; headers['x-s'] = xs;
headers['x-t'] = xt; headers['x-t'] = xt;
@ -214,15 +221,23 @@ export class XhsClient extends XhsClientBase {
* @param comment * @param comment
* @returns * @returns
*/ */
async postComment(comment: { note_id: string; comment_id: string; content: string }) { async postComment(comment: { note_id: string; comment_id?: string; content: string; images_info?: any, images?: string[] }) {
const uri = '/api/sns/web/v1/comment/post'; const uri = '/api/sns/web/v1/comment/post';
try { try {
const data = { const data = {
note_id: comment.note_id, note_id: comment.note_id,
content: comment.content, content: comment.content,
target_comment_id: comment.comment_id,
at_users: [], // at_users: [], //
}; };
if (comment.comment_id) {
data['target_comment_id'] = comment.comment_id;
}
if (comment.images_info) {
data['images_info'] = comment.images_info;
}
if (comment.images) {
data['images'] = comment.images;
}
type PostCommentResponse = { type PostCommentResponse = {
comment: CommonentInfo; comment: CommonentInfo;
time: number; time: number;

View File

@ -1,5 +1,9 @@
import { app, xhsServices } from '@kevisual/xhs/app.ts'; import { app, xhsServices } from '@kevisual/xhs/app.ts';
import { Parse } from '@kevisual/xhs/libs/parse.ts';
import { Mention } from '@kevisual/xhs/libs/xhs-type/mention.ts';
const sleep = (ms: number) => {
return new Promise((resolve) => setTimeout(resolve, ms));
};
app app
.route({ .route({
path: 'mention', path: 'mention',
@ -7,11 +11,9 @@ app
description: '获取提及列表', description: '获取提及列表',
}) })
.define(async (ctx) => { .define(async (ctx) => {
//
const client = xhsServices.getClient(); const client = xhsServices.getClient();
const res = await client.getUnread(); const res = await client.getUnread();
if (res.code === 0) { if (res.code === 0) {
const unread_count = res.data.unread_count;
ctx.body = res.data; ctx.body = res.data;
} else { } else {
ctx.body = { ctx.body = {
@ -20,6 +22,19 @@ app
} }
}) })
.addTo(app); .addTo(app);
app
.route({
path: 'mention',
key: 'postRead',
description: '标记为已读',
})
.define(async (ctx) => {
const type = ctx.query.type || 'mentions';
const client = xhsServices.getClient();
const res = await client.postRead(type);
ctx.body = res.data;
})
.addTo(app);
app app
.route({ .route({
path: 'mention', path: 'mention',
@ -58,14 +73,19 @@ app
key: 'addComment', key: 'addComment',
}) })
.define(async (ctx) => { .define(async (ctx) => {
const { node_id, comment_id, content } = ctx.query; const { note_id, comment_id, content } = ctx.query;
const client = xhsServices.getClient(); const client = xhsServices.getClient();
const res = await client.postComment({ const res = await client.postComment({
note_id: node_id, note_id: note_id,
comment_id: comment_id, comment_id: comment_id,
content, content,
}); });
ctx.body = res.data; if (res.code === 0) {
ctx.body = res.data;
} else {
console.log('添加评论失败', res.code);
ctx.throw(res.code, '添加评论失败');
}
}) })
.addTo(app); .addTo(app);
app app
@ -84,6 +104,28 @@ app
const num = ctx.query.num; const num = ctx.query.num;
const client = xhsServices.getClient(); const client = xhsServices.getClient();
const res = await client.getMention(num); const res = await client.getMention(num);
ctx.body = res.data; if (res.code === 0) {
const mentionList = res.data.message_list;
const handleMention: any[] = [];
for (const mention of mentionList) {
const mention_id = mention.id;
const note_id = mention.item_info.id;
const xsec_token = mention.item_info.xsec_token;
let comment: any = Parse.getComment(mention);
// console.log('note_id', note_id, 'xsec_token', xsec_token, comment);
handleMention.push({
mention_id,
note_id,
xsec_token,
comment,
mention,
});
}
console.log('获取提及列表成功', res.code, res.data?.message_list?.length);
ctx.body = handleMention;
} else {
console.log('获取提及列表失败', res.code);
ctx.throw(res.code, '获取提及列表失败');
}
}) })
.addTo(app); .addTo(app);

View File

@ -1,6 +1,6 @@
import { XhsClient } from '@kevisual/xhs/libs/xhs.ts'; import { XhsClient } from '@kevisual/xhs/libs/xhs.ts';
import { Sequelize } from 'sequelize'; import { Sequelize } from 'sequelize';
import { createSequelize } from '@kevisual/xhs/services/xhs-db/db.ts'; // import { createSequelize } from '@kevisual/xhs/services/xhs-db/db.ts';
import path from 'node:path'; import path from 'node:path';
import fs from 'node:fs'; import fs from 'node:fs';
@ -17,7 +17,7 @@ type XhsClientMap = {
client: XhsClient; client: XhsClient;
key: string; key: string;
options: XhsClientOptions; options: XhsClientOptions;
db: Sequelize; db?: Sequelize;
}; };
type XhsServicesOptions = { type XhsServicesOptions = {
root?: string; root?: string;
@ -49,16 +49,16 @@ export class XhsServices {
if (!fs.existsSync(storage) || !isNew) { if (!fs.existsSync(storage) || !isNew) {
isNew = true; isNew = true;
} }
const db = createSequelize({ storage: storage }); // const db = createSequelize({ storage: storage });
const xhsClientMap = { const xhsClientMap = {
client, client,
key, key,
options, options,
db, // db,
}; };
if (isNew) { // if (isNew) {
this.initDb(xhsClientMap); // this.initDb(xhsClientMap);
} // }
this.map.set(key, xhsClientMap); this.map.set(key, xhsClientMap);
return client; return client;

View File

@ -1,4 +1,4 @@
import { xhsServices } from '../index.ts'; import { xhsServices, app } from '../index.ts';
import { program } from 'commander'; import { program } from 'commander';
export { program, xhsServices }; export { program, xhsServices, app };

View File

@ -7,8 +7,30 @@ program
const client = xhsServices.getClient(); const client = xhsServices.getClient();
const res = await client.postComment({ const res = await client.postComment({
note_id: '68136dab0000000007034c46', note_id: '68136dab0000000007034c46',
content: 'test comment 233', content: 'test',
comment_id: '68136dcf000000000401a8c9', comment_id: '681741610000000004014e77',
// images_info: {
// images: [
// {
// file_id: '1040g2h031h28ues73i405ostgpcpgo3mff4lk68',
// metadata: { source: -1 },
// stickers: { version: 2, floating: [] },
// extra_info_json: '{"mimeType":"image/jpeg"}',
// },
// ],
// },
images: ['http://sns-img-qc.xhscdn.com/comment/1040g2h031h28ues73i405ostgpcpgo3mff4lk68']
}); });
console.log(res); console.log(res);
}); });
// http://sns-img-qc.xhscdn.com/comment/1040g2h031h28ues73i405ostgpcpgo3mff4lk68
const item_info = {
type: 'note_info',
id: '68136dab0000000007034c46',
image: 'http://ci.xiaohongshu.com/notes_pre_post/1040g3k031gulc4mn3q505pp6prq734o3hmigh70?imageView2/2/w/1080/format/jpg',
image_info: {
url: 'http://ci.xiaohongshu.com/notes_pre_post/1040g3k031gulc4mn3q505pp6prq734o3hmigh70?imageView2/2/w/1080/format/jpg',
width: 1200,
height: 1600,
},
};

View File

@ -1,10 +1,12 @@
import { xhsServices, program } from '../common.ts'; import { xhsServices, program, app } from '../common.ts';
import util from 'node:util'; import util from 'node:util';
import { omit } from 'lodash-es';
const getMentions = async () => { const getMentions = async () => {
try { try {
const client = xhsServices.getClient(); const client = xhsServices.getClient();
const res = await client.getMention(1); const res = await client.getMention(1);
if (res.code) { if (res.code === 0) {
const data = res.data || {}; const data = res.data || {};
console.log('getMentionNotifications', util.inspect(data, { depth: 10 })); console.log('getMentionNotifications', util.inspect(data, { depth: 10 }));
} }
@ -27,3 +29,20 @@ const getTestMentionNote = async () => {
}; };
program.command('test-mention').description('get mention note').action(getTestMentionNote); program.command('test-mention').description('get mention note').action(getTestMentionNote);
const queryMention = async () => {
const res = await app.call({
path: 'mention',
key: 'getMention',
payload: {
num: 1,
},
});
if (res.code === 200) {
let data = res.body || [];
// data = data.map((item) => omit(item, 'mention'));
console.log('queryMention', util.inspect(data, { depth: 10 }));
}
};
program.command('query-mention').description('query mention').action(queryMention);

View File

@ -1,3 +0,0 @@
// https://edith.xiaohongshu.com/api/sns/web/unread_count
export const XHS_GET_UNREAD = 'unread_count';
export const XHS_QUEUE_NAME = 'XHS_QUEUE';

View File

@ -0,0 +1,4 @@
import { queue, taskApp, XHS_QUEUE_NAME } from './task.ts';
import './routes/mention.ts';
export { queue, taskApp, XHS_QUEUE_NAME };

32
src/task/queue.ts Normal file
View File

@ -0,0 +1,32 @@
import { redis } from '@/modules/redis.ts';
import { Queue } from 'bullmq';
import { nanoid } from 'nanoid';
import { addUnreadTask, XHS_QUEUE_NAME } from '@/task/task.ts';
export const queue = new Queue(XHS_QUEUE_NAME, {
connection: redis,
});
// 初始启动
async function start() {
addUnreadTask();
}
//
start();
const getTasks = async () => {
const tasks = await queue.getJobs(['waiting', 'active', 'completed', 'failed']);
return tasks;
};
const getTask = async (id: string) => {
const task = await queue.getJob(id);
return task;
};
const removeTask = async (id: string) => {
const task = await queue.getJob(id);
if (task) {
await task.remove();
}
};
// const task = await getTask('4');
// console.log('task', task);

100
src/task/routes/mention.ts Normal file
View File

@ -0,0 +1,100 @@
import { taskApp, queue, xhsApp } from '../task.ts';
import { random, omit } from 'lodash-es';
import util from 'node:util';
export const sleep = (ms: number) => {
return new Promise((resolve) => setTimeout(resolve, ms));
};
taskApp
.route({
path: 'task',
key: 'getUnread',
})
.define(async (ctx) => {
const res = await xhsApp.call({
path: 'mention',
key: 'getUnread',
});
if (res.code === 200) {
const data = res.body;
const unread_count = data.unread_count;
console.log('unread_count====', data);
if (unread_count > 0) {
queue.add(
'mention',
{
path: 'task',
key: 'getMention',
payload: {
unread_count,
},
},
{
attempts: 3,
delay: 0,
removeOnComplete: true,
removeOnFail: {
age: 24 * 3600, // keep up to 24 hours
},
},
);
}
ctx.body = {
job: unread_count,
};
}
})
.addTo(taskApp);
taskApp
.route({
path: 'task',
key: 'getMention',
})
.define(async (ctx) => {
const { unread_count } = ctx.query;
if (unread_count > 0) {
const mentionRes = await xhsApp.call({
path: 'mention',
key: 'getMention',
payload: {
num: unread_count,
},
});
console.log('mentionRes', mentionRes.body);
if (mentionRes.code === 200) {
let data = mentionRes.body || [];
// data = data.map((item) => omit(item, 'mention'));
console.log('queryMention', util.inspect(data, { depth: 10 }));
for (let i = 0; i < data.length; i++) {
const item = data[i];
const note_id = item.note_id;
const xsec_token = item.xsec_token;
const comment_id = item.comment.comment_id;
const content = item.comment?.content || 'test';
const postData = {
note_id,
content,
comment_id,
};
const res = await xhsApp.call({
path: 'mention',
key: 'addComment',
payload: postData,
});
console.log('addComment', postData, 'res', res.body);
}
}
const postRead = await xhsApp.call({
path: 'mention',
key: 'postRead',
});
console.log('postRead', postRead.body);
}
await sleep(1000);
ctx.body = {
job: unread_count,
};
})
.addTo(taskApp);

View File

@ -1,44 +1,38 @@
// https://edith.xiaohongshu.com/api/sns/web/unread_count
import { QueryRouterServer } from '@kevisual/router';
import { redis } from '@/modules/redis.ts'; import { redis } from '@/modules/redis.ts';
import { Queue } from 'bullmq'; import { Queue } from 'bullmq';
import { app as xhsApp } from '@kevisual/xhs/index';
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
import { XHS_QUEUE_NAME } from '@/task/common.ts'; export const XHS_GET_UNREAD = 'unread_count';
export const XHS_QUEUE_NAME = 'XHS_QUEUE';
export const taskApp = new QueryRouterServer();
export { xhsApp };
export const queue = new Queue(XHS_QUEUE_NAME, { export const queue = new Queue(XHS_QUEUE_NAME, {
connection: redis, connection: redis,
}); });
// 初始启动 export const addUnreadTask = async (nextTime = 0) => {
async function start() { const id = 'unread';
const res = await queue.add( const job = await queue.add(
'start-job', 'unread',
{ {
name: 'initialJob', path: 'task',
key: 'getUnread',
}, },
{ {
delay: 0, // 立即执行 delay: nextTime,
removeOnComplete: true, removeOnComplete: true,
removeOnFail: true, removeOnFail: {
jobId: nanoid(), // 使用 nanoid 生成唯一 ID age: 24 * 3600, // keep up to 24 hours
},
jobId: id,
}, },
); );
console.log('Queue started:', res.id); return {
} id,
// job,
start(); };
const getTasks = async () => {
const tasks = await queue.getJobs(['waiting', 'active', 'completed', 'failed']);
return tasks;
}; };
const getTask = async (id: string) => {
const task = await queue.getJob(id);
return task;
};
const removeTask = async (id: string) => {
const task = await queue.getJob(id);
if (task) {
await task.remove();
}
};
// const task = await getTask('4');
// console.log('task', task);

View File

@ -1,52 +1,112 @@
import { redis } from '@/modules/redis.ts'; import { redis } from '@/modules/redis.ts';
import { Queue, Worker } from 'bullmq'; import { Worker } from 'bullmq';
import { clamp } from 'lodash-es'; import { add, clamp } from 'lodash-es';
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
import { queue, XHS_QUEUE_NAME, taskApp } from './index.ts';
const XHS_QUEUE_NAME = 'XHS_QUEUE'; import { addUnreadTask } from './task.ts';
export const queue = new Queue(XHS_QUEUE_NAME); import dayjs from 'dayjs';
export const sleep = (ms: number) => { export const sleep = (ms: number) => {
return new Promise((resolve) => setTimeout(resolve, ms)); return new Promise((resolve) => setTimeout(resolve, ms));
}; };
class TimeRecorder {
startTime: number;
endTime: number;
duration: number;
updateTime: number;
maxDuration: number = 60 * 1000; // 20s;
constructor() {
const now = Date.now();
this.startTime = now;
this.endTime = now;
this.updateTime = now;
this.duration = 0;
}
start() {
this.startTime = Date.now();
return this.startTime;
}
end() {
this.endTime = Date.now();
this.duration = this.endTime - this.startTime;
return this.endTime;
}
update() {
this.updateTime = Date.now();
return this.updateTime;
}
getClampDuration() {
const duration = Date.now() - this.updateTime;
return {
duration: duration,
maxDuration: this.maxDuration,
updateTime: this.updateTime,
nextTime: clamp(this.maxDuration - duration, 0, this.maxDuration),
};
}
time() {
return {
startTime: this.startTime,
endTime: this.endTime,
duration: this.duration,
updateTime: this.updateTime,
};
}
}
const timeRecorder = new TimeRecorder();
let errorCount = 0;
export const worker = new Worker( export const worker = new Worker(
XHS_QUEUE_NAME, XHS_QUEUE_NAME,
async (job) => { async (job) => {
const startTime = Date.now(); const timer = new TimeRecorder();
console.log('job', job.name, job.data); const data = job.data;
await sleep(1000); if (data.path === 'task' && data.key === 'getUnread') {
const endTime = Date.now(); console.log('====run time', dayjs().format('YYYY-MM-DD HH:mm:ss'));
const duration = endTime - startTime; timeRecorder.update();
}
const res = await taskApp.call(data);
if (res.code !== 200) {
console.log('job error', job.name, job.id, res);
return { errorCount++;
startTime: startTime, if (errorCount > 3) {
endTime: endTime, queue.pause();
duration: duration, console.log('error count', errorCount);
}; }
throw new Error('job error' + job.name + ' ' + job.id);
}
errorCount = 0;
timer.end();
return timer.time();
}, },
{ connection: redis, concurrency: 1 }, { connection: redis, concurrency: 1, limiter: { max: 1, duration: 2000 } },
); );
worker.on('completed', async (job) => { worker.on('completed', async (job) => {
console.log('job completed', job.name, job.id, job.returnvalue); const jobCounts = await queue.getJobCounts('waiting', 'wait', 'delayed');
const duration = job.returnvalue.duration || 0; if (job.name !== 'unread') {
const maxNextTime = 20 * 1000; // 5 minutes console.log('job completed', job.name, job.id, job.returnvalue, jobCounts.delayed, jobCounts.wait);
const nextTime = clamp(maxNextTime - duration, 0, maxNextTime); }
const hasJobs = await queue.getJobCounts('waiting', 'wait', 'delayed'); if (jobCounts.delayed + jobCounts.wait > 0) {
console.log('hasJobs', nextTime, 'joblenght', hasJobs); // console.log('======has jobs, no need to add new job');
if (hasJobs.delayed + hasJobs.wait > 0) {
console.log('======has jobs, no need to add new job');
} else { } else {
const id = nanoid(); const up = timeRecorder.getClampDuration();
queue.add( const nextTime = up.nextTime;
'repeact-call-job' + id, const unread = await queue.getJob('unread');
{}, if (!unread) {
{ addUnreadTask(nextTime);
delay: nextTime, }
removeOnComplete: true,
removeOnFail: {
age: 24 * 3600, // keep up to 24 hours
},
jobId: id,
},
);
} }
}); });
const init = async () => {
const jobCounts = await queue.getJobCounts('waiting', 'wait', 'delayed');
if (jobCounts.delayed + jobCounts.wait > 0) {
// console.log('======has jobs, no need to add new job');
} else {
const unread = await queue.getJob('unread');
if (!unread) {
addUnreadTask(0);
timeRecorder.update();
}
}
};
init();