diff --git a/packages/xhs-core/src/exception.js b/packages/xhs-core/src/exception.js
index 837b90d..92a3260 100644
--- a/packages/xhs-core/src/exception.js
+++ b/packages/xhs-core/src/exception.js
@@ -1,6 +1,7 @@
// ErrorTuple and ErrorEnum
const ErrorEnum = {
IP_BLOCK: { code: 300012, msg: '网络连接异常,请检查网络设置或重启试试' },
+ NOTE_CANT_GET: { code: 300031, msg: '当前笔记暂时无法浏览' },
NOTE_ABNORMAL: { code: -510001, msg: '笔记状态异常,请稍后查看' },
NOTE_SECRETE_FAULT: { code: -510001, msg: '当前内容无法展示' },
SIGN_FAULT: { code: 300015, msg: '浏览器异常,请尝试关闭/卸载风险插件或重启试试!' },
diff --git a/packages/xhs-core/src/index.js b/packages/xhs-core/src/index.js
index 0838ac4..bb007db 100644
--- a/packages/xhs-core/src/index.js
+++ b/packages/xhs-core/src/index.js
@@ -58,7 +58,12 @@ class XhsClient {
this.cookie = cookie;
}
}
-
+ /**
+ * @params {*} args
+ */
+ printResult(...args) {
+ //
+ }
// Getter for cookie
get cookie() {
return this.axiosInstance.defaults.headers.Cookie;
@@ -82,14 +87,11 @@ class XhsClient {
const X_S = x_s_result['X-s'];
const X_t = x_s_result['X-t'].toString();
const X_S_COMMON = getXCommon(a1, b1, X_S, X_t);
- // this.axiosInstance.defaults.headers['X-s'] = X_S;
- // this.axiosInstance.defaults.headers['X-t'] = X_t;
- // this.axiosInstance.defaults.headers['X-s-common'] = X_S_COMMON;
return {
headers: {
- 'X-s': X_S,
- 'X-t': X_t,
- 'X-s-common': X_S_COMMON,
+ 'x-s': X_S,
+ 'x-t': X_t,
+ 'xs-common': X_S_COMMON,
},
};
}
@@ -123,9 +125,11 @@ class XhsClient {
async request(method, url, config = {}) {
try {
- const response = await this.axiosInstance({ method, url, ...config });
+ delete config.sign;
+ const requestOptions = { method, url, ...config };
+ this.printResult('request', requestOptions);
+ const response = await this.axiosInstance(requestOptions);
if (!response.data) return response;
- // console.log('response', response)
if (response.status === 471 || response.status === 461) {
const verifyType = response.headers['verifytype'];
const verifyUuid = response.headers['verifyuuid'];
@@ -133,8 +137,12 @@ class XhsClient {
}
const data = response.data;
+ this.printResult('reponse', {
+ url: url,
+ response,
+ });
if (data.success) {
- return data.data || data.success;
+ return data;
} else if (data.code === ErrorEnum.IP_BLOCK.code) {
throw new IPBlockError(ErrorEnum.IP_BLOCK.msg, response);
} else if (data.code === ErrorEnum.SIGN_FAULT.code) {
@@ -167,9 +175,11 @@ class XhsClient {
if (params) {
uri = `${uri}?${qs.stringify(params)}`;
}
- if (config.sign) {
- await config.sign(uri, data, config);
- } else {
+ const header = config.headers || {};
+ const xs = header['x-s'];
+ if (config.sign && !xs) {
+ await config.sign(uri, null, config);
+ } else if (!xs) {
const { headers } = this._preHeaders(uri, null);
config = { ...config, headers: { ...config.headers, ...headers } };
}
@@ -197,9 +207,11 @@ class XhsClient {
async post(uri, data = null, config = {}) {
let jsonStr = data ? JSON.stringify(data) : null;
let endpoint = this._host;
- if (config.sign) {
+ const header = config.headers || {};
+ const xs = header['x-s'];
+ if (config.sign && !xs) {
await config.sign(uri, data, config);
- } else {
+ } else if (!xs) {
const { headers } = this._preHeaders(uri, data);
config = { ...config, headers: { ...config.headers, ...headers } };
}
@@ -226,10 +238,11 @@ class XhsClient {
/**
* 获取笔记详情
* 注意: 需要xsec_token
+ * @uri /api/sns/web/v1/feed
* @param {string} noteId
* @returns
*/
- async getNoteById(noteId, xsecToken, xsecSource = 'pc_feed') {
+ async getNoteById(noteId, xsecToken, xsecSource = 'pc_feed', config = {}) {
if (!xsecToken) {
throw new Error('xsecToken is required');
}
@@ -242,16 +255,24 @@ class XhsClient {
const uri = '/api/sns/web/v1/feed';
try {
- const res = await this.post(uri, data);
+ const res = await this.post(uri, data, config);
return res.items[0].note_card;
} catch (error) {
console.error('Error fetching note:', error);
throw error;
}
}
-
+ /**
+ * 获取笔记详情
+ * @uri /api/sns/web/v1/feed
+ * @param {string} noteId
+ * @param {string} xsecToken
+ * @param {string} [xsecSource=pc_feed]
+ * @returns
+ */
async getNoteByIdFromHtml(noteId, xsecToken, xsecSource = 'pc_feed') {
const url = `https://www.xiaohongshu.com/explore/${noteId}?xsec_token=${xsecToken}&xsec_source=${xsecSource}`;
+ this.printResult('html', { url, noteId, xsecToken, xsecSource });
let html = '';
try {
const response = await this.axiosInstance.get(url, {
@@ -283,17 +304,29 @@ class XhsClient {
throw error;
}
}
-
+ /**
+ * 获取用户信息
+ * @uri /api/sns/web/v1/user/selfinfo
+ * @returns
+ */
async getSelfInfo() {
const uri = '/api/sns/web/v1/user/selfinfo';
return this.get(uri);
}
-
+ /**
+ * @uri /api/sns/web/v2/user/me
+ * @returns
+ */
async getSelfInfoV2() {
const uri = '/api/sns/web/v2/user/me';
return this.get(uri);
}
-
+ /**
+ * 获取用户信息
+ * @uri /api/sns/web/v1/user/otherinfo
+ * @param {string} userId
+ * @returns
+ */
async getUserInfo(userId) {
const uri = '/api/sns/web/v1/user/otherinfo';
const params = {
@@ -304,6 +337,7 @@ class XhsClient {
/**
*
+ * @uri /api/sns/web/v1/search/notes
* @param {string} keyword 关键词
* @param {number} page 页码
* @param {number} pageSize 分页查询的数量
@@ -329,21 +363,26 @@ class XhsClient {
/**
* 获取笔记评论
+ * @uri /api/sns/web/v2/comment/page
* @param {string} noteId 笔记id
* @param {string} cursor 分页查询的下标,默认为""
+ * @param {Object} params 其他参数
* @returns
*/
- async getNoteComments(noteId, cursor = '') {
+ async getNoteComments(noteId, cursor = '', otherParams = {}) {
const uri = '/api/sns/web/v2/comment/page';
const params = {
note_id: noteId,
cursor: cursor,
+ image_formats: 'jpg,webp,avif',
+ ...otherParams,
};
return this.get(uri, params);
}
/**
* 获取用户笔记
+ * @uri /api/sns/web/v1/user_posted
* @param {*} userId
* @param {*} cursor
* @returns
@@ -361,6 +400,7 @@ class XhsClient {
/**
* 获取账号@我通知
+ * @uri /api/sns/web/v1/you/mentions
* @param {*} num
* @param {*} cursor
* @returns
@@ -373,6 +413,7 @@ class XhsClient {
/**
* 获取点赞通知
+ * @uri /api/sns/web/v1/you/likes
* @param {*} num
* @param {*} cursor
* @returns
@@ -385,16 +426,23 @@ class XhsClient {
/**
* 获取关注通知
+ * @uri /api/sns/web/v1/you/connections
* @param {*} num
* @param {*} cursor
* @returns
*/
- async getFollowNotifications(num = 20, cursor = '') {
+ async getFollowNotifications(num = 20, cursor = '', config = {}) {
const uri = '/api/sns/web/v1/you/connections';
const params = { num: num, cursor: cursor };
- return this.get(uri, params);
+ return this.get(uri, params, config);
}
-
+ /**
+ * 获取用户信息
+ * @uri /user/profile/{userId}
+ * @description 通过用户ID获取用户信息
+ * @param {string} userId
+ * @returns
+ */
async getUserInfoFromHtml(userId) {
const url = `https://www.xiaohongshu.com/user/profile/${userId}`;
try {
diff --git a/packages/xhs/a.html b/packages/xhs/a.html
deleted file mode 100644
index 475c04b..0000000
--- a/packages/xhs/a.html
+++ /dev/null
@@ -1,492 +0,0 @@
-
小红书 - 你访问的页面不见了
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
你访问的页面不见了
3 秒后将自动返回首页
\ No newline at end of file
diff --git a/packages/xhs/package.json b/packages/xhs/package.json
index d316366..ec6e81c 100644
--- a/packages/xhs/package.json
+++ b/packages/xhs/package.json
@@ -7,6 +7,7 @@
"scripts": {
"build": "bun run bun.config.mjs",
"postbuild": "dts -i src/index.js -o app.d.ts",
+ "cmd": "tsx src/test/command.ts ",
"dts": "dts -i src/index.js -o app.d.ts"
},
"file": [
@@ -20,8 +21,8 @@
"license": "MIT",
"packageManager": "pnpm@10.10.0",
"type": "module",
- "dependencies": {},
"devDependencies": {
- "@kevisual/xhs-core": "workspace:*"
+ "@kevisual/xhs-core": "workspace:*",
+ "@types/node": "^22.15.3"
}
}
\ No newline at end of file
diff --git a/packages/xhs/src/app.ts b/packages/xhs/src/app.ts
new file mode 100644
index 0000000..9614c8c
--- /dev/null
+++ b/packages/xhs/src/app.ts
@@ -0,0 +1,11 @@
+import { QueryRouterServer } from '@kevisual/router';
+import { XhsServices } from '@/services/xhs-services.ts';
+
+export const app = new QueryRouterServer();
+export const xhsServices = new XhsServices();
+export const cookie =
+ 'a1=1968ba02ff4xrt6hfrzdiz7ubs82j9y3vx11vfw9c40000317680;abRequestId=0a794332-4561-5f49-93f7-780b8b028e1f;access-token-creator.xiaohongshu.com=customer.creator.AT-68c517498636784561614544frjvxzj7yu8iewie;agora_session=6a0031373435393132333637323735343733393437313634000000000000;customerClientId=536706778174172;galaxy_creator_session_id=OhpHDDSoADhNEhnH5LLnQpletFLApu1fd91f;galaxy.creator.beaker.session.id=1745912429847011598150;gid=yjKqYfK0qDyYyj2DDSqd4ujxyW9kvxIuT62ddkMWhElyuxq8yDd6hl888q2WYy88j8i80yYD;loadts=1746020512562;sec_poison_id=441c932e-a6ac-4d8d-97ae-beb14adb1929;unread={%22ub%22:%2267eaf1fe000000001202c3ea%22%2C%22ue%22:%226803aa37000000001c0319d8%22%2C%22uc%22:35};web_session=040069b2e9c511ca302086ca253a4bde8b1cd1;webBuild=4.62.3;webId=97e5f097499594cad49aa0bd1a8ed83f;websectiga=3633fe24d49c7dd0eb923edc8205740f10fdb18b25d424d2a2322c6196d2a4ad;x-user-id-creator.xiaohongshu.com=639d86590000000026006076;xsecappid=xhs-pc-web;acw_tc=0a00df6217460205042195762e721fba339a0dbe8e4738b961a5ff15e74619;';
+
+xhsServices.createRoot({
+ cookie,
+});
diff --git a/packages/xhs/src/index.ts b/packages/xhs/src/index.ts
index eb2d05e..d5fde62 100644
--- a/packages/xhs/src/index.ts
+++ b/packages/xhs/src/index.ts
@@ -1,66 +1,5 @@
-import { XhsClient as XhsClientBase } from '@kevisual/xhs-core';
+import { XhsClient } from './libs/xhs.ts';
+import { app, xhsServices } from './app.ts';
+import './routes/index.ts';
-export const getSign = async (uri: string, data: any, a1: string, web_session?: string) => {
- const signs = await fetch('http://light.xiongxiao.me:5006/sign', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({
- uri,
- data,
- a1,
- web_session: web_session,
- }),
- }).then((res) => res.json());
- return signs;
-};
-type XhsOptions = {
- cookie?: string;
-};
-export class XhsClient extends XhsClientBase {
- constructor(opts: XhsOptions) {
- super(opts as any);
- }
- /**
- * 获取未读消息
- * @returns
- */
- async getUnread(): Promise {
- const url = '/api/sns/web/unread_count';
- const response = await this.get(url);
- return response;
- }
- async sign(uri: string, data: any, config: any) {
- let header = config?.header || {};
- const cookieDist = this.getCookieMap();
- const web_session = cookieDist['web_session'];
- const a1 = cookieDist['a1'];
- const res = await getSign(uri, data, a1, web_session);
- header['x-s'] = res['x-s'];
- header['x-t'] = res['x-t'];
- config.header = header;
- return config;
- }
- async getNoteById(noteId: string, xsecToken?: string) {
- const data = {
- source_note_id: noteId,
- image_scenes: ['CRD_WM_WEBP'],
- };
- const uri = '/api/sns/web/v1/feed';
-
- try {
- const response = await this.post(uri, data, { sign: this.sign.bind(this) });
- return response['items'][0]['node_card'];
- } catch (error) {
- console.log(error);
- }
- }
-}
-
-type UnreadCount = {
- unread_count: number;
- likes: number;
- connections: number;
- mentions: number;
-};
+export { XhsClient, app, xhsServices };
diff --git a/packages/xhs/src/libs/parse.ts b/packages/xhs/src/libs/parse.ts
new file mode 100644
index 0000000..d6ee6d5
--- /dev/null
+++ b/packages/xhs/src/libs/parse.ts
@@ -0,0 +1,61 @@
+import { Connection } from './xhs-type/connection.ts';
+import { Mention, CommonentInfo } from './xhs-type/mention.ts';
+import { NoteFromHtml } from './xhs-type/note.ts';
+const parseComment = (comment: CommonentInfo) => {
+ if (!comment) {
+ return null;
+ }
+ return {
+ comment_id: comment.id,
+ content: comment.content,
+ };
+};
+export class Parse {
+ static getComment(mention: Mention) {
+ if (mention.type === 'mention/comment') {
+ const comment_info = mention.comment_info;
+ const comment = parseComment(comment_info);
+ const target_comment = parseComment(comment_info.target_comment);
+ return {
+ ...comment,
+ target_comment,
+ };
+ }
+ return null;
+ }
+ static getUser(mention: Mention) {
+ const user_info = mention?.item_info?.user_info;
+ if (user_info) {
+ return {
+ user_id: user_info.userid,
+ nickname: user_info.nickname,
+ image: user_info.image,
+ };
+ }
+ return null;
+ }
+ static getNote(note: NoteFromHtml) {
+ return {
+ node_id: note.note_id,
+ xsec_token: note.xsec_token,
+ time: note.time,
+ last_update_time: note.last_update_time,
+ desc: note.desc,
+ title: note.title,
+ image_list: note.image_list,
+ user: note.user,
+ };
+ }
+ static getConnects(message: Connection[]) {
+ return message.map((item) => {
+ const user = item.user;
+ return {
+ fstatus: user.fstatus,
+ xsec_token: user.xsec_token,
+ userid: user.userid,
+ nickname: user.nickname,
+ images: user.images,
+ };
+ });
+ }
+}
diff --git a/packages/xhs/src/libs/xhs-type/connection.ts b/packages/xhs/src/libs/xhs-type/connection.ts
new file mode 100644
index 0000000..6b964e5
--- /dev/null
+++ b/packages/xhs/src/libs/xhs-type/connection.ts
@@ -0,0 +1,9 @@
+export type Connection = {
+ type: 'fllow/you';
+ title: string;
+ id: string;
+ track_type: string;
+ user: { fstatus: 'both' | 'follows' | 'fans'; red_official_verify_type: number; xsec_token: string; userid: string; nickname: string; images: string };
+ time: number;
+ score: number;
+};
diff --git a/packages/xhs/src/libs/xhs-type/mention.ts b/packages/xhs/src/libs/xhs-type/mention.ts
new file mode 100644
index 0000000..c67eae7
--- /dev/null
+++ b/packages/xhs/src/libs/xhs-type/mention.ts
@@ -0,0 +1,47 @@
+type NoteBase = {
+ /**
+ * note id
+ **/
+ id: string;
+ xsec_token: string;
+ type: 'note_info';
+ user_info: UserInfo;
+};
+
+export type UserInfo = {
+ image: string;
+ indicator: '作者';
+ nickname: string;
+ red_official_verify_type: 0;
+ userid: string;
+ xsec_token: string;
+};
+export type CommonentInfo = {
+ id: string;
+ content: string;
+ target_comment: CommonentInfo;
+ user_info?: UserInfo;
+};
+
+export type MentionItem = {
+ type: 'mention/item';
+ track_type: '2';
+ title: string;
+ user_info: UserInfo;
+ item_info: {} & NoteBase;
+};
+export type MentionComment = {
+ type: 'mention/comment';
+ track_type: '8';
+ title: string;
+ item_info: {} & NoteBase;
+ comment_info: CommonentInfo;
+};
+export type Mention = MentionItem | MentionComment;
+
+export type ResponseMession = {
+ has_more: boolean;
+ cursor: string;
+ strCursor: string;
+ message_list: Mention[];
+};
diff --git a/packages/xhs/src/libs/xhs-type/note.ts b/packages/xhs/src/libs/xhs-type/note.ts
new file mode 100644
index 0000000..f02ed30
--- /dev/null
+++ b/packages/xhs/src/libs/xhs-type/note.ts
@@ -0,0 +1,47 @@
+type InteractInfo = {
+ collected_count: string | number;
+ comment_count: string | number;
+ share_count: string | number;
+ followed: boolean;
+ relation: 'both';
+ liked: boolean;
+ liked_count: string | number;
+ collected: boolean;
+};
+export type NoteFromHtml = {
+ xsec_token: string;
+ interact_info: InteractInfo;
+ time: number;
+ last_update_time: number;
+ ip_location: string;
+ share_info: {
+ un_share: boolean;
+ };
+ note_id: string;
+ type: 'normal';
+ desc: string;
+ tag_list: string[];
+ at_user_list: { user_id: string; nickname: string; xsec_token: string }[];
+ title: string;
+ user: {
+ user_id: string;
+ nickname: string;
+ avatar: string;
+ xsec_token: string;
+ };
+ image_list: {
+ url: string;
+ trace_id: string;
+ url_per: string;
+ live_photo: boolean;
+ file_id: string;
+ width: number;
+ height: number;
+ info_list: {
+ image_scend: string; // WB_PRV WB_DFT,
+ url: string;
+ }[];
+ url_default: string;
+ stream: any;
+ }[];
+};
diff --git a/packages/xhs/src/libs/xhs.ts b/packages/xhs/src/libs/xhs.ts
new file mode 100644
index 0000000..4568108
--- /dev/null
+++ b/packages/xhs/src/libs/xhs.ts
@@ -0,0 +1,210 @@
+import { XhsClient as XhsClientBase } from '@kevisual/xhs-core';
+import { Mention, CommonentInfo } from './xhs-type/mention.ts';
+
+export type Result = {
+ code: number; // 0: success
+ msg?: string;
+ data?: T;
+ success?: boolean;
+};
+type SignInfo = {
+ uri: string;
+ data: any;
+ a1: string;
+ web_session?: string;
+};
+type SignOptions = {
+ signUrl?: string;
+};
+export const getSign = async (signInfo: SignInfo, options?: SignOptions) => {
+ const { uri, data, a1, web_session } = signInfo;
+ // console.log('getSign', uri, data, a1, web_session);
+ // let signUri = new URL(uri, 'http://light.xiongxiao.me:5006').pathname;
+ // signUri = '/api/sns/web/v2/user/me';
+ try {
+ let signUrl = options?.signUrl || 'http://light.xiongxiao.me:5006/sign';
+ // signUrl = 'http://localhost:5005/sign'
+ const signs = await fetch(signUrl, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ uri: uri,
+ data: data,
+ a1,
+ web_session: web_session,
+ }),
+ }).then((res) => res.json());
+ return signs;
+ } catch (error) {
+ return null;
+ }
+};
+type XhsOptions = {
+ cookie?: string;
+};
+type XhsSign = {
+ signUrl?: string;
+};
+export class XhsClient extends XhsClientBase {
+ signConfig?: XhsSign;
+ constructor(opts: XhsOptions) {
+ super(opts as any);
+ }
+ printResult(msg: string, response: any) {
+ if (msg === 'response') {
+ console.log('url', response.url);
+ if (response.response) {
+ console.log('status', response.response.status);
+ console.log('data', response.response.data);
+ }
+ } else if (msg === 'request') {
+ // console.log('request', response);
+ } else if (msg === 'html') {
+ // console.log('html', response);
+ }
+ }
+ /**
+ * 获取未读消息
+ * @returns
+ */
+ async getUnread(): Promise> {
+ const url = '/api/sns/web/unread_count';
+ const response = await this.get(url);
+
+ return response;
+ }
+ async postRead(type: 'unread_count' | 'likes' | 'mentions' | 'unread_count' = 'mentions') {
+ const uri = '/api/sns/web/v1/message/read';
+ const data = {
+ type,
+ };
+ type RetrunData = {
+ code: number;
+ msg: string;
+ success: boolean;
+ };
+ const response = await this.post(uri, data, {
+ sign: this.sign.bind(this),
+ });
+ return response as Result;
+ }
+ async getUserInfoFromHtml(userId: string) {
+ type ReturnData = {
+ ip_location: string;
+ desc: string;
+ imageb: string;
+ nickname: string;
+ images: string;
+ red_id: string;
+ gender: number;
+ };
+ const response = await super.getUserInfoFromHtml(userId);
+ return response as ReturnData;
+ }
+ async getFollowNotifications(num = 10, cursor = '') {
+ const url = '/api/sns/web/v1/you/connections';
+ type Connection = {
+ type: 'fllow/you';
+ title: string;
+ id: string;
+ track_type: string;
+ user: { fstatus: 'both' | 'follows' | 'fans'; red_official_verify_type: number; xsec_token: string; userid: string; nickname: string; images: string };
+ time: number;
+ score: number;
+ };
+ type ReturnData = {
+ message_list: Connection[];
+ has_more: boolean;
+ cursor: number;
+ strCursor: string;
+ };
+ const response = await this.get(url, { num, cursor }, { sign: this.sign.bind(this) });
+ return response as Result;
+ }
+ async getComment(noteId: string, xsecToken?: string) {}
+ /**
+ *
+ * @uri /api/sns/web/v1/you/mentions
+ * @returns
+ */
+ async getMention(num = 10): Promise> {
+ const url = '/api/sns/web/v1/you/mentions';
+ const response = await this.get(
+ url,
+ { num },
+ {
+ sign: this.sign.bind(this),
+ // headers: {
+ // 'x-s':
+ // 'XYW_eyJzaWduU3ZuIjoiNTYiLCJzaWduVHlwZSI6IngyIiwiYXBwSWQiOiJsb2dpbiIsInNpZ25WZXJzaW9uIjoiMSIsInBheWxvYWQiOiJiNGJmMGI2MDVkZTlkOWMyY2RlNTI2YmVjNjM2ZmIxMjkxYzUxMTIyYWQyOTk5MzIyMzNjMmU0OTEzMWFmYzgzY2FmOGQzZDIzMTA0Y2RlNWUzZDZlZDczMDg0MmUzYzAxOTNkY2FjZjEyZjk1NTMzZGQzY2ZkMGFmOTg5MGZmMDIwNWI0MmQwOTNiYmJjMGNkZWU3MzdmOGE2MmRkYWVlYjZhMjcxZDViNjZkNGRjYjA1NDg2MGZhNTllN2M5MjE0ZDE2OTJjYWQyZjZmNzE1NThmYWQ3YjQxZjlhZTNiYjA1ZDExN2YzYWI2ZjRjYzY5MzcyMzRhOTY1OTkxYzMwMWY2YjI1MzY4MTZiNzM1YzhmMWEzOTk2ODhkMWU0NDFiODljYTNlNzQ3YWNlN2M2MGIzZDlhZWQwZDVlZDZlNGFhMDE5MmQ5YzZjNDE1M2IxM2RjODAwYjUzZTQxYWEzOTU4MjJhMzYyMmJjODEwYmY4MzA3MjkwMjY2ZDUzNmQwMjdkMTJlOWEwMzhlZmY1YWU4OTM5NDVlNDhmYmY2MCJ9',
+ // 'x-t': '1746097556685',
+ // },
+ },
+ );
+ return response;
+ }
+ async sign(uri: string, data: any, config: any) {
+ let headers = config?.headers || {};
+ const cookieDist = this.getCookieMap();
+ const web_session = cookieDist['web_session'];
+ const a1 = cookieDist['a1'];
+ const res = await getSign({ uri, data, a1, web_session }, this.signConfig);
+ if (res) {
+ headers['x-s'] = res?.['x-s'];
+ headers['x-t'] = res?.['x-t'];
+ config.headers = headers;
+ } else {
+ console.log('get sign error');
+ throw new Error('get sign error');
+ }
+ return config;
+ }
+ async getNoteById(noteId: string, xsecToken?: string) {
+ const data = {
+ source_note_id: noteId,
+ image_scenes: ['CRD_WM_WEBP'],
+ };
+ const uri = '/api/sns/web/v1/feed';
+
+ try {
+ const response = await this.post(uri, data, { sign: this.sign.bind(this) });
+ return response['items'][0]['node_card'];
+ } catch (error) {
+ console.log(error);
+ }
+ }
+ /**
+ * 发送评论,content最多为300个字符,如果多余300个字符,发送两次
+ * @param comment
+ * @returns
+ */
+ async postComment(comment: { note_id: string; comment_id: string; content: string }) {
+ const uri = '/api/sns/web/v1/comment/post';
+ try {
+ const data = {
+ note_id: comment.note_id,
+ content: comment.content,
+ target_comment_id: comment.comment_id,
+ at_users: [], //
+ };
+ type PostCommentResponse = {
+ comment: CommonentInfo;
+ time: number;
+ toast: string;
+ };
+ const response = await this.post(uri, data, { sign: this.sign.bind(this) });
+ return response as Result;
+ } catch (error) {
+ console.log(error);
+ }
+ }
+}
+
+type UnreadCount = {
+ unread_count: number;
+ likes: number;
+ connections: number;
+ mentions: number;
+};
diff --git a/packages/xhs/src/routes/fans/fans.ts b/packages/xhs/src/routes/fans/fans.ts
new file mode 100644
index 0000000..fdf51d9
--- /dev/null
+++ b/packages/xhs/src/routes/fans/fans.ts
@@ -0,0 +1,20 @@
+import { app, xhsServices } from '@/app.ts';
+import { Parse } from '@/libs/parse.ts';
+app
+ .route({
+ path: 'fans',
+ key: 'init-fans',
+ })
+ .define(async (ctx) => {
+ const client = xhsServices.getClient();
+ const res = await client.getFollowNotifications();
+ if (res.code === 0) {
+ const data = res.data;
+ const fans = data.message_list;
+ const fansList = Parse.getConnects(fans);
+ //
+ } else {
+ ctx.body = res;
+ }
+ })
+ .addTo(app);
diff --git a/packages/xhs/src/routes/index.ts b/packages/xhs/src/routes/index.ts
new file mode 100644
index 0000000..1a477c4
--- /dev/null
+++ b/packages/xhs/src/routes/index.ts
@@ -0,0 +1 @@
+import './mentions/index.ts'
\ No newline at end of file
diff --git a/packages/xhs/src/routes/mentions/index.ts b/packages/xhs/src/routes/mentions/index.ts
new file mode 100644
index 0000000..acdaa70
--- /dev/null
+++ b/packages/xhs/src/routes/mentions/index.ts
@@ -0,0 +1,2 @@
+import './mention.ts'
+import './unread-task.ts'
\ No newline at end of file
diff --git a/packages/xhs/src/routes/mentions/mention.ts b/packages/xhs/src/routes/mentions/mention.ts
new file mode 100644
index 0000000..3ebf846
--- /dev/null
+++ b/packages/xhs/src/routes/mentions/mention.ts
@@ -0,0 +1,89 @@
+import { app, xhsServices } from '@/app.ts';
+
+app
+ .route({
+ path: 'mention',
+ key: 'getUnread',
+ description: '获取提及列表',
+ })
+ .define(async (ctx) => {
+ //
+ const client = xhsServices.getClient();
+ const res = await client.getUnread();
+ if (res.code === 0) {
+ const unread_count = res.data.unread_count;
+ ctx.body = res.data;
+ } else {
+ ctx.body = {
+ unread_count: 0,
+ };
+ }
+ })
+ .addTo(app);
+app
+ .route({
+ path: 'mention',
+ key: 'getNote',
+ description: '获取笔记',
+ validator: {
+ node_id: {
+ type: 'string',
+ required: true,
+ },
+ xsec_token: {
+ type: 'string',
+ required: true,
+ },
+ },
+ isDebug: true,
+ })
+ .define(async (ctx) => {
+ const node_id = ctx.query.node_id;
+ const xsec_token = ctx.query.xsec_token;
+ const client = xhsServices.getClient();
+ try {
+ console.log('get note by node_id', node_id, 'xsec_token', xsec_token);
+ const res = await client.getNoteByIdFromHtml(node_id, xsec_token);
+ console.log('res====', res);
+ ctx.body = res;
+ } catch (error) {
+ console.log('res', error);
+ ctx.throw(500, '获取笔记失败');
+ }
+ })
+ .addTo(app);
+app
+ .route({
+ path: 'mention',
+ key: 'addComment',
+ })
+ .define(async (ctx) => {
+ const { node_id, comment_id, content } = ctx.query;
+ const client = xhsServices.getClient();
+ const res = await client.postComment({
+ note_id: node_id,
+ comment_id: comment_id,
+ content,
+ });
+ ctx.body = res.data;
+ })
+ .addTo(app);
+app
+ .route({
+ path: 'mention',
+ key: 'getMention',
+ description: '获取提及列表',
+ validator: {
+ num: {
+ type: 'number',
+ required: true,
+ },
+ },
+ })
+ .define(async (ctx) => {
+ const num = ctx.query.num;
+ const client = xhsServices.getClient();
+ const res = await client.getMention(num);
+ ctx.body = res.data;
+ })
+ .addTo(app);
diff --git a/packages/xhs/src/routes/mentions/unread-task.ts b/packages/xhs/src/routes/mentions/unread-task.ts
new file mode 100644
index 0000000..49b18f4
--- /dev/null
+++ b/packages/xhs/src/routes/mentions/unread-task.ts
@@ -0,0 +1,58 @@
+import { app, xhsServices } from '@/app.ts';
+import { Mention } from '@/libs/xhs-type/mention.ts';
+import { Parse } from '@/libs/parse.ts';
+import { client } from '@/test/common.ts';
+app
+ .route({
+ path: 'mention',
+ key: 'run-unread-task',
+ description: '运行未读任务',
+ })
+ .define(async (ctx) => {
+ const unredRes = await app.call({
+ path: 'mention',
+ key: 'getUnread',
+ });
+ console.log('unredRes', unredRes.body, unredRes.code);
+ if (unredRes.code === 200) {
+ const unread_count = unredRes.body.unread_count;
+ const mentionRes = await app.call({
+ path: 'mention',
+ key: 'getMention',
+ payload: {
+ num: 2,
+ },
+ });
+ if (mentionRes.code === 200) {
+ const mentionList = mentionRes.body.message_list as Mention[];
+ console.log('mentionList', mentionList);
+ for (const mention of mentionList) {
+ const node_id = mention.item_info.id;
+ const xsec_token = mention.item_info.xsec_token;
+ let comment: any = Parse.getComment(mention);
+ const user = Parse.getUser(mentionList[0]);
+ console.log('node_id', node_id, 'xsec_token', xsec_token, comment);
+ console.log('user', user);
+ const noteRes = await app.call({
+ path: 'mention',
+ key: 'getNote',
+ payload: {
+ node_id,
+ xsec_token,
+ },
+ });
+ const note = Parse.getNote(noteRes.body);
+ console.log('getNote', noteRes.body);
+ const note_id = note.node_id;
+ const comment_id = comment?.comment_id || '';
+ // const commentRes = await client.postComment({
+ // note_id: note_id,
+ // comment_id: comment_id,
+ // content: 'comment',
+ // });
+ // console.log('commentRes', commentRes);
+ }
+ }
+ }
+ })
+ .addTo(app);
diff --git a/packages/xhs/src/services/xhs-services.ts b/packages/xhs/src/services/xhs-services.ts
new file mode 100644
index 0000000..5a1560a
--- /dev/null
+++ b/packages/xhs/src/services/xhs-services.ts
@@ -0,0 +1,56 @@
+import { XhsClient } from '@/libs/xhs.ts';
+
+type XhsClientOptions = {
+ key: string;
+ cookie: string;
+ signConfig?: {
+ signUrl: string;
+ };
+ [key: string]: any;
+};
+type XhsClientMap = {
+ client: XhsClient;
+ key: string;
+ options: XhsClientOptions;
+};
+type XhsServicesOptions = {
+ root?: string;
+};
+export class XhsServices {
+ map: Map = new Map();
+ root: string = 'root';
+ constructor(opts?: XhsServicesOptions) {
+ this.root = opts?.root || this.root;
+ }
+ createClient(options: XhsClientOptions) {
+ const { key, cookie, signConfig } = options;
+ if (this.map.has(key)) {
+ return this.map.get(key);
+ }
+ const client = new XhsClient({ cookie });
+ client.signConfig = signConfig;
+ this.map.set(key, { client, key, options });
+ return client;
+ }
+ createRoot(options: Partial) {
+ options.key = options.key || this.root;
+ return this.createClient(options as XhsClientOptions);
+ }
+ getKey(key?: string) {
+ if (!key) key = this.root;
+ return key;
+ }
+ /**
+ * Get the XhsClient instance by key
+ * @param key
+ * @returns
+ */
+ getClient(key?: string) {
+ const xhsClient = this.map.get(this.getKey(key));
+ return xhsClient.client;
+ }
+ getXhsClient(key?: string) {
+ const xhsClient = this.map.get(this.getKey(key));
+ return xhsClient;
+ }
+}
diff --git a/packages/xhs/src/test/command.ts b/packages/xhs/src/test/command.ts
new file mode 100644
index 0000000..efbc896
--- /dev/null
+++ b/packages/xhs/src/test/command.ts
@@ -0,0 +1,14 @@
+import { program } from 'commander';
+import './query/get-components.ts';
+import './query/get-note.ts';
+import './query/mention.ts'
+import './query/unread.ts';
+import './query/get-userinfo.ts';
+import './query/get-connections.ts'
+program
+ .command('test')
+ .description('test command')
+ .action(() => {
+ console.log('test command');
+ });
+program.parse(process.argv);
diff --git a/packages/xhs/src/test/common.ts b/packages/xhs/src/test/common.ts
index 67a5e52..6968656 100644
--- a/packages/xhs/src/test/common.ts
+++ b/packages/xhs/src/test/common.ts
@@ -1,4 +1,8 @@
-import { XhsClient } from '../index.ts';
-export const cookie = "a1=19686f83a65uiloc0y7wv79une457hsjh5lt00dpe40000147626;abRequestId=0a794332-4561-5f49-93f7-780b8b028e1f;access-token-creator.xiaohongshu.com=customer.creator.AT-68c517498636784561614544frjvxzj7yu8iewie;agora_session=6a0031373435393132333637323735343733393437313634000000000000;customerClientId=536706778174172;galaxy_creator_session_id=OhpHDDSoADhNEhnH5LLnQpletFLApu1fd91f;galaxy.creator.beaker.session.id=1745912429847011598150;gid=yjKqYfK0qDyYyj2DDSqd4ujxyW9kvxIuT62ddkMWhElyuxq8yDd6hl888q2WYy88j8i80yYD;loadts=1746020512562;sec_poison_id=441c932e-a6ac-4d8d-97ae-beb14adb1929;unread={%22ub%22:%2267eaf1fe000000001202c3ea%22%2C%22ue%22:%226803aa37000000001c0319d8%22%2C%22uc%22:35};web_session=040069b2e9c511ca302086ca253a4bde8b1cd1;webBuild=4.62.3;webId=97e5f097499594cad49aa0bd1a8ed83f;websectiga=3633fe24d49c7dd0eb923edc8205740f10fdb18b25d424d2a2322c6196d2a4ad;x-user-id-creator.xiaohongshu.com=639d86590000000026006076;xsecappid=xhs-pc-web;acw_tc=0a00df6217460205042195762e721fba339a0dbe8e4738b961a5ff15e74619;"
+import { XhsClient, xhsServices } from '../index.ts';
+import { program } from 'commander';
+export const cookie =
+ 'a1=1968ba02ff4xrt6hfrzdiz7ubs82j9y3vx11vfw9c40000317680;abRequestId=0a794332-4561-5f49-93f7-780b8b028e1f;access-token-creator.xiaohongshu.com=customer.creator.AT-68c517498636784561614544frjvxzj7yu8iewie;agora_session=6a0031373435393132333637323735343733393437313634000000000000;customerClientId=536706778174172;galaxy_creator_session_id=OhpHDDSoADhNEhnH5LLnQpletFLApu1fd91f;galaxy.creator.beaker.session.id=1745912429847011598150;gid=yjKqYfK0qDyYyj2DDSqd4ujxyW9kvxIuT62ddkMWhElyuxq8yDd6hl888q2WYy88j8i80yYD;loadts=1746020512562;sec_poison_id=441c932e-a6ac-4d8d-97ae-beb14adb1929;unread={%22ub%22:%2267eaf1fe000000001202c3ea%22%2C%22ue%22:%226803aa37000000001c0319d8%22%2C%22uc%22:35};web_session=040069b2e9c511ca302086ca253a4bde8b1cd1;webBuild=4.62.3;webId=97e5f097499594cad49aa0bd1a8ed83f;websectiga=3633fe24d49c7dd0eb923edc8205740f10fdb18b25d424d2a2322c6196d2a4ad;x-user-id-creator.xiaohongshu.com=639d86590000000026006076;xsecappid=xhs-pc-web;acw_tc=0a00df6217460205042195762e721fba339a0dbe8e4738b961a5ff15e74619;';
export const client = new XhsClient({ cookie } as any);
+
+export { program, xhsServices };
diff --git a/packages/xhs/src/test/query/get-components.ts b/packages/xhs/src/test/query/get-components.ts
new file mode 100644
index 0000000..7e45d2b
--- /dev/null
+++ b/packages/xhs/src/test/query/get-components.ts
@@ -0,0 +1,14 @@
+import { client } from '../common.ts';
+import { program } from 'commander';
+
+program
+ .command('get-commonents')
+ .description('get commonents')
+ .action(async () => {
+ const res = await client.getNoteComments('6810d722000000002100f139', '', {
+ //
+ xsec_token: 'LBEqTFigLzp41AdwQ-E3hbQScnvrx2flLgHElHpQ8zHWc=',
+ top_comment_id: '6810e7d80000000012020c7a'
+ });
+ console.log(res);
+ });
diff --git a/packages/xhs/src/test/query/get-connections.ts b/packages/xhs/src/test/query/get-connections.ts
new file mode 100644
index 0000000..aecd49c
--- /dev/null
+++ b/packages/xhs/src/test/query/get-connections.ts
@@ -0,0 +1,14 @@
+import { xhsServices, program } from '../common.ts';
+import util from 'node:util';
+const getConnections = async () => {
+ const client = xhsServices.getClient();
+ const res = await client.getFollowNotifications(10, '');
+ console.log(util.inspect(res, { depth: null, colors: true }));
+};
+
+program
+ .command('get-connections')
+ .description('获取关注通知')
+ .action(async () => {
+ await getConnections();
+ });
diff --git a/packages/xhs/src/test/query/get-note.ts b/packages/xhs/src/test/query/get-note.ts
new file mode 100644
index 0000000..dc249b0
--- /dev/null
+++ b/packages/xhs/src/test/query/get-note.ts
@@ -0,0 +1,33 @@
+import { client } from '../common.ts';
+import { program } from 'commander';
+
+// client.getNoteComments()
+
+// client.getNoteByIdFromHtml('67dcc34e000000000602a8eb', 'ABuYS8Xb1o08DlRmMLIabdqnW0OKnLR9nMpDGq5bVRdvk').then((res) => {
+// console.log(res);
+// });
+const getNoteById = async () => {
+ client.getNoteById('67dcc34e000000000602a8eb', 'ABuYS8Xb1o08DlRmMLIabdqnW0OKnLR9nMpDGq5bVRdvk').then((res) => {
+ console.log(res);
+ });
+};
+const getNote = async () => {
+ const id = '6810d722000000002100f139';
+ const x = 'LBEqTFigLzp41AdwQ-E3hbQScnvrx2flLgHElHpQ8zHWc=';
+ client.getNoteByIdFromHtml(id, x).then((res) => {
+ console.log(res);
+ });
+};
+program
+ .command('get-note')
+ .description('get note')
+ .action(async () => {
+ getNote();
+ });
+
+program
+ .command('get-note-by-id')
+ .description('get note by id')
+ .action(async () => {
+ getNoteById();
+ });
\ No newline at end of file
diff --git a/packages/xhs/src/test/query/get-sign.ts b/packages/xhs/src/test/query/get-sign.ts
new file mode 100644
index 0000000..14dfd7a
--- /dev/null
+++ b/packages/xhs/src/test/query/get-sign.ts
@@ -0,0 +1,12 @@
+import { client, program } from '../common.ts';
+
+const getSign = async () => {
+ const uri = '/api/sns/web/v1/you/mentions';
+ const data = null;
+ const config = { headers: {} };
+ const res = await client.sign(uri, data, config);
+ console.log('getSign', res);
+};
+// getSign();
+
+program.command('sign').description('get sign').action(getSign);
diff --git a/packages/xhs/src/test/query/get-userinfo.ts b/packages/xhs/src/test/query/get-userinfo.ts
new file mode 100644
index 0000000..f214bd3
--- /dev/null
+++ b/packages/xhs/src/test/query/get-userinfo.ts
@@ -0,0 +1,21 @@
+import { client, program } from '../common.ts';
+
+program
+ .command('get-userinfo')
+ .description('获取用户信息')
+ .action(async () => {
+ const res = await client.getSelfInfoV2();
+ console.log(res);
+ });
+
+const getUserById = async (userId: string) => {
+ const res = await client.getUserInfoFromHtml(userId);
+ console.log(res);
+};
+
+program
+ .command('get-userinfo-by-id ')
+ .description('获取用户信息')
+ .action(async (userId: string) => {
+ await getUserById(userId);
+ });
diff --git a/packages/xhs/src/test/query/get_note.ts b/packages/xhs/src/test/query/get_note.ts
deleted file mode 100644
index 11ba5cc..0000000
--- a/packages/xhs/src/test/query/get_note.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { client } from '../common.ts';
-
-// client.getNoteComments()
-
-// client.getNoteById('67dcc34e000000000602a8eb', 'ABuYS8Xb1o08DlRmMLIabdqnW0OKnLR9nMpDGq5bVRdvk').then((res) => {
-// console.log(res);
-// });
-// client.getNoteByIdFromHtml('67dcc34e000000000602a8eb', 'ABuYS8Xb1o08DlRmMLIabdqnW0OKnLR9nMpDGq5bVRdvk').then((res) => {
-// console.log(res);
-// });
-const id = '6810d722000000002100f139';
-const x = 'LBEqTFigLzp41AdwQ-E3hbQScnvrx2flLgHElHpQ8zHWc=';
-client.getNoteByIdFromHtml(id, x).then((res) => {
- console.log(res);
-});
diff --git a/packages/xhs/src/test/query/get_userinfo.ts b/packages/xhs/src/test/query/get_userinfo.ts
deleted file mode 100644
index 3af5f49..0000000
--- a/packages/xhs/src/test/query/get_userinfo.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import { client } from '../common.ts';
-
-client.getSelfInfoV2().then((res) => {
- console.log(res);
-});
diff --git a/packages/xhs/src/test/query/mention.ts b/packages/xhs/src/test/query/mention.ts
new file mode 100644
index 0000000..5c5a59b
--- /dev/null
+++ b/packages/xhs/src/test/query/mention.ts
@@ -0,0 +1,26 @@
+import { client, program } from '../common.ts';
+import util from 'node:util';
+const getMentions = async () => {
+ try {
+ const res = await client.getMention();
+ if (res.code === 0) {
+ console.log('getMentionNotifications', util.inspect(res, { depth: 10 }));
+ }
+ } catch (e) {
+ console.error('error');
+ }
+};
+
+program.command('mention').description('get mention notifications').action(getMentions);
+
+const getTestMentionNote = async () => {
+ const id = '68136dab0000000007034c46';
+ const xsec_token = 'LByEmonX8WfJ9ebpAowVbOZcNgtJAAC8K2zx5Rmr_9Q7Y=';
+ const title = '在笔记中@了你';
+ const type = 'mention/item';
+ const track_type = '2';
+ const note = await client.getNoteByIdFromHtml(id, xsec_token);
+ console.log('note', note);
+};
+
+program.command('test-mention').description('get mention note').action(getTestMentionNote);
diff --git a/packages/xhs/src/test/query/unread.ts b/packages/xhs/src/test/query/unread.ts
index bf9c1bd..be94ab6 100644
--- a/packages/xhs/src/test/query/unread.ts
+++ b/packages/xhs/src/test/query/unread.ts
@@ -1,5 +1,9 @@
-import { client } from '../common.ts';
+import { client, program } from '../common.ts';
-client.getUnread().then((res) => {
- console.log(res);
-});
+const getUnread = () => {
+ client.getUnread().then((res) => {
+ console.log(res);
+ });
+};
+
+program.command('unread').description('获取未读消息').action(getUnread);
diff --git a/packages/xhs/src/test/run/mention.ts b/packages/xhs/src/test/run/mention.ts
new file mode 100644
index 0000000..63b1bf7
--- /dev/null
+++ b/packages/xhs/src/test/run/mention.ts
@@ -0,0 +1,6 @@
+import { app } from '@/index.ts';
+
+app.parse({
+ path: 'mention',
+ key: 'run-unread-task',
+});
diff --git a/packages/xhs/tsconfig.json b/packages/xhs/tsconfig.json
new file mode 100644
index 0000000..1bdfd4e
--- /dev/null
+++ b/packages/xhs/tsconfig.json
@@ -0,0 +1,15 @@
+{
+ "extends": "@kevisual/types/json/backend.json",
+ "compilerOptions": {
+ "baseUrl": "./",
+ "paths": {
+ "@/*": [
+ "src/*"
+ ]
+ }
+ },
+ "include": [
+ "src/**/*.ts",
+ ],
+ "exclude": [],
+}
\ No newline at end of file
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 9af9130..d4d645e 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -112,6 +112,9 @@ importers:
'@kevisual/xhs-core':
specifier: workspace:*
version: link:../xhs-core
+ '@types/node':
+ specifier: ^22.15.3
+ version: 22.15.3
packages/xhs-core:
devDependencies:
@@ -292,67 +295,56 @@ packages:
resolution: {integrity: sha512-ehSKrewwsESPt1TgSE/na9nIhWCosfGSFqv7vwEtjyAqZcvbGIg4JAcV7ZEh2tfj/IlfBeZjgOXm35iOOjadcg==}
cpu: [arm]
os: [linux]
- libc: [glibc]
'@rollup/rollup-linux-arm-musleabihf@4.40.1':
resolution: {integrity: sha512-m39iO/aaurh5FVIu/F4/Zsl8xppd76S4qoID8E+dSRQvTyZTOI2gVk3T4oqzfq1PtcvOfAVlwLMK3KRQMaR8lg==}
cpu: [arm]
os: [linux]
- libc: [musl]
'@rollup/rollup-linux-arm64-gnu@4.40.1':
resolution: {integrity: sha512-Y+GHnGaku4aVLSgrT0uWe2o2Rq8te9hi+MwqGF9r9ORgXhmHK5Q71N757u0F8yU1OIwUIFy6YiJtKjtyktk5hg==}
cpu: [arm64]
os: [linux]
- libc: [glibc]
'@rollup/rollup-linux-arm64-musl@4.40.1':
resolution: {integrity: sha512-jEwjn3jCA+tQGswK3aEWcD09/7M5wGwc6+flhva7dsQNRZZTe30vkalgIzV4tjkopsTS9Jd7Y1Bsj6a4lzz8gQ==}
cpu: [arm64]
os: [linux]
- libc: [musl]
'@rollup/rollup-linux-loongarch64-gnu@4.40.1':
resolution: {integrity: sha512-ySyWikVhNzv+BV/IDCsrraOAZ3UaC8SZB67FZlqVwXwnFhPihOso9rPOxzZbjp81suB1O2Topw+6Ug3JNegejQ==}
cpu: [loong64]
os: [linux]
- libc: [glibc]
'@rollup/rollup-linux-powerpc64le-gnu@4.40.1':
resolution: {integrity: sha512-BvvA64QxZlh7WZWqDPPdt0GH4bznuL6uOO1pmgPnnv86rpUpc8ZxgZwcEgXvo02GRIZX1hQ0j0pAnhwkhwPqWg==}
cpu: [ppc64]
os: [linux]
- libc: [glibc]
'@rollup/rollup-linux-riscv64-gnu@4.40.1':
resolution: {integrity: sha512-EQSP+8+1VuSulm9RKSMKitTav89fKbHymTf25n5+Yr6gAPZxYWpj3DzAsQqoaHAk9YX2lwEyAf9S4W8F4l3VBQ==}
cpu: [riscv64]
os: [linux]
- libc: [glibc]
'@rollup/rollup-linux-riscv64-musl@4.40.1':
resolution: {integrity: sha512-n/vQ4xRZXKuIpqukkMXZt9RWdl+2zgGNx7Uda8NtmLJ06NL8jiHxUawbwC+hdSq1rrw/9CghCpEONor+l1e2gA==}
cpu: [riscv64]
os: [linux]
- libc: [musl]
'@rollup/rollup-linux-s390x-gnu@4.40.1':
resolution: {integrity: sha512-h8d28xzYb98fMQKUz0w2fMc1XuGzLLjdyxVIbhbil4ELfk5/orZlSTpF/xdI9C8K0I8lCkq+1En2RJsawZekkg==}
cpu: [s390x]
os: [linux]
- libc: [glibc]
'@rollup/rollup-linux-x64-gnu@4.40.1':
resolution: {integrity: sha512-XiK5z70PEFEFqcNj3/zRSz/qX4bp4QIraTy9QjwJAb/Z8GM7kVUsD0Uk8maIPeTyPCP03ChdI+VVmJriKYbRHQ==}
cpu: [x64]
os: [linux]
- libc: [glibc]
'@rollup/rollup-linux-x64-musl@4.40.1':
resolution: {integrity: sha512-2BRORitq5rQ4Da9blVovzNCMaUlyKrzMSvkVR0D4qPuOy/+pMCrh1d7o01RATwVy+6Fa1WBw+da7QPeLWU/1mQ==}
cpu: [x64]
os: [linux]
- libc: [musl]
'@rollup/rollup-win32-arm64-msvc@4.40.1':
resolution: {integrity: sha512-b2bcNm9Kbde03H+q+Jjw9tSfhYkzrDUf2d5MAd1bOJuVplXvFhWz7tRtWvD8/ORZi7qSCy0idW6tf2HgxSXQSg==}
diff --git a/src/task/test/worker.ts b/src/task/test/worker.ts
new file mode 100644
index 0000000..f1ebbc6
--- /dev/null
+++ b/src/task/test/worker.ts
@@ -0,0 +1,55 @@
+import { redis } from '@/modules/redis.ts';
+import { Queue, Worker } from 'bullmq';
+import { clamp } from 'lodash-es';
+import { nanoid } from 'nanoid';
+
+export const queue = new Queue('TEST_QUEUE');
+export const sleep = (ms: number) => {
+ return new Promise((resolve) => setTimeout(resolve, ms));
+};
+export const worker = new Worker(
+ 'TEST_QUEUE',
+ async (job) => {
+ const startTime = Date.now();
+ console.log('job', job.name, job.data);
+ await sleep(1000);
+ const endTime = Date.now();
+ const duration = endTime - startTime;
+ job.updateProgress(50);
+ const isRetry = job.attemptsMade > 0;
+ console.log('job attemptsMade', job.attemptsMade, 'isRetry', isRetry);
+ if (job.data.index === 1 && !isRetry) {
+ // set fail
+ throw new Error('test error');
+ }
+ return {
+ startTime: startTime,
+ endTime: endTime,
+ duration: duration,
+ };
+ },
+ {
+ connection: redis,
+ limiter: {
+ max: 1,
+ duration: 10000,
+ },
+ },
+);
+worker.on('completed', async (job) => {
+ console.log('job completed', job.name, job.id, job.returnvalue);
+});
+worker.on('failed', async (job) => {
+ console.log('job failed', job.name, job.id, job.failedReason, await job.getState());
+});
+
+queue.pause();
+await queue.drain();
+const arr = Array.from({ length: 2 }, (_, i) => i + 1);
+for (const i of arr) {
+ queue.add('job' + i, { name: 'test' + i, index: i }, { jobId: 'job' + i, removeOnFail: true, removeOnComplete: true, attempts: 3 });
+}
+const job = await queue.getJob('job1');
+console.log('job', job);
+await sleep(4000)
+queue.resume();