n8n-nodes-xiaohongshu
Version:
n8n node for Xiaohongshu (Little Red Book) API integration
676 lines (675 loc) • 27.7 kB
JavaScript
;
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;