UNPKG

n8n-nodes-kingsoft-airscript

Version:

一个 n8n 社区节点,用于执行金山 AirScript 脚本,支持同步、异步和所有 Context 参数。

475 lines 24.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.KingsoftAirscript = void 0; const n8n_workflow_1 = require("n8n-workflow"); class KingsoftAirscript { constructor() { this.description = { displayName: 'Kingsoft AirScript', name: 'kingsoftAirscript', icon: 'file:kingsoftAirscript.svg', group: ['transform'], version: 3, subtitle: '={{$parameter["operation"]}}', description: '一个通用的金山 AirScript 执行器。访问 <a href="https://www.kdocs.cn/l/cho73TDGgMPP">官方指南 & 脚本库</a> 获取更多帮助', defaults: { name: 'Kingsoft AirScript', }, inputs: ['main'], outputs: ['main'], credentials: [ { name: 'kingsoftAirscriptApi', required: true, }, ], properties: [ { displayName: '操作', name: 'operation', type: 'options', noDataExpression: true, options: [ { name: '同步执行脚本 (Sync)', value: 'runScriptSync', action: '同步执行一个脚本并立即等待结果' }, { name: '异步执行脚本 (Async)', value: 'runScriptAsync', action: '异步执行一个脚本并立即返回任务ID' }, { name: '获取任务状态 (Get Status)', value: 'getTaskStatus', action: '根据任务ID获取脚本的执行状态和结果' }, ], default: 'runScriptSync', }, { displayName: 'ID 输入模式', name: 'idInputMode', type: 'options', noDataExpression: true, displayOptions: { show: { operation: ['runScriptSync', 'runScriptAsync'], }, }, options: [ { name: '通过 Webhook 链接输入', value: 'url', description: '粘贴完整的链接,最简单快捷', }, { name: '手动输入 ID', value: 'manual', description: '分别填写 File ID 和 Script ID', }, ], default: 'url', }, { displayName: 'Webhook 链接', name: 'webhookUrl', type: 'string', default: '', required: true, displayOptions: { show: { operation: ['runScriptSync', 'runScriptAsync'], idInputMode: ['url'], }, }, placeholder: '在此粘贴从金山文档复制的完整链接', description: '推荐!最简单的方式。', }, { displayName: '文件 ID (File ID)', name: 'fileId', type: 'string', default: '', required: true, displayOptions: { show: { operation: ['runScriptSync', 'runScriptAsync'], idInputMode: ['manual'], }, }, description: '脚本所在文档的 ID。', }, { displayName: '脚本 ID (Script ID)', name: 'scriptId', type: 'string', default: '', required: true, displayOptions: { show: { operation: ['runScriptSync', 'runScriptAsync'], idInputMode: ['manual'], }, }, description: '要执行的脚本的 ID。', }, { displayName: '可选上下文参数', name: 'contextParameters', type: 'collection', placeholder: '添加可选参数', default: {}, description: '设置可选的 Context 参数,会附加到请求中', displayOptions: { show: { operation: ['runScriptSync', 'runScriptAsync'], }, }, options: [ { displayName: '表名 (Sheet Name)', name: 'sheetName', type: 'string', default: '', description: '对应 Context.sheet_name' }, { displayName: '范围 (Range)', name: 'range', type: 'string', default: '', description: '对应 Context.range, 例如 "$B$156"' }, { displayName: '链接来源 (Link From)', name: 'linkFrom', type: 'string', default: '', description: '对应 Context.link_from' }, { displayName: '视图名称 (DB Active View)', name: 'dbActiveView', type: 'string', default: '', description: '对应 Context.db_active_view' }, { displayName: '选区 (DB Selection)', name: 'dbSelection', type: 'string', default: '', description: '对应 Context.db_selection' }, ], }, { displayName: '脚本参数 (Argv)', name: 'argv', type: 'json', default: '{\n "message": "Hello from n8n!"\n}', required: true, displayOptions: { show: { operation: [ 'runScriptSync', 'runScriptAsync', ], }, }, description: '以 JSON 格式向脚本传递动态参数。不知道如何为您的业务场景构建参数?欢迎访问我们的[脚本模板库](https://www.kdocs.cn/l/cho73TDGgMPP)寻找灵感。', }, { displayName: '任务 ID (Task ID)', name: 'taskId', type: 'string', default: '', required: true, displayOptions: { show: { operation: [ 'getTaskStatus', ], }, }, description: '从异步执行脚本操作中获取到的任务 ID', }, { displayName: '高级选项', name: 'options', type: 'collection', placeholder: '添加高级选项', default: {}, description: '配置异步等待、输出格式和性能优化。', options: [ { displayName: '并行执行', name: 'a_parallelExecution', type: 'boolean', default: false, description: 'Whether开启后,节点将同时处理所有输入项。', }, { displayName: '超时时间 (秒)', name: 'b_timeoutSeconds', type: 'number', default: 300, typeOptions: { minValue: 5 }, displayOptions: { show: { '/operation': ['runScriptAsync'], '/c_waitForCompletion': [true] } }, }, { displayName: '等待执行完成', name: 'c_waitForCompletion', type: 'boolean', default: false, description: 'Whether开启后,当使用"异步执行"时,节点将自动轮询,直到任务完成。', displayOptions: { show: { '/operation': ['runScriptAsync'] } }, }, { displayName: '轮询间隔 (秒)', name: 'd_pollIntervalSeconds', type: 'number', default: 5, typeOptions: { minValue: 1 }, displayOptions: { show: { '/operation': ['runScriptAsync'], '/c_waitForCompletion': [true] } }, }, { displayName: '批处理数量', name: 'e_batchSize', type: 'number', default: 10, typeOptions: { minValue: 1 }, displayOptions: { show: { '/a_parallelExecution': [true] } }, }, { displayName: '输出格式', name: 'f_outputFormat', type: 'options', default: 'fullResponse', displayOptions: { show: { '/operation': ['runScriptSync', 'runScriptAsync'] } }, options: [ { name: '完整响应', value: 'fullResponse' }, { name: '仅结果', value: 'resultOnly' }, { name: '拆分所有日志', value: 'splitLogs' }, { name: '仅拆分错误日志', value: 'splitErrors' } ] }, { displayName: '智能解析 Result', name: 'g_parseResultString', type: 'boolean', default: false, description: 'Whether开启后,如果 `result` 是 JSON 字符串,会自动解析。', displayOptions: { show: { '/operation': ['runScriptSync', 'runScriptAsync'] } }, }, ], }, ], usableAsTool: true, }; } async execute() { var _a; const 输入数据项列表 = this.getInputData(); const 凭证 = await this.getCredentials('kingsoftAirscriptApi'); const 执行脚本 = async (itemIndex, 类型) => { const id输入模式 = this.getNodeParameter('idInputMode', itemIndex, 'url'); let 文件ID = '', 脚本ID = ''; if (id输入模式 === 'url') { const webhook链接 = this.getNodeParameter('webhookUrl', itemIndex, ''); try { const url对象 = new URL(webhook链接); const 路径分段 = url对象.pathname.split('/'); const 文件ID索引 = 路径分段.indexOf('file'); const 脚本ID索引 = 路径分段.indexOf('script'); if (文件ID索引 !== -1 && 脚本ID索引 !== -1 && 路径分段.length > Math.max(文件ID索引, 脚本ID索引) + 1) { 文件ID = 路径分段[文件ID索引 + 1]; 脚本ID = 路径分段[脚本ID索引 + 1]; } else { throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Webhook 链接格式不正确,无法解析出 File ID 和 Script ID。', { itemIndex }); } } catch { throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Webhook 链接格式不正确,无法解析出 File ID 和 Script ID。', { itemIndex }); } } else { 文件ID = this.getNodeParameter('fileId', itemIndex, ''); 脚本ID = this.getNodeParameter('scriptId', itemIndex, ''); } if (!文件ID || !脚本ID) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), '未能获取到有效的 File ID 和 Script ID。', { itemIndex }); } const argv字符串 = this.getNodeParameter('argv', itemIndex, {}); const 可选上下文参数 = this.getNodeParameter('contextParameters', itemIndex, {}); let argv参数; if (typeof argv字符串 === 'string') { try { argv参数 = JSON.parse(argv字符串); } catch { argv参数 = { value: argv字符串 }; } } else { argv参数 = argv字符串; } for (const key in argv参数) { if (Object.prototype.hasOwnProperty.call(argv参数, key)) { const value = argv参数[key]; if (typeof value === 'string') { const trimmedValue = value.trim(); if ((trimmedValue.startsWith('{') && trimmedValue.endsWith('}')) || (trimmedValue.startsWith('[') && trimmedValue.endsWith(']'))) { try { argv参数[key] = JSON.parse(trimmedValue); console.log(`[N8N Node] 智能反序列化成功: 字段 '${key}' 已从字符串恢复为对象/数组。`); } catch { console.log(`[N8N Node] 字段 '${key}' 看起来像JSON但解析失败,保持为字符串。`); } } } } } const 上下文 = { argv: argv参数 }; if (可选上下文参数.sheetName) 上下文.sheet_name = 可选上下文参数.sheetName; if (可选上下文参数.range) 上下文.range = 可选上下文参数.range; if (可选上下文参数.linkFrom) 上下文.link_from = 可选上下文参数.linkFrom; if (可选上下文参数.dbActiveView) 上下文.db_active_view = 可选上下文参数.dbActiveView; if (可选上下文参数.dbSelection) 上下文.db_selection = 可选上下文参数.dbSelection; const 接口路径 = 类型 === 'sync' ? 'sync_task' : 'task'; const 请求URL = `https://www.kdocs.cn/api/v3/ide/file/${文件ID}/script/${脚本ID}/${接口路径}`; return await this.helpers.httpRequest({ method: 'POST', url: 请求URL, headers: { 'Content-Type': 'application/json', 'AirScript-Token': 凭证.apiToken }, body: { Context: 上下文 }, json: true, }); }; const 获取任务状态 = async (任务ID) => { return await this.helpers.httpRequest({ method: 'GET', url: `https://www.kdocs.cn/api/v3/script/task`, headers: { 'Content-Type': 'application/json' }, qs: { task_id: 任务ID }, json: true, }); }; const 处理单个项目 = async (索引) => { var _a, _b, _c, _d, _e, _f; try { const 操作类型 = this.getNodeParameter('operation', 索引, 'runScriptSync'); const 高级选项 = this.getNodeParameter('options', 索引, {}); let 响应数据; if (操作类型 === 'runScriptSync') { 响应数据 = await 执行脚本(索引, 'sync'); } else if (操作类型 === 'runScriptAsync') { const 初始响应 = await 执行脚本(索引, 'async'); if (!高级选项.c_waitForCompletion) { 响应数据 = 初始响应; } else { const 任务ID = (((_a = 初始响应.data) === null || _a === void 0 ? void 0 : _a.task_id) || 初始响应.task_id); if (!任务ID) throw new n8n_workflow_1.NodeOperationError(this.getNode(), '无法从异步响应中获取 task_id。', { itemIndex: 索引 }); const 轮询间隔 = ((_b = 高级选项.d_pollIntervalSeconds) !== null && _b !== void 0 ? _b : 5) * 1000; const 超时时间 = ((_c = 高级选项.b_timeoutSeconds) !== null && _c !== void 0 ? _c : 300) * 1000; let 轮询次数 = 0; const 最大轮询次数 = Math.floor(超时时间 / 轮询间隔); while (轮询次数 < 最大轮询次数) { if (轮询次数 > 0) { } const 状态响应 = await 获取任务状态(任务ID); if (状态响应.status === 'finished') { 响应数据 = 状态响应; break; } 轮询次数++; } if (!响应数据) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), `异步任务等待超时(${超时时间 / 1000}秒)。`, { itemIndex: 索引 }); } } } else if (操作类型 === 'getTaskStatus') { const 任务ID = this.getNodeParameter('taskId', 索引, ''); 响应数据 = await 获取任务状态(任务ID); } const 单项的返回数据列表 = []; if (响应数据) { const data = 响应数据.data; if (高级选项.g_parseResultString && data && typeof data.result === 'string') { try { data.result = JSON.parse(data.result); } catch { if (data.result) { data.result = data.result.replace(/\\n/g, '\n'); } } } switch (高级选项.f_outputFormat) { case 'resultOnly': 单项的返回数据列表.push({ json: { result: (_d = data === null || data === void 0 ? void 0 : data.result) !== null && _d !== void 0 ? _d : null }, pairedItem: { item: 索引 } }); break; case 'splitLogs': if ((data === null || data === void 0 ? void 0 : data.logs) && Array.isArray(data.logs)) { data.logs.forEach((日志条目) => 单项的返回数据列表.push({ json: 日志条目, pairedItem: { item: 索引 } })); } break; case 'splitErrors': if ((data === null || data === void 0 ? void 0 : data.logs) && Array.isArray(data.logs)) { data.logs .filter((log) => log.level === 'error') .forEach((日志条目) => 单项的返回数据列表.push({ json: 日志条目, pairedItem: { item: 索引 } })); } break; default: 单项的返回数据列表.push({ json: 响应数据, pairedItem: { item: 索引 } }); break; } } return 单项的返回数据列表; } catch (error) { if (this.continueOnFail()) { let 错误消息 = '未知错误'; let 错误详情; if (error instanceof Error) { 错误消息 = error.message; } if (error && typeof error === 'object' && 'response' in error) { const httpError = error; if ((_e = httpError.response) === null || _e === void 0 ? void 0 : _e.data) { 错误详情 = httpError.response.data; 错误消息 = ((_f = httpError.response.data.error_details) === null || _f === void 0 ? void 0 : _f.msg) || httpError.response.data.error || httpError.message || 错误消息; } } return [{ json: { error: 错误消息, details: 错误详情 }, pairedItem: { item: 索引 } }]; } throw error; } }; const 返回数据列表 = []; const 性能选项 = this.getNodeParameter('options', 0, {}); if (!性能选项.a_parallelExecution || 输入数据项列表.length <= 1) { for (let 索引 = 0; 索引 < 输入数据项列表.length; 索引++) { const 单项的处理结果 = await 处理单个项目(索引); 返回数据列表.push(...单项的处理结果); } } else { const 批处理大小 = (_a = 性能选项.e_batchSize) !== null && _a !== void 0 ? _a : 10; for (let i = 0; i < 输入数据项列表.length; i += 批处理大小) { const 当前批次的索引范围 = 输入数据项列表.slice(i, i + 批处理大小).map((_, 本地索引) => i + 本地索引); const 批处理Promises = 当前批次的索引范围.map(真实索引 => 处理单个项目(真实索引)); const 批处理结果 = await Promise.allSettled(批处理Promises); 批处理结果.forEach((result, 本地索引) => { var _a, _b, _c, _d, _e, _f; const 真实索引 = i + 本地索引; if (result.status === 'fulfilled') { 返回数据列表.push(...result.value); } else { if (!this.continueOnFail()) { const originalError = result.reason; throw new n8n_workflow_1.NodeOperationError(this.getNode(), originalError, { itemIndex: 真实索引 }); } const typedReason = result.reason; const 错误消息 = ((_c = (_b = (_a = typedReason.response) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.error_details) === null || _c === void 0 ? void 0 : _c.msg) || ((_e = (_d = typedReason.response) === null || _d === void 0 ? void 0 : _d.data) === null || _e === void 0 ? void 0 : _e.error) || typedReason.message || '未知错误'; 返回数据列表.push({ json: { error: 错误消息, details: (_f = typedReason.response) === null || _f === void 0 ? void 0 : _f.data }, pairedItem: { item: 真实索引 } }); } }); } } return [this.helpers.returnJsonArray(返回数据列表)]; } } exports.KingsoftAirscript = KingsoftAirscript; //# sourceMappingURL=KingsoftAirscript.node.js.map