@luka-cat-mimi/n8n-nodes-feishu-project
Version:
飞书项目 N8N 集成插件,支持附件管理等功能
494 lines • 23.6 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.FeishuProject = void 0;
const n8n_workflow_1 = require("n8n-workflow");
const ResourceFactory_1 = __importDefault(require("../help/builder/ResourceFactory"));
const parameters_1 = require("../help/utils/parameters");
const GenericFunctions_1 = require("./GenericFunctions");
const resourceBuilder = ResourceFactory_1.default.build(__dirname);
function getBatchConfig(context) {
var _a, _b, _c, _d, _e, _f, _g;
try {
const options = context.getNodeParameter('options', 0, {});
const batchingEnabled = ((_a = options === null || options === void 0 ? void 0 : options.batching) === null || _a === void 0 ? void 0 : _a.batch) !== undefined;
const batchSize = (_d = (_c = (_b = options === null || options === void 0 ? void 0 : options.batching) === null || _b === void 0 ? void 0 : _b.batch) === null || _c === void 0 ? void 0 : _c.batchSize) !== null && _d !== void 0 ? _d : 50;
const batchInterval = (_g = (_f = (_e = options === null || options === void 0 ? void 0 : options.batching) === null || _e === void 0 ? void 0 : _e.batch) === null || _f === void 0 ? void 0 : _f.batchInterval) !== null && _g !== void 0 ? _g : 0;
return {
enabled: batchingEnabled,
batchSize: batchSize === 0 ? 1 : batchSize,
batchInterval,
};
}
catch {
return { enabled: false, batchSize: 50, batchInterval: 0 };
}
}
function getErrorMessage(error) {
if (error instanceof Error) {
return error.message;
}
return String(error);
}
function getErrorStack(error) {
if (error instanceof Error) {
return error.stack;
}
return undefined;
}
function isMultipleOutputResponse(response) {
return (response !== null &&
typeof response === 'object' &&
'outputType' in response &&
response.outputType !== undefined);
}
function processResponseData(context, responseData, itemIndex) {
if (isMultipleOutputResponse(responseData)) {
const { outputType, outputData } = responseData;
if (outputType === "multiple" && outputData) {
return { data: null, multipleOutput: outputData };
}
if (outputType === "none") {
return { data: null };
}
}
const executionData = context.helpers.constructExecutionMetaData(context.helpers.returnJsonArray(responseData), { itemData: { item: itemIndex } });
return { data: executionData };
}
function createErrorData(context, error, itemIndex) {
var _a, _b;
const errorObj = error;
return context.helpers.constructExecutionMetaData(context.helpers.returnJsonArray({
error: (_a = errorObj.description) !== null && _a !== void 0 ? _a : getErrorMessage(error),
...(errorObj.name === 'NodeApiError' && ((_b = errorObj.cause) === null || _b === void 0 ? void 0 : _b.error)
? { details: errorObj.cause.error }
: {}),
}), { itemData: { item: itemIndex } });
}
async function executeSerial(context, items, callFunc, resource, operation) {
const returnData = [[]];
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
try {
context.logger.debug('call function (serial)', {
resource,
operation,
itemIndex,
});
const responseData = await callFunc.call(context, itemIndex);
const processed = processResponseData(context, responseData, itemIndex);
if (processed.multipleOutput) {
return processed.multipleOutput;
}
if (processed.data === null && !processed.multipleOutput) {
return [];
}
if (processed.data) {
returnData[0].push(...processed.data);
}
}
catch (error) {
context.logger.error('call function error (serial)', {
resource,
operation,
itemIndex,
errorMessage: getErrorMessage(error),
stack: getErrorStack(error),
});
if (context.continueOnFail()) {
returnData[0].push(...createErrorData(context, error, itemIndex));
continue;
}
throw error;
}
}
return returnData;
}
async function executeParallel(context, items, callFunc, batchConfig, resource, operation) {
const { batchSize, batchInterval } = batchConfig;
const requestPromises = [];
const returnData = [[]];
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
if (itemIndex > 0 && batchSize > 0 && batchInterval > 0) {
if (itemIndex % batchSize === 0) {
await (0, n8n_workflow_1.sleep)(batchInterval);
}
}
context.logger.debug('call function (parallel)', {
resource,
operation,
itemIndex,
batch: batchSize > 0 ? Math.floor(itemIndex / batchSize) : 0,
});
const requestPromise = callFunc
.call(context, itemIndex)
.then((result) => ({ itemIndex, result }))
.catch((error) => ({ itemIndex, error: error }));
requestPromises.push(requestPromise);
}
const promisesResponses = await Promise.allSettled(requestPromises);
for (let i = 0; i < promisesResponses.length; i++) {
const response = promisesResponses[i];
if (response.status === 'rejected') {
const error = response.reason;
context.logger.error('call function error (parallel - rejected)', {
resource,
operation,
itemIndex: i,
errorMessage: getErrorMessage(error),
});
if (context.continueOnFail()) {
returnData[0].push(...createErrorData(context, error, i));
continue;
}
throw error;
}
const { itemIndex, result, error } = response.value;
if (error) {
context.logger.error('call function error (parallel)', {
resource,
operation,
itemIndex,
errorMessage: error.message,
stack: error.stack,
});
if (context.continueOnFail()) {
returnData[0].push(...createErrorData(context, error, itemIndex));
continue;
}
throw error;
}
if (result === undefined) {
continue;
}
const processed = processResponseData(context, result, itemIndex);
if (processed.multipleOutput) {
return processed.multipleOutput;
}
if (processed.data === null && !processed.multipleOutput) {
continue;
}
if (processed.data) {
returnData[0].push(...processed.data);
}
}
return returnData;
}
class FeishuProject {
constructor() {
this.description = {
displayName: '飞书项目',
subtitle: '={{ $parameter.resource }}:{{ $parameter.operation }}',
name: 'feishuProject',
icon: 'file:icon.svg',
group: ['transform'],
version: [1],
defaultVersion: 1,
description: '飞书项目管理 API 集成,支持工作项、空间、用户、工作流等完整功能',
defaults: {
name: '飞书项目',
},
usableAsTool: true,
inputs: [n8n_workflow_1.NodeConnectionTypes.Main],
outputs: `={{(${parameters_1.configuredOutputs})($parameter)}}`,
credentials: [
{
name: "feishuProjectApi",
required: true,
},
],
properties: [...resourceBuilder.build()],
};
this.methods = {
listSearch: {
async searchSpaces() {
const spaceIds = await GenericFunctions_1.getSpaceList.call(this);
if (!spaceIds || spaceIds.length === 0) {
return { results: [] };
}
const spaceDetails = await GenericFunctions_1.getSpaceDetails.call(this, spaceIds);
return {
results: spaceDetails.map((space) => ({
name: space.name,
value: space.project_key,
})),
};
},
async searchWorkItemTypes() {
const projectKey = this.getNodeParameter('project_key', undefined, {
extractValue: true,
});
if (!projectKey) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), '请先选择空间');
}
const workItemTypes = await GenericFunctions_1.getWorkItemTypes.call(this, projectKey);
return {
results: workItemTypes
.filter((type) => type.is_disable === 2)
.map((type) => ({
name: type.name,
value: type.type_key,
description: `工作项类型唯一key:${type.type_key}`,
})),
};
},
},
loadOptions: {
async loadSpaces() {
try {
const spaceIds = await GenericFunctions_1.getSpaceList.call(this);
if (!spaceIds || spaceIds.length === 0) {
return [];
}
const spaceDetails = await GenericFunctions_1.getSpaceDetails.call(this, spaceIds);
return spaceDetails.map((space) => ({
name: `${space.name} (${space.project_key})`,
value: space.project_key,
}));
}
catch {
return [];
}
},
async loadWorkItemTypes() {
try {
const projectKey = this.getNodeParameter('project_key', undefined, {
extractValue: true,
});
if (!projectKey) {
return [];
}
const workItemTypes = await GenericFunctions_1.getWorkItemTypes.call(this, projectKey);
return workItemTypes
.filter((type) => type.is_disable === 2)
.map((type) => ({
name: type.name,
value: type.type_key,
description: `工作项类型唯一key:${type.type_key}`,
}));
}
catch {
return [];
}
},
async loadWorkItemFields() {
const excludedFieldTypes = [
'multi_file',
];
try {
const projectKey = this.getNodeParameter('project_key', undefined, {
extractValue: true,
});
const workItemTypeKey = this.getNodeParameter('work_item_type_key');
if (!projectKey || !workItemTypeKey) {
return [];
}
const fieldMeta = await GenericFunctions_1.getWorkItemFieldMeta.call(this, projectKey, workItemTypeKey);
return fieldMeta
.filter((field) => !excludedFieldTypes.includes(field.field_type_key || ''))
.map((field) => ({
name: `${field.field_name}${field.is_required === 1 ? ' *' : ''}`,
value: field.field_key,
description: `类型: ${field.field_type_key || '未知'}, field_key: ${field.field_key}${field.is_required === 1 ? ' (必填)' : field.is_required === 3 ? ' (条件必填)' : ''}`,
}));
}
catch {
return [];
}
},
async loadWorkItemFieldsForWrite() {
const excludedFieldTypes = [
'multi_file',
];
const excludedFieldKeys = [
'current_status_operator',
'start_time',
'finish_time',
'owner',
'archiving_date',
'issue_reporter',
'archiving_status',
'template',
];
try {
const projectKey = this.getNodeParameter('project_key', undefined, {
extractValue: true,
});
const workItemTypeKey = this.getNodeParameter('work_item_type_key');
if (!projectKey || !workItemTypeKey) {
return [];
}
const fieldMeta = await GenericFunctions_1.getWorkItemFieldMeta.call(this, projectKey, workItemTypeKey);
return fieldMeta
.filter((field) => !excludedFieldTypes.includes(field.field_type_key || '') &&
!excludedFieldKeys.includes(field.field_key || ''))
.map((field) => ({
name: `${field.field_name}${field.is_required === 1 ? ' *' : ''}`,
value: field.field_key,
description: `类型: ${field.field_type_key || '未知'}, field_key: ${field.field_key}${field.is_required === 1 ? ' (必填)' : field.is_required === 3 ? ' (条件必填)' : ''}`,
}));
}
catch {
return [];
}
},
async loadAttachmentFields() {
try {
const projectKey = this.getNodeParameter('project_key', undefined, {
extractValue: true,
});
const workItemTypeKey = this.getNodeParameter('work_item_type_key');
if (!projectKey || !workItemTypeKey) {
return [];
}
const fieldMeta = await GenericFunctions_1.getWorkItemFieldMeta.call(this, projectKey, workItemTypeKey);
return fieldMeta
.filter((field) => field.field_type_key === 'multi_file')
.map((field) => ({
name: field.field_name,
value: field.field_key,
}));
}
catch {
return [];
}
},
async loadWorkItemFieldsAll() {
const excludedFieldTypes = [
'multi_file',
];
try {
const projectKey = this.getNodeParameter('project_key', undefined, {
extractValue: true,
});
const workItemTypeKey = this.getNodeParameter('work_item_type_key');
if (!projectKey || !workItemTypeKey) {
return [];
}
const fields = await GenericFunctions_1.getWorkItemFieldsAll.call(this, projectKey, workItemTypeKey);
return fields
.filter((field) => !excludedFieldTypes.includes(field.field_type_key || ''))
.map((field) => ({
name: field.field_name,
value: field.field_key,
description: `类型: ${field.field_type_key || '未知'}, field_key: ${field.field_key}${field.is_custom_field ? ' (自定义字段)' : ''}`,
}));
}
catch {
return [];
}
},
async loadWorkflowTemplates() {
try {
const projectKey = this.getNodeParameter('project_key', undefined, {
extractValue: true,
});
const workItemTypeKey = this.getNodeParameter('work_item_type_key');
if (!projectKey || !workItemTypeKey) {
return [];
}
const templates = await GenericFunctions_1.getWorkflowTemplates.call(this, projectKey, workItemTypeKey);
return templates.map((tpl) => ({
name: tpl.template_name,
value: tpl.template_id,
description: `Template_id:${tpl.template_id}, version:${tpl.version}`,
}));
}
catch {
return [];
}
},
},
resourceMapping: {
async getWorkItemFieldMapping() {
var _a, _b;
const excludedFieldTypes = ['multi_file'];
try {
const projectKey = this.getNodeParameter('project_key', undefined, {
extractValue: true,
});
const workItemTypeKey = this.getNodeParameter('work_item_type_key');
if (!projectKey || !workItemTypeKey) {
return { fields: [] };
}
const [fieldMeta, fieldDetails] = await Promise.all([
GenericFunctions_1.getWorkItemFieldMeta.call(this, projectKey, workItemTypeKey),
GenericFunctions_1.getWorkItemFieldsAll.call(this, projectKey, workItemTypeKey),
]);
const metaMap = new Map(fieldMeta.map((f) => [f.field_key, f]));
const detailMap = new Map(fieldDetails.map((f) => [f.field_key, f]));
const allFieldKeys = new Set([
...fieldMeta.map((f) => f.field_key),
...fieldDetails.map((f) => f.field_key),
]);
const fields = [];
for (const fieldKey of allFieldKeys) {
const meta = metaMap.get(fieldKey);
const detail = detailMap.get(fieldKey);
const fieldTypeKey = (meta === null || meta === void 0 ? void 0 : meta.field_type_key) || (detail === null || detail === void 0 ? void 0 : detail.field_type_key) || '';
if (excludedFieldTypes.includes(fieldTypeKey))
continue;
let type = (0, GenericFunctions_1.mapFeishuFieldTypeToN8n)(fieldTypeKey);
const hasOptions = (detail === null || detail === void 0 ? void 0 : detail.options) && Array.isArray(detail.options) && detail.options.length > 0;
if (type === 'options' && !hasOptions) {
type = 'string';
}
const fieldName = (meta === null || meta === void 0 ? void 0 : meta.field_name) || (detail === null || detail === void 0 ? void 0 : detail.field_name) || fieldKey;
const isRequired = (meta === null || meta === void 0 ? void 0 : meta.is_required) === 1;
const field = {
id: fieldKey,
displayName: `${fieldName}${isRequired ? ' *' : ''} (${fieldTypeKey || '未知'})`,
defaultMatch: false,
required: isRequired,
display: true,
type,
};
if (type === 'options' && hasOptions) {
field.options = detail.options
.filter((opt) => opt.is_disabled !== 1)
.map((opt) => ({
name: opt.label || String(opt.value || ''),
value: JSON.stringify(opt),
}));
}
if (((_a = meta === null || meta === void 0 ? void 0 : meta.default_value) === null || _a === void 0 ? void 0 : _a.value) !== undefined &&
((_b = meta === null || meta === void 0 ? void 0 : meta.default_value) === null || _b === void 0 ? void 0 : _b.value) !== null) {
const defaultVal = meta.default_value.value;
if (typeof defaultVal === 'string' ||
typeof defaultVal === 'number' ||
typeof defaultVal === 'boolean') {
field.defaultValue = defaultVal;
}
else {
field.defaultValue = JSON.stringify(defaultVal);
}
}
fields.push(field);
}
return { fields };
}
catch {
return { fields: [] };
}
},
},
};
}
async execute() {
const items = this.getInputData();
const resource = this.getNodeParameter('resource', 0);
const operation = this.getNodeParameter('operation', 0);
const callFunc = resourceBuilder.getCall(resource, operation);
if (!callFunc) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `未实现方法: ${resource}.${operation}`);
}
const batchConfig = getBatchConfig(this);
if (batchConfig.enabled) {
return executeParallel(this, items, callFunc, batchConfig, resource, operation);
}
return executeSerial(this, items, callFunc, resource, operation);
}
}
exports.FeishuProject = FeishuProject;
//# sourceMappingURL=FeishuProject.node.js.map