n8n-nodes-kingsoft-airscript
Version:
一个 n8n 社区节点,用于执行金山 AirScript 脚本,支持同步、异步和所有 Context 参数。
475 lines • 24.9 kB
JavaScript
;
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