UNPKG

red-note-api

Version:

Parse the Xiaohongshu API

242 lines (217 loc) 7.65 kB
// noinspection JSStringConcatenationToES6Template,ES6ConvertRequireIntoImport,ExceptionCaughtLocallyJS,JSUnusedGlobalSymbols,JSUnusedLocalSymbols import axios, { AxiosInstance } from 'axios' import qs from 'query-string' const get_xs = require('./jsvmp/xhs.js') const { getXCommon, getSearchId, getRequestId } = require('./help.js') const { ErrorEnum, DataFetchError, IPBlockError, SignError, NeedVerifyError } = require('./exception.js') export default class RedNoteClient { private readonly _host: string = 'https://edith.xiaohongshu.com' private readonly _creatorHost: string = 'https://creator.xiaohongshu.com' private readonly _customerHost: string = 'https://customer.xiaohongshu.com' private readonly axiosInstance: AxiosInstance private readonly timeout?: number private readonly userAgent?: string public constructor(cookie?: string, userAgent?: string, timeout?: number) { this.timeout = timeout this.userAgent = userAgent this.axiosInstance = axios.create({ timeout: this.timeout, headers: { 'user-agent': this.userAgent, 'Content-Type': 'application/json' } }) if (cookie) { this.cookie = cookie } } set cookie(cookie: string) { this.axiosInstance.defaults.headers.Cookie = cookie } get cookie(): string { return this.axiosInstance.defaults.headers.Cookie as string } get cookieDict() { const cookieStr: string = this.cookie return cookieStr ? qs.parse(cookieStr.replace(/; /g, '&')) : {} } _preHeaders(url: string, data: any = 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 } async request(method: 'GET' | 'POST', url: string, config: any = {}) { 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: any) { 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 } } async get(uri: string, params: any = null, isCreator: boolean = false, isCustomer: boolean = false, config: any = {}) { let finalUri = uri if (params) { finalUri = `${uri}?${qs.stringify(params)}` } this._preHeaders(finalUri, null) let endpoint = this._host if (isCustomer) { endpoint = this._customerHost } else if (isCreator) { endpoint = this._creatorHost } return this.request('GET', `${endpoint}${finalUri}`, config) } async post(uri: string, data: any = null, isCreator: boolean = false, isCustomer: boolean = false, config: { headers?: any } = {}) { // let jsonStr = data ? JSON.stringify(data).replace(/[\u007F-\uFFFF]/g, function(chr) { // return "\\u" + ("0000" + chr.charCodeAt(0).toString(16)).substr(-4); // }) : null; this._preHeaders(uri, data) let endpoint = this._host if (isCustomer) { endpoint = this._customerHost } else if (isCreator) { endpoint = this._creatorHost } if (data) { return this.request('POST', `${endpoint}${uri}`, { ...config, data: data, headers: { ...config.headers, 'Content-Type': 'application/json' } }) } return this.request('POST', `${endpoint}${uri}`, { ...config, data }) } public async getNoteById(noteId: string, image_scenes: string[] = ['CRD_WM_WEBP']) { const data = { source_note_id: noteId, image_scenes: image_scenes } 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 } } public async getSelfInfo() { const uri = '/api/sns/web/v2/user/me' return this.get(uri) } public async searchUser(keyword: string, page = 1, page_size = 20) { const body = { search_user_request: { biz_type: 'web_search_user', keyword: keyword, page: page, page_size: page_size, request_id: getRequestId(), search_id: getSearchId() } } const uri = '/api/sns/web/v1/search/usersearch' return this.post(uri, body) } public async searchNotes( keyword: string, page = 1, page_size = 20, sort = 'general', note_type = 0, // 0 = All, 1 = Video, 2 = Image ext_flags = [], image_formats = ['jpg', 'webp', 'avif'] ) { const body = { keyword: keyword, page: page, page_size: page_size, search_id: getSearchId(), sort: sort, note_type: note_type, ext_flags: ext_flags, image_formats: image_formats } const uri = '/api/sns/web/v1/search/notes' return this.post(uri, body) } public async getUserInfo(userId: string) { const uri = '/api/sns/web/v1/user/otherinfo' const params = { target_user_id: userId } return this.get(uri, params) } public async getNoteListByUserId(num = 30, cursor = '', user_id: string, image_scenes = 'FD_WM_WEBP') { const uri = '/api/sns/web/v1/user_posted' const data = { // num: num, cursor: cursor, user_id: user_id, image_scenes: image_scenes } return this.get(uri, data) } public async postComment(note_id: string, content: string, target_comment_id?: string, at_users: string[] = []) { const uri = '/api/sns/web/v1/comment/post' const body = { note_id: note_id, content: content, target_comment_id: target_comment_id, at_users: at_users } return this.post(uri, body) } public async deleteComment(note_id: string, comment_id: string) { const uri = '/api/sns/web/v1/comment/delete' return this.post(uri, { note_id: note_id, comment_id: comment_id }) } }