UNPKG

n8n-nodes-xiaohongshu

Version:

n8n node for Xiaohongshu (Little Red Book) API integration

676 lines (675 loc) 27.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.XiaohongshuNode = void 0; const n8n_workflow_1 = require("n8n-workflow"); const GenericFunctions_1 = require("./GenericFunctions"); class XiaohongshuNode { constructor() { this.description = { displayName: '小红书', name: 'xiaohongshuNode', icon: 'file:xiaohongshu.svg', group: ['transform'], version: 1, subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', description: '与小红书 API 进行交互', defaults: { name: '小红书', }, inputs: ['main'], outputs: ['main'], credentials: [ { name: 'xiaohongshuCookieApi', required: true, }, ], properties: [ { displayName: 'API 分类', name: 'category', type: 'options', typeOptions: { loadOptionsMethod: 'getCategories', }, default: '', required: true, description: '选择 API 分类', }, { displayName: 'API 接口', name: 'operation', type: 'options', typeOptions: { loadOptionsMethod: 'getOperations', loadOptionsDependsOn: ['category'], }, default: '', required: true, description: '选择具体的 API 接口', }, { displayName: '使用预设参数', name: 'usePresetParams', type: 'boolean', default: true, description: '是否使用接口的预设参数模板', }, { displayName: '请求方法', name: 'method', type: 'options', options: [ { name: 'GET', value: 'GET', }, { name: 'POST', value: 'POST', }, { name: 'PUT', value: 'PUT', }, { name: 'DELETE', value: 'DELETE', }, ], default: 'GET', description: 'HTTP 请求方法', displayOptions: { show: { usePresetParams: [false], }, }, }, { displayName: 'URL', name: 'url', type: 'string', default: '', placeholder: '/api/sns/web/v1/search/notes', required: true, description: 'API 接口的 URL 路径', displayOptions: { show: { usePresetParams: [false], }, }, }, { displayName: '查询参数', name: 'queryParameters', placeholder: '添加查询参数', type: 'fixedCollection', typeOptions: { multipleValues: true, }, default: {}, options: [ { name: 'parameter', displayName: '参数', values: [ { displayName: '参数名', name: 'name', type: 'string', default: '', description: '查询参数的名称', }, { displayName: '参数值', name: 'value', type: 'string', default: '', description: '查询参数的值', }, ], }, ], displayOptions: { show: { usePresetParams: [false], method: ['GET'], }, }, }, // 通用参数字段 - 这些字段将根据选择的操作动态显示 // 关键词参数(搜索类接口) { displayName: '关键词', name: 'keyword', type: 'string', default: '', description: '搜索关键词', displayOptions: { show: { usePresetParams: [true], operation: ['searchNotes', 'searchUsers', 'getSearchSuggestion'], }, }, }, // 分页参数 { displayName: '页码', name: 'page', type: 'number', default: 1, description: '分页页码,从1开始', displayOptions: { show: { usePresetParams: [true], operation: ['searchNotes', 'searchUsers'], }, }, }, { displayName: '每页数量', name: 'page_size', type: 'number', default: 20, description: '每页返回的结果数量', displayOptions: { show: { usePresetParams: [true], operation: ['searchNotes', 'searchUsers'], }, }, }, // 笔记ID参数(笔记相关接口) { displayName: '笔记ID', name: 'note_id', type: 'string', default: '', required: false, description: '笔记的唯一标识符(如果填写了笔记链接则会自动解析)', displayOptions: { show: { usePresetParams: [true], operation: ['getNoteComments', 'getNoteSubComments'], note_url: [''], }, }, }, // 笔记链接解析 { displayName: '笔记链接', name: 'note_url', type: 'string', default: '', placeholder: 'https://www.xiaohongshu.com/explore/67da7a9a000000000a03c169?xsec_token=...', description: '小红书笔记链接,将自动解析笔记ID和安全令牌', displayOptions: { show: { usePresetParams: [true], operation: ['getNoteInfo'], }, }, }, // 用户链接解析(用于获取用户笔记) { displayName: '用户链接', name: 'user_url', type: 'string', default: '', placeholder: 'https://www.xiaohongshu.com/user/profile/67da7a9a000000000a03c169?xsec_token=...', description: '小红书用户链接,将自动解析用户ID和安全令牌', displayOptions: { show: { usePresetParams: [true], operation: ['getUserNotes'], }, }, }, // 用户ID参数(用于获取用户笔记) { displayName: '用户ID', name: 'user_id', type: 'string', default: '', required: false, description: '用户的唯一标识符(如果填写了用户链接则会自动解析)', displayOptions: { show: { usePresetParams: [true], operation: ['getUserNotes'], user_url: [''], }, }, }, // 安全令牌参数(用于获取用户笔记) { displayName: '安全令牌', name: 'xsec_token', type: 'string', default: '', required: false, description: '安全验证令牌(如果填写了用户链接则会自动解析)', displayOptions: { show: { usePresetParams: [true], operation: ['getUserNotes'], user_url: [''], }, }, }, { displayName: '笔记ID', name: 'source_note_id', type: 'string', default: '', required: false, description: '源笔记的唯一标识符(如果填写了笔记链接则会自动解析)', displayOptions: { show: { usePresetParams: [true], operation: ['getNoteInfo'], note_url: [''], }, }, }, // 用户链接解析(获取用户信息) { displayName: '用户链接', name: 'user_url', type: 'string', default: '', placeholder: 'https://www.xiaohongshu.com/user/profile/63fdb99e0000000029010fc2?xsec_token=...', description: '小红书用户链接,将自动解析用户ID和安全令牌', displayOptions: { show: { usePresetParams: [true], operation: ['getUserInfo'], }, }, }, // 用户ID参数(用户相关接口) { displayName: '用户ID', name: 'target_user_id', type: 'string', default: '', required: false, description: '目标用户的唯一标识符(如果填写了用户链接则会自动解析)', displayOptions: { show: { usePresetParams: [true], operation: ['getUserInfo'], user_url: [''], }, }, }, // 安全令牌参数(获取用户信息) { displayName: '安全令牌', name: 'xsec_token', type: 'string', default: '', required: false, description: '安全验证令牌(如果填写了用户链接则会自动解析)', displayOptions: { show: { usePresetParams: [true], operation: ['getUserInfo'], user_url: [''], }, }, }, // 游标参数(分页相关) { displayName: '游标', name: 'cursor', type: 'string', default: '', description: '分页游标,用于获取下一页数据', displayOptions: { show: { usePresetParams: [true], operation: ['getNoteComments', 'getNoteSubComments', 'getUserNotes'], }, }, }, // 数量参数 { displayName: '数量', name: 'num', type: 'number', default: 30, description: '获取的数据数量', displayOptions: { show: { usePresetParams: [true], operation: ['getUserNotes', 'getNoteSubComments'], }, }, }, // 评论相关参数 { displayName: '顶部评论ID', name: 'top_comment_id', type: 'string', default: '', required: false, description: '顶部评论的唯一标识符(可选,用于获取特定评论下的回复)', displayOptions: { show: { usePresetParams: [true], operation: ['getNoteComments'], }, }, }, { displayName: '根评论ID', name: 'root_comment_id', type: 'string', default: '', required: true, description: '根评论的唯一标识符', displayOptions: { show: { usePresetParams: [true], operation: ['getNoteSubComments'], }, }, }, // 安全令牌参数 { displayName: '安全令牌', name: 'xsec_token', type: 'string', default: '', required: false, description: '安全验证令牌(如果填写了笔记链接则会自动解析)', displayOptions: { show: { usePresetParams: [true], operation: ['getNoteInfo'], note_url: [''], }, }, }, // 排序参数 { displayName: '排序方式', name: 'sort', type: 'options', options: [ { name: '综合排序', value: 'general', }, { name: '最热排序', value: 'popularity_descending', }, { name: '最新排序', value: 'time_descending', }, ], default: 'general', description: '搜索结果排序方式', displayOptions: { show: { usePresetParams: [true], operation: ['searchNotes'], }, }, }, // 内容类型参数 { displayName: '内容类型', name: 'note_type', type: 'options', options: [ { name: '全部', value: 0, }, { name: '仅视频', value: 1, }, { name: '仅图片', value: 2, }, ], default: 0, description: '搜索的内容类型', displayOptions: { show: { usePresetParams: [true], operation: ['searchNotes'], }, }, }, // 通用请求体(当不使用预设参数时) { displayName: '请求体', name: 'body', type: 'json', default: '{}', description: 'JSON 格式的请求体数据', displayOptions: { show: { usePresetParams: [false], method: ['POST', 'PUT'], }, }, }, { displayName: '响应格式', name: 'responseFormat', type: 'options', options: [ { name: 'JSON', value: 'json', }, { name: '文本', value: 'text', }, { name: '自动检测', value: 'autodetect', }, ], default: 'json', description: '期望的响应数据格式', }, { displayName: '完整响应', name: 'fullResponse', type: 'boolean', default: false, description: '是否返回完整的 HTTP 响应(包括状态码、响应头等)', }, ], }; this.methods = { loadOptions: { async getCategories() { try { const categories = await GenericFunctions_1.getApiCategories.call(this); return categories.map((category) => ({ name: category.name, value: category.value, })); } catch (error) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), `获取 API 分类失败: ${error instanceof Error ? error.message : String(error)}`); } }, async getOperations() { try { const category = this.getNodeParameter('category'); if (!category) { return []; } const operations = await GenericFunctions_1.getApisByCategory.call(this, category); return operations.map((operation) => ({ name: operation.name, value: operation.value, description: operation.description, })); } catch (error) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), `获取 API 接口失败: ${error instanceof Error ? error.message : String(error)}`); } }, }, }; } async execute() { const items = this.getInputData(); const returnData = []; for (let i = 0; i < items.length; i++) { try { const category = this.getNodeParameter('category', i); const operation = this.getNodeParameter('operation', i); const usePresetParams = this.getNodeParameter('usePresetParams', i); const responseFormat = this.getNodeParameter('responseFormat', i); const fullResponse = this.getNodeParameter('fullResponse', i); // 获取操作详情 const operationDetails = await GenericFunctions_1.getOperationDetails.call(this, category, operation); if (!operationDetails) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), `未找到操作: ${operation}`, { itemIndex: i }); } // 构建请求参数 let requestOptions = { method: operationDetails.method, url: operationDetails.url, }; // 智能参数处理 if (usePresetParams) { // 使用新的动态参数构建系统 requestOptions = (0, GenericFunctions_1.buildDynamicRequestParams)(this, category, operation, i); } else { // 使用用户自定义参数 const method = this.getNodeParameter('method', i); const url = this.getNodeParameter('url', i); requestOptions.method = method; requestOptions.url = url; // 处理查询参数 if (method === 'GET') { const queryParameters = this.getNodeParameter('queryParameters.parameter', i, []); if (queryParameters.length > 0) { requestOptions.params = {}; queryParameters.forEach((param) => { requestOptions.params[param.name] = param.value; }); } } // 处理请求体 if (['POST', 'PUT'].includes(method)) { const bodyData = this.getNodeParameter('body', i); if (bodyData) { try { requestOptions.data = JSON.parse(bodyData); } catch (error) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), `请求体 JSON 格式错误: ${error instanceof Error ? error.message : String(error)}`, { itemIndex: i }); } } } } // 处理动态参数替换(从输入数据中获取值) if (items[i].json) { requestOptions = XiaohongshuNode.replaceDynamicValues(requestOptions, items[i].json); } // 发送请求 const response = await GenericFunctions_1.xiaohongshuApiRequest.call(this, requestOptions); let responseData; if (fullResponse) { responseData = { statusCode: response.statusCode || 200, headers: response.headers || {}, body: response.data || response, }; } else { responseData = response.data || response; } // 处理响应格式 if (responseFormat === 'json' && typeof responseData === 'string') { try { responseData = JSON.parse(responseData); } catch (error) { // 如果解析失败,保持原始字符串格式 } } // 添加操作信息到响应中 const resultData = { ...responseData, _meta: { operation: operation, category: category, method: requestOptions.method, url: requestOptions.url, timestamp: new Date().toISOString(), } }; returnData.push({ json: resultData, pairedItem: { item: i }, }); } catch (error) { if (this.continueOnFail()) { returnData.push({ json: { error: error instanceof Error ? error.message : String(error), _meta: { failed: true, timestamp: new Date().toISOString(), } }, pairedItem: { item: i }, }); continue; } throw error; } } return [returnData]; } /** * 替换动态值(支持 {{$json.field}} 语法) */ static replaceDynamicValues(obj, inputData) { if (typeof obj === 'string') { return obj.replace(/\{\{\$json\.([^}]+)\}\}/g, (match, field) => { const value = XiaohongshuNode.getNestedValue(inputData, field); return value !== undefined ? value : match; }); } else if (Array.isArray(obj)) { return obj.map(item => XiaohongshuNode.replaceDynamicValues(item, inputData)); } else if (obj && typeof obj === 'object') { const result = {}; for (const [key, value] of Object.entries(obj)) { result[key] = XiaohongshuNode.replaceDynamicValues(value, inputData); } return result; } return obj; } /** * 获取嵌套对象的值 */ static getNestedValue(obj, path) { return path.split('.').reduce((current, key) => { return current && current[key] !== undefined ? current[key] : undefined; }, obj); } } exports.XiaohongshuNode = XiaohongshuNode;