generated from tailored/router-template
temp
This commit is contained in:
45
packages/xhs-core/src/exception.js
Normal file
45
packages/xhs-core/src/exception.js
Normal file
@@ -0,0 +1,45 @@
|
||||
// ErrorTuple and ErrorEnum
|
||||
const ErrorEnum = {
|
||||
IP_BLOCK: { code: 300012, msg: '网络连接异常,请检查网络设置或重启试试' },
|
||||
NOTE_ABNORMAL: { code: -510001, msg: '笔记状态异常,请稍后查看' },
|
||||
NOTE_SECRETE_FAULT: { code: -510001, msg: '当前内容无法展示' },
|
||||
SIGN_FAULT: { code: 300015, msg: '浏览器异常,请尝试关闭/卸载风险插件或重启试试!' },
|
||||
SESSION_EXPIRED: { code: -100, msg: '登录已过期' },
|
||||
};
|
||||
|
||||
// Custom error classes
|
||||
class DataFetchError extends Error {
|
||||
constructor(message) {
|
||||
super(message);
|
||||
this.message = message;
|
||||
this.name = 'DataFetchError';
|
||||
}
|
||||
}
|
||||
|
||||
class IPBlockError extends Error {
|
||||
constructor(message) {
|
||||
super(message);
|
||||
this.message = message;
|
||||
this.name = 'IPBlockError';
|
||||
}
|
||||
}
|
||||
|
||||
class SignError extends Error {
|
||||
constructor(message) {
|
||||
super(message);
|
||||
this.message = message;
|
||||
this.name = 'SignError';
|
||||
}
|
||||
}
|
||||
|
||||
class NeedVerifyError extends Error {
|
||||
constructor(message, verifyType = null, verifyUuid = null) {
|
||||
super(message);
|
||||
this.message = message;
|
||||
this.name = 'NeedVerifyError';
|
||||
this.verifyType = verifyType;
|
||||
this.verifyUuid = verifyUuid;
|
||||
}
|
||||
}
|
||||
|
||||
export { ErrorEnum, DataFetchError, IPBlockError, SignError, NeedVerifyError };
|
||||
259
packages/xhs-core/src/helper.js
Normal file
259
packages/xhs-core/src/helper.js
Normal file
@@ -0,0 +1,259 @@
|
||||
const lookup = [
|
||||
"Z",
|
||||
"m",
|
||||
"s",
|
||||
"e",
|
||||
"r",
|
||||
"b",
|
||||
"B",
|
||||
"o",
|
||||
"H",
|
||||
"Q",
|
||||
"t",
|
||||
"N",
|
||||
"P",
|
||||
"+",
|
||||
"w",
|
||||
"O",
|
||||
"c",
|
||||
"z",
|
||||
"a",
|
||||
"/",
|
||||
"L",
|
||||
"p",
|
||||
"n",
|
||||
"g",
|
||||
"G",
|
||||
"8",
|
||||
"y",
|
||||
"J",
|
||||
"q",
|
||||
"4",
|
||||
"2",
|
||||
"K",
|
||||
"W",
|
||||
"Y",
|
||||
"j",
|
||||
"0",
|
||||
"D",
|
||||
"S",
|
||||
"f",
|
||||
"d",
|
||||
"i",
|
||||
"k",
|
||||
"x",
|
||||
"3",
|
||||
"V",
|
||||
"T",
|
||||
"1",
|
||||
"6",
|
||||
"I",
|
||||
"l",
|
||||
"U",
|
||||
"A",
|
||||
"F",
|
||||
"M",
|
||||
"9",
|
||||
"7",
|
||||
"h",
|
||||
"E",
|
||||
"C",
|
||||
"v",
|
||||
"u",
|
||||
"R",
|
||||
"X",
|
||||
"5",
|
||||
]
|
||||
|
||||
function encodeUtf8(e) {
|
||||
const b = [];
|
||||
const m = encodeURIComponent(e).replace(/[!'()*]/g, c => `%${c.charCodeAt(0).toString(16).toUpperCase()}`);
|
||||
let w = 0;
|
||||
|
||||
while (w < m.length) {
|
||||
const T = m[w];
|
||||
if (T === "%") {
|
||||
const E = m.slice(w + 1, w + 3);
|
||||
const S = parseInt(E, 16);
|
||||
b.push(S);
|
||||
w += 2;
|
||||
} else {
|
||||
b.push(T.charCodeAt(0));
|
||||
}
|
||||
w += 1;
|
||||
}
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
function mrc(e) {
|
||||
const ie = [
|
||||
0, 1996959894, 3993919788, 2567524794, 124634137, 1886057615, 3915621685,
|
||||
2657392035, 249268274, 2044508324, 3772115230, 2547177864, 162941995,
|
||||
2125561021, 3887607047, 2428444049, 498536548, 1789927666, 4089016648,
|
||||
2227061214, 450548861, 1843258603, 4107580753, 2211677639, 325883990,
|
||||
1684777152, 4251122042, 2321926636, 335633487, 1661365465, 4195302755,
|
||||
2366115317, 997073096, 1281953886, 3579855332, 2724688242, 1006888145,
|
||||
1258607687, 3524101629, 2768942443, 901097722, 1119000684, 3686517206,
|
||||
2898065728, 853044451, 1172266101, 3705015759, 2882616665, 651767980,
|
||||
1373503546, 3369554304, 3218104598, 565507253, 1454621731, 3485111705,
|
||||
3099436303, 671266974, 1594198024, 3322730930, 2970347812, 795835527,
|
||||
1483230225, 3244367275, 3060149565, 1994146192, 31158534, 2563907772,
|
||||
4023717930, 1907459465, 112637215, 2680153253, 3904427059, 2013776290,
|
||||
251722036, 2517215374, 3775830040, 2137656763, 141376813, 2439277719,
|
||||
3865271297, 1802195444, 476864866, 2238001368, 4066508878, 1812370925,
|
||||
453092731, 2181625025, 4111451223, 1706088902, 314042704, 2344532202,
|
||||
4240017532, 1658658271, 366619977, 2362670323, 4224994405, 1303535960,
|
||||
984961486, 2747007092, 3569037538, 1256170817, 1037604311, 2765210733,
|
||||
3554079995, 1131014506, 879679996, 2909243462, 3663771856, 1141124467,
|
||||
855842277, 2852801631, 3708648649, 1342533948, 654459306, 3188396048,
|
||||
3373015174, 1466479909, 544179635, 3110523913, 3462522015, 1591671054,
|
||||
702138776, 2966460450, 3352799412, 1504918807, 783551873, 3082640443,
|
||||
3233442989, 3988292384, 2596254646, 62317068, 1957810842, 3939845945,
|
||||
2647816111, 81470997, 1943803523, 3814918930, 2489596804, 225274430,
|
||||
2053790376, 3826175755, 2466906013, 167816743, 2097651377, 4027552580,
|
||||
2265490386, 503444072, 1762050814, 4150417245, 2154129355, 426522225,
|
||||
1852507879, 4275313526, 2312317920, 282753626, 1742555852, 4189708143,
|
||||
2394877945, 397917763, 1622183637, 3604390888, 2714866558, 953729732,
|
||||
1340076626, 3518719985, 2797360999, 1068828381, 1219638859, 3624741850,
|
||||
2936675148, 906185462, 1090812512, 3747672003, 2825379669, 829329135,
|
||||
1181335161, 3412177804, 3160834842, 628085408, 1382605366, 3423369109,
|
||||
3138078467, 570562233, 1426400815, 3317316542, 2998733608, 733239954,
|
||||
1555261956, 3268935591, 3050360625, 752459403, 1541320221, 2607071920,
|
||||
3965973030, 1969922972, 40735498, 2617837225, 3943577151, 1913087877,
|
||||
83908371, 2512341634, 3803740692, 2075208622, 213261112, 2463272603,
|
||||
3855990285, 2094854071, 198958881, 2262029012, 4057260610, 1759359992,
|
||||
534414190, 2176718541, 4139329115, 1873836001, 414664567, 2282248934,
|
||||
4279200368, 1711684554, 285281116, 2405801727, 4167216745, 1634467795,
|
||||
376229701, 2685067896, 3608007406, 1308918612, 956543938, 2808555105,
|
||||
3495958263, 1231636301, 1047427035, 2932959818, 3654703836, 1088359270,
|
||||
936918000, 2847714899, 3736837829, 1202900863, 817233897, 3183342108,
|
||||
3401237130, 1404277552, 615818150, 3134207493, 3453421203, 1423857449,
|
||||
601450431, 3009837614, 3294710456, 1567103746, 711928724, 3020668471,
|
||||
3272380065, 1510334235, 755167117,
|
||||
]
|
||||
let o = -1
|
||||
|
||||
function rightWithoutSign(num, bit = 0) {
|
||||
return (num >>> bit) | 0;
|
||||
}
|
||||
|
||||
for (let n = 0; n < 57; n++) {
|
||||
o = ie[(o & 255) ^ e.charCodeAt(n)] ^ rightWithoutSign(o, 8);
|
||||
}
|
||||
return o ^ -1 ^ 3988292384;
|
||||
}
|
||||
|
||||
function encodeChunk(e, t, r) {
|
||||
const m = [];
|
||||
for (let b = t; b < r; b += 3) {
|
||||
const n = (16711680 & (e[b] << 16)) +
|
||||
((e[b + 1] << 8) & 65280) +
|
||||
(e[b + 2] & 255);
|
||||
m.push(tripletToBase64(n));
|
||||
}
|
||||
return m.join('');
|
||||
}
|
||||
|
||||
function tripletToBase64(e) {
|
||||
return (
|
||||
lookup[63 & (e >> 18)] +
|
||||
lookup[63 & (e >> 12)] +
|
||||
lookup[(e >> 6) & 63] +
|
||||
lookup[e & 63]
|
||||
);
|
||||
}
|
||||
function b64Encode(e) {
|
||||
const P = e.length;
|
||||
const W = P % 3;
|
||||
const U = [];
|
||||
const z = 16383;
|
||||
let H = 0;
|
||||
const Z = P - W;
|
||||
|
||||
while (H < Z) {
|
||||
U.push(encodeChunk(e, H, Math.min(H + z, Z)));
|
||||
H += z;
|
||||
}
|
||||
|
||||
if (W === 1) {
|
||||
const F = e[P - 1];
|
||||
U.push(lookup[F >> 2] + lookup[(F << 4) & 63] + "==");
|
||||
} else if (W === 2) {
|
||||
const F = (e[P - 2] << 8) + e[P - 1];
|
||||
U.push(lookup[F >> 10] +
|
||||
lookup[63 & (F >> 4)] +
|
||||
lookup[(F << 2) & 63] +
|
||||
"=");
|
||||
}
|
||||
|
||||
return U.join("");
|
||||
}
|
||||
|
||||
|
||||
export {
|
||||
getXCommon,
|
||||
encodeUtf8,
|
||||
mrc,
|
||||
encodeChunk
|
||||
};
|
||||
|
||||
function getXCommon(a1="", b1 = "", xS = undefined, xT = undefined) {
|
||||
const common = {
|
||||
s0: 5, // getPlatformCode
|
||||
s1: "",
|
||||
x0: "1", // localStorage.getItem("b1b1")
|
||||
x1: "3.7.8-2", // version
|
||||
x2: "Windows",
|
||||
x3: "xhs-pc-web",
|
||||
x4: "4.27.7",
|
||||
x5: a1, // cookie of a1
|
||||
x6: xT,
|
||||
x7: xS,
|
||||
x8: b1, // localStorage.getItem("b1")
|
||||
x9: mrc(xT + xS + b1),
|
||||
x10: 1, // getSigCount
|
||||
};
|
||||
const encodeStr = encodeUtf8(JSON.stringify(common));
|
||||
const x_s_common = b64Encode(encodeStr);
|
||||
return x_s_common;
|
||||
}
|
||||
|
||||
function getSearchId() {
|
||||
const e = BigInt(Date.now()) << 64n;
|
||||
const t = Math.floor(Math.random() * 2147483647);
|
||||
return base36encode(e + BigInt(t));
|
||||
}
|
||||
|
||||
function base36encode(num) {
|
||||
return num.toString(36).toUpperCase();
|
||||
}
|
||||
const SearchSortType = Object.freeze({
|
||||
// default
|
||||
GENERAL: { value: "general" },
|
||||
// most popular
|
||||
MOST_POPULAR: { value: "popularity_descending" },
|
||||
// Latest
|
||||
LATEST: { value: "time_descending" }
|
||||
});
|
||||
|
||||
const SearchNoteType = Object.freeze({
|
||||
// default
|
||||
ALL: { value: 0 },
|
||||
// only video
|
||||
VIDEO: { value: 1 },
|
||||
// only image
|
||||
IMAGE: { value: 2 }
|
||||
});
|
||||
|
||||
|
||||
|
||||
export {
|
||||
// getXCommon,
|
||||
// encodeUtf8,
|
||||
// mrc,
|
||||
// encodeChunk,
|
||||
getSearchId,
|
||||
SearchSortType,
|
||||
SearchNoteType,
|
||||
}
|
||||
425
packages/xhs-core/src/index.js
Normal file
425
packages/xhs-core/src/index.js
Normal file
@@ -0,0 +1,425 @@
|
||||
import axios from 'axios';
|
||||
import qs from 'querystring';
|
||||
import { get_xs } from './jsvmp/xhs';
|
||||
import fs from 'fs';
|
||||
|
||||
import { getXCommon, getSearchId, SearchSortType, SearchNoteType } from './helper.js';
|
||||
import { ErrorEnum, DataFetchError, IPBlockError, SignError, NeedVerifyError } from './exception';
|
||||
|
||||
const camelToUnderscore = (key) => {
|
||||
return key.replace(/([A-Z])/g, '_$1').toLowerCase();
|
||||
};
|
||||
|
||||
const transformJsonKeys = (jsonData) => {
|
||||
const dataDict = typeof jsonData === 'string' ? JSON.parse(jsonData) : jsonData;
|
||||
const dictNew = {};
|
||||
for (const [key, value] of Object.entries(dataDict)) {
|
||||
const newKey = camelToUnderscore(key);
|
||||
if (!value) {
|
||||
dictNew[newKey] = value;
|
||||
} else if (typeof value === 'object' && !Array.isArray(value)) {
|
||||
dictNew[newKey] = transformJsonKeys(value);
|
||||
} else if (Array.isArray(value)) {
|
||||
dictNew[newKey] = value.map((item) => (item && typeof item === 'object' ? transformJsonKeys(item) : item));
|
||||
} else {
|
||||
dictNew[newKey] = value;
|
||||
}
|
||||
}
|
||||
return dictNew;
|
||||
};
|
||||
|
||||
class XhsClient {
|
||||
/**
|
||||
* Constructor for XhsClient
|
||||
* @param {Object} options - Configuration options
|
||||
* @param {string} options.cookie - Cookie string for authentication
|
||||
* @param {string} options.userAgent - User agent string for requests
|
||||
* @param {number} options.timeout - Request timeout in milliseconds
|
||||
* @param {string} options.proxies - Proxy settings
|
||||
*/
|
||||
constructor({ cookie = null, userAgent = null, timeout = 10000, proxies = null } = {}) {
|
||||
this.proxies = proxies;
|
||||
this.timeout = timeout;
|
||||
this._host = 'https://edith.xiaohongshu.com';
|
||||
this._creatorHost = 'https://creator.xiaohongshu.com';
|
||||
this._customerHost = 'https://customer.xiaohongshu.com';
|
||||
this.home = 'https://www.xiaohongshu.com';
|
||||
this.userAgent = userAgent || 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36';
|
||||
|
||||
this.axiosInstance = axios.create({
|
||||
timeout: this.timeout,
|
||||
headers: {
|
||||
'user-agent': this.userAgent,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (cookie) {
|
||||
this.cookie = cookie;
|
||||
}
|
||||
}
|
||||
|
||||
// Getter for cookie
|
||||
get cookie() {
|
||||
return this.axiosInstance.defaults.headers.Cookie;
|
||||
}
|
||||
|
||||
// Setter for cookie
|
||||
set cookie(cookie) {
|
||||
this.axiosInstance.defaults.headers.Cookie = cookie;
|
||||
}
|
||||
|
||||
// Getter for cookieDict
|
||||
get cookieDict() {
|
||||
const cookieStr = this.axiosInstance.defaults.headers.Cookie;
|
||||
return cookieStr ? qs.parse(cookieStr.replace(/; /g, '&')) : {};
|
||||
}
|
||||
|
||||
_preHeaders(url, data = null) {
|
||||
let a1 = this.cookieDict.a1;
|
||||
let b1 = '';
|
||||
let x_s_result = get_xs(url, data, this.cookie);
|
||||
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,
|
||||
},
|
||||
};
|
||||
}
|
||||
getCookieMap() {
|
||||
const cookie = this.cookie;
|
||||
let cookieDict = {};
|
||||
if (cookie) {
|
||||
const cookieArray = cookie.split(';');
|
||||
cookieArray.forEach((item) => {
|
||||
const [key, value] = item.split('=');
|
||||
const trimKey = key.trim();
|
||||
if (trimKey) {
|
||||
const _value = value ? value.trim() : '';
|
||||
cookieDict[trimKey] = _value;
|
||||
}
|
||||
});
|
||||
return cookieDict;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
/**
|
||||
* Get X-S and X-T
|
||||
* @param {*} url
|
||||
* @param {*} data
|
||||
* @param {*} cookie
|
||||
* @returns
|
||||
*/
|
||||
get_xs(url, data, cookie) {
|
||||
return get_xs(url, data, cookie);
|
||||
}
|
||||
|
||||
async request(method, url, config = {}) {
|
||||
try {
|
||||
const response = await this.axiosInstance({ method, url, ...config });
|
||||
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'];
|
||||
throw new NeedVerifyError(`出现验证码,请求失败,Verifytype: ${verifyType},Verifyuuid: ${verifyUuid}`, response, verifyType, verifyUuid);
|
||||
}
|
||||
|
||||
const data = response.data;
|
||||
if (data.success) {
|
||||
return data.data || data.success;
|
||||
} else if (data.code === ErrorEnum.IP_BLOCK.code) {
|
||||
throw new IPBlockError(ErrorEnum.IP_BLOCK.msg, response);
|
||||
} else if (data.code === ErrorEnum.SIGN_FAULT.code) {
|
||||
throw new SignError(ErrorEnum.SIGN_FAULT.msg, response);
|
||||
} else {
|
||||
throw new DataFetchError(data, response);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.response && (error.response.status === 471 || error.response.status) === 461) {
|
||||
// Handle verification error
|
||||
const verifyType = error.response.headers['verifytype'];
|
||||
const verifyUuid = error.response.headers['verifyuuid'];
|
||||
throw new NeedVerifyError(`出现验证码,请求失败,Verifytype: ${verifyType},Verifyuuid: ${verifyUuid}`, error.response, verifyType, verifyUuid);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param {*} uri
|
||||
* @param {*} params
|
||||
* @param {Object} config
|
||||
* @param {*} [config.sign] - Whether to sign the request
|
||||
* @param {boolean} [config.isCreator] - Whether the request is for a creator
|
||||
* @param {boolean} [config.isCustomer] - Whether the request is for a customer
|
||||
* @param {*} [config.headers] - XSEC token for authentication
|
||||
* @returns
|
||||
*/
|
||||
async get(uri, params = null, config = {}) {
|
||||
if (params) {
|
||||
uri = `${uri}?${qs.stringify(params)}`;
|
||||
}
|
||||
if (config.sign) {
|
||||
await config.sign(uri, data, config);
|
||||
} else {
|
||||
const { headers } = this._preHeaders(uri, null);
|
||||
config = { ...config, headers: { ...config.headers, ...headers } };
|
||||
}
|
||||
let isCreator = config?.isCreator ?? false;
|
||||
let isCustomer = config?.isCustomer ?? false;
|
||||
let endpoint = this._host;
|
||||
if (isCustomer) {
|
||||
endpoint = this._customerHost;
|
||||
} else if (isCreator) {
|
||||
endpoint = this._creatorHost;
|
||||
}
|
||||
return this.request('GET', `${endpoint}${uri}`, config);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param {*} uri
|
||||
* @param {*} data
|
||||
* @param {Object} config
|
||||
* @param {*} [config.sign] - Whether to sign the request
|
||||
* @param {boolean} [config.isCreator] - Whether the request is for a creator
|
||||
* @param {boolean} [config.isCustomer] - Whether the request is for a customer
|
||||
* @param {*} [config.headers] - XSEC token for authentication
|
||||
* @returns
|
||||
*/
|
||||
async post(uri, data = null, config = {}) {
|
||||
let jsonStr = data ? JSON.stringify(data) : null;
|
||||
let endpoint = this._host;
|
||||
if (config.sign) {
|
||||
await config.sign(uri, data, config);
|
||||
} else {
|
||||
const { headers } = this._preHeaders(uri, data);
|
||||
config = { ...config, headers: { ...config.headers, ...headers } };
|
||||
}
|
||||
let isCreator = config?.isCreator ?? false;
|
||||
let isCustomer = config?.isCustomer ?? false;
|
||||
if (isCustomer) {
|
||||
endpoint = this._customerHost;
|
||||
} else if (isCreator) {
|
||||
endpoint = this._creatorHost;
|
||||
}
|
||||
if (data) {
|
||||
return this.request('POST', `${endpoint}${uri}`, {
|
||||
...config,
|
||||
data: jsonStr,
|
||||
headers: {
|
||||
...config.headers,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
}
|
||||
return this.request('POST', `${endpoint}${uri}`, { ...config, data });
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取笔记详情
|
||||
* 注意: 需要xsec_token
|
||||
* @param {string} noteId
|
||||
* @returns
|
||||
*/
|
||||
async getNoteById(noteId, xsecToken, xsecSource = 'pc_feed') {
|
||||
if (!xsecToken) {
|
||||
throw new Error('xsecToken is required');
|
||||
}
|
||||
const data = {
|
||||
source_note_id: noteId,
|
||||
image_scenes: ['CRD_WM_WEBP'],
|
||||
xsec_token: xsecToken,
|
||||
xsec_source: xsecSource,
|
||||
};
|
||||
const uri = '/api/sns/web/v1/feed';
|
||||
|
||||
try {
|
||||
const res = await this.post(uri, data);
|
||||
return res.items[0].note_card;
|
||||
} catch (error) {
|
||||
console.error('Error fetching note:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async getNoteByIdFromHtml(noteId, xsecToken, xsecSource = 'pc_feed') {
|
||||
const url = `https://www.xiaohongshu.com/explore/${noteId}?xsec_token=${xsecToken}&xsec_source=${xsecSource}`;
|
||||
let html = '';
|
||||
try {
|
||||
const response = await this.axiosInstance.get(url, {
|
||||
headers: {
|
||||
'user-agent': this.userAgent,
|
||||
referer: 'https://www.xiaohongshu.com/',
|
||||
},
|
||||
});
|
||||
|
||||
html = response.data;
|
||||
const stateMatch = html.match(/window.__INITIAL_STATE__=({.*})<\/script>/);
|
||||
|
||||
if (stateMatch) {
|
||||
const state = stateMatch[1].replace(/undefined/g, '""');
|
||||
if (state !== '{}') {
|
||||
const noteDict = transformJsonKeys(JSON.parse(state));
|
||||
return noteDict.note.note_detail_map[noteId].note;
|
||||
}
|
||||
}
|
||||
|
||||
if (html.includes(ErrorEnum.IP_BLOCK.value)) {
|
||||
throw new IPBlockError(ErrorEnum.IP_BLOCK.value);
|
||||
}
|
||||
|
||||
throw new DataFetchError(html);
|
||||
} catch (error) {
|
||||
console.error('Error fetching note:', error);
|
||||
fs.writeFileSync('a.html', html);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async getSelfInfo() {
|
||||
const uri = '/api/sns/web/v1/user/selfinfo';
|
||||
return this.get(uri);
|
||||
}
|
||||
|
||||
async getSelfInfoV2() {
|
||||
const uri = '/api/sns/web/v2/user/me';
|
||||
return this.get(uri);
|
||||
}
|
||||
|
||||
async getUserInfo(userId) {
|
||||
const uri = '/api/sns/web/v1/user/otherinfo';
|
||||
const params = {
|
||||
target_user_id: userId,
|
||||
};
|
||||
return this.get(uri, params);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} keyword 关键词
|
||||
* @param {number} page 页码
|
||||
* @param {number} pageSize 分页查询的数量
|
||||
* @param {string} sort 搜索的类型,分为: general, popularity_descending, time_descending
|
||||
* @param {number} noteType 笔记类型
|
||||
* @returns
|
||||
*/
|
||||
async getNoteByKeyword(keyword, page = 1, pageSize = 20, sort = SearchSortType.GENERAL, noteType = SearchNoteType.ALL) {
|
||||
const uri = '/api/sns/web/v1/search/notes';
|
||||
const data = {
|
||||
keyword: keyword,
|
||||
page: page,
|
||||
page_size: pageSize,
|
||||
search_id: getSearchId(),
|
||||
sort: sort.value,
|
||||
note_type: noteType.value,
|
||||
image_formats: ['jpg', 'webp', 'avif'],
|
||||
ext_flags: [],
|
||||
};
|
||||
|
||||
return this.post(uri, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取笔记评论
|
||||
* @param {string} noteId 笔记id
|
||||
* @param {string} cursor 分页查询的下标,默认为""
|
||||
* @returns
|
||||
*/
|
||||
async getNoteComments(noteId, cursor = '') {
|
||||
const uri = '/api/sns/web/v2/comment/page';
|
||||
const params = {
|
||||
note_id: noteId,
|
||||
cursor: cursor,
|
||||
};
|
||||
return this.get(uri, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户笔记
|
||||
* @param {*} userId
|
||||
* @param {*} cursor
|
||||
* @returns
|
||||
*/
|
||||
async getUserNotes(userId, cursor = '') {
|
||||
const uri = '/api/sns/web/v1/user_posted';
|
||||
const params = {
|
||||
cursor: cursor,
|
||||
num: 30,
|
||||
user_id: userId,
|
||||
image_scenes: 'FD_WM_WEBP',
|
||||
};
|
||||
return this.get(uri, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取账号@我通知
|
||||
* @param {*} num
|
||||
* @param {*} cursor
|
||||
* @returns
|
||||
*/
|
||||
async getMentionNotifications(num = 20, cursor = '') {
|
||||
const uri = '/api/sns/web/v1/you/mentions';
|
||||
const params = { num: num, cursor: cursor };
|
||||
return this.get(uri, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取点赞通知
|
||||
* @param {*} num
|
||||
* @param {*} cursor
|
||||
* @returns
|
||||
*/
|
||||
async getLikeNotifications(num = 20, cursor = '') {
|
||||
const uri = '/api/sns/web/v1/you/likes';
|
||||
const params = { num: num, cursor: cursor };
|
||||
return this.get(uri, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取关注通知
|
||||
* @param {*} num
|
||||
* @param {*} cursor
|
||||
* @returns
|
||||
*/
|
||||
async getFollowNotifications(num = 20, cursor = '') {
|
||||
const uri = '/api/sns/web/v1/you/connections';
|
||||
const params = { num: num, cursor: cursor };
|
||||
return this.get(uri, params);
|
||||
}
|
||||
|
||||
async getUserInfoFromHtml(userId) {
|
||||
const url = `https://www.xiaohongshu.com/user/profile/${userId}`;
|
||||
try {
|
||||
const response = await this.axiosInstance.get(url, {
|
||||
headers: {
|
||||
'user-agent': this.userAgent,
|
||||
referer: 'https://www.xiaohongshu.com/',
|
||||
},
|
||||
});
|
||||
const html = response.data;
|
||||
const stateMatch = html.match(/window.__INITIAL_STATE__=({.*})<\/script>/);
|
||||
if (stateMatch) {
|
||||
const state = stateMatch[1].replace(/"undefined"/g, '"_"').replace(/\bundefined\b/g, '""');
|
||||
if (state !== '{}') {
|
||||
const parsedState = JSON.parse(state);
|
||||
const userBasicInfo = transformJsonKeys(parsedState).user.user_page_data.basic_info;
|
||||
return userBasicInfo;
|
||||
}
|
||||
}
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching user info:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { XhsClient };
|
||||
2666
packages/xhs-core/src/jsvmp/xhs.js
Normal file
2666
packages/xhs-core/src/jsvmp/xhs.js
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user