| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102 |
- const VOC_SOCIAL_API_ROOT = 'https://server.fmode.cn/api/voc-social';
- const XIAOHONGSHU_APP_BASE_URL = `${VOC_SOCIAL_API_ROOT}/xiaohongshu/app`;
- function buildUrl(baseUrl, path, query = {}) {
- const url = new URL(`${baseUrl.replace(/\/$/, '')}/${path.replace(/^\//, '')}`);
- Object.entries(query).forEach(([key, value]) => {
- if (value !== undefined && value !== null && value !== '') {
- url.searchParams.set(key, String(value));
- }
- });
- return url.toString();
- }
- function unwrapData(json) {
- if (!json || typeof json !== 'object') return {};
- return json.data?.data || json.data || json;
- }
- function pickBusinessStatus(json) {
- const topCode = Number(json?.code);
- if (Number.isFinite(topCode) && topCode !== 0 && topCode !== 200) {
- return topCode;
- }
- const nestedCode = Number(json?.data?.status_code);
- if (Number.isFinite(nestedCode) && nestedCode !== 0 && nestedCode !== 200) {
- return nestedCode;
- }
- return 0;
- }
- function redactSecret(value) {
- return String(value || '')
- .replace(/Bearer\s+[^"'\s,}]+/gi, 'Bearer [REDACTED]')
- .replace(/\bsk-[A-Za-z0-9_-]{6,}\b/g, 'sk-[REDACTED]')
- .replace(/\br:[A-Za-z0-9]{4,}\b/g, 'r:[REDACTED]')
- .replace(/("Authorization"\s*:\s*")([^"]+)(")/gi, '$1[REDACTED]$3')
- .replace(/("api[_-]?token"\s*:\s*")([^"]+)(")/gi, '$1[REDACTED]$3');
- }
- async function requestJson(url, token) {
- const response = await fetch(url, {
- method: 'GET',
- headers: {
- Authorization: `Bearer ${token}`,
- Accept: 'application/json'
- }
- });
- const text = await response.text();
- let json;
- try {
- json = JSON.parse(text);
- } catch {
- json = undefined;
- }
- const businessStatus = pickBusinessStatus(json);
- if (!response.ok || businessStatus) {
- const message = json?.message || json?.msg || json?.data?.msg || text || `HTTP ${response.status}`;
- const error = new Error(redactSecret(message));
- error.httpStatus = businessStatus || response.status || 500;
- error.responseBody = redactSecret(text);
- throw error;
- }
- return { json, data: unwrapData(json), rawText: text };
- }
- class XiaohongshuApi {
- constructor(options = {}) {
- this.baseUrl = options.baseUrl || process.env.XIAOHONGSHU_API_BASE_URL || XIAOHONGSHU_APP_BASE_URL;
- this.token = options.token;
- }
- async searchNotes({ keyword, page = 1, sort = 'popularity_descending', noteType = '_0' }) {
- const url = buildUrl(this.baseUrl, 'search_notes', {
- keyword,
- page,
- sort,
- note_type: noteType
- });
- return requestJson(url, this.token);
- }
- async getNoteDetail({ noteId }) {
- const url = buildUrl(this.baseUrl, 'get_note_info', {
- note_id: noteId
- });
- return requestJson(url, this.token);
- }
- async getNoteComments({ noteId, cursor = '' }) {
- const url = buildUrl(this.baseUrl, 'get_note_comments', {
- note_id: noteId,
- cursor
- });
- return requestJson(url, this.token);
- }
- }
- module.exports = {
- VOC_SOCIAL_API_ROOT,
- XIAOHONGSHU_APP_BASE_URL,
- XiaohongshuApi
- };
|