UNPKG

@luka-cat-mimi/n8n-nodes-feishu-project

Version:
313 lines 15 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.FeishuProjectTrigger = void 0; const crypto_1 = require("crypto"); const n8n_workflow_1 = require("n8n-workflow"); const GenericFunctions_1 = require("../FeishuProject/GenericFunctions"); const EVENT_OPTIONS = [ { name: '所有事件 (*)', value: '*' }, { name: '创建工作项 (1001)', value: '1001' }, { name: '删除工作项 (1002)', value: '1002' }, { name: '恢复工作项 (1004)', value: '1004' }, { name: '终止工作项 (1005)', value: '1005' }, { name: '模版升级 (1006)', value: '1006' }, { name: '修改字段 (1009)', value: '1009' }, { name: '完成节点 (2001)', value: '2001' }, { name: '删除节点 (2002)', value: '2002' }, { name: '恢复节点 (2003)', value: '2003' }, { name: '回滚节点 (2004)', value: '2004' }, { name: '修改估分与排期 (2005)', value: '2005' }, { name: '修改节点负责人 (2006)', value: '2006' }, { name: '修改实际工时 (2007)', value: '2007' }, { name: '多人确认节点流转事件 (2009)', value: '2009' }, { name: '状态变更 (3001)', value: '3001' }, { name: '新建子任务 (4001)', value: '4001' }, { name: '删除子任务 (4002)', value: '4002' }, { name: '完成子任务 (4003)', value: '4003' }, { name: '取消完成子任务 (4004)', value: '4004' }, { name: '修改子任务负责人 (4005)', value: '4005' }, { name: '修改子任务估分 (4006)', value: '4006' }, { name: '修改子任务排期 (4007)', value: '4007' }, { name: '修改子任务实际工时 (4008)', value: '4008' }, { name: '修改子任务字段 (4009)', value: '4009' }, { name: '添加空间成员 (5001)', value: '5001' }, { name: '删除空间成员 (5002)', value: '5002' }, { name: '完成工作移交 (5003)', value: '5003' }, { name: '拒绝工作移交 (5004)', value: '5004' }, { name: '插件安装 (6001)', value: '6001' }, { name: '插件卸载 (6002)', value: '6002' }, { name: '插件更新 (6003)', value: '6003' }, { name: '插件购买 (6004)', value: '6004' }, ]; function parseAllowedList(value) { let list = []; if (Array.isArray(value)) { list = value.map((item) => String(item).trim()); } else if (typeof value === 'string') { const trimmed = value.trim(); if (trimmed !== '') { if (trimmed.startsWith('[')) { try { const parsed = JSON.parse(trimmed); if (Array.isArray(parsed)) { list = parsed.map((item) => String(item).trim()); } } catch { list = trimmed.split(',').map((item) => item.trim()); } } else { list = trimmed.split(',').map((item) => item.trim()); } } } list = list.filter((item) => item !== ''); if (list.includes('*')) { return ['*']; } return list; } class FeishuProjectTrigger { constructor() { this.description = { displayName: '飞书项目 Trigger', name: 'feishuProjectTrigger', icon: 'file:icon.svg', group: ['trigger'], version: [1], defaultVersion: 1, description: '当飞书项目发生指定事件时,通过 Webhook 回调触发工作流', defaults: { name: '飞书项目 Trigger', }, inputs: [], outputs: [n8n_workflow_1.NodeConnectionTypes.Main], credentials: [ { name: "feishuProjectApi", required: true, }, ], webhooks: [ { name: 'default', httpMethod: 'POST', responseMode: 'onReceived', path: 'webhook', }, ], properties: [ { displayName: '将上方生成的 Webhook URL 填入飞书项目对应的回调地址中:', name: 'noticeTitle', type: 'notice', default: '', }, { displayName: '1. 工作项 / 节点 / 状态 / 子任务 / 空间等业务事件(100~500):开发者后台 → 插件功能 → 插件构成 添加构成 → 监听事件', name: 'noticeBusiness', type: 'notice', default: '', }, { displayName: '2. 插件安装 / 卸载 / 更新 / 购买等插件事件(600):开发者后台 → 插件发布 → 插件事件订阅', name: 'noticePlugin', type: 'notice', default: '', }, { displayName: '监听事件', name: 'events', type: 'multiOptions', required: true, default: ['*'], description: '选择需要监听的事件,选择「所有事件」时将接收全部回调', options: EVENT_OPTIONS, }, { displayName: '验证签名', name: 'verifySignature', type: 'boolean', default: false, description: 'Whether to verify the signature in the callback request to ensure it comes from Feishu Project', }, { displayName: '回调 Token', name: 'callbackToken', type: 'string', typeOptions: { password: true, }, default: '', description: '飞书项目「监听事件配置」中的回调 Token,用于签名校验。可在开发者后台插件构成页面查看。', displayOptions: { show: { verifySignature: [true], }, }, }, { displayName: '选项', name: 'options', type: 'collection', placeholder: '添加选项', default: {}, options: [ { displayName: '允许来源类型', name: 'allowedSources', type: 'multiOptions', default: [], description: '仅放行指定来源类型的回调,留空则不限制来源', options: [ { name: '普通用户操作', value: 'normal', description: '该操作来自普通用户操作', }, { name: 'OpenAPI', value: 'openapi', description: '该操作来自于 OpenAPI', }, { name: '系统行为', value: 'system', description: '该操作来自于系统行为', }, { name: '自动化', value: 'automation', description: '该操作来自自动化', }, ], }, { displayName: '允许飞书项目空间 Names or IDs', name: 'allowedSpaces', type: 'multiOptions', typeOptions: { loadOptionsMethod: 'loadSpacesWithWildcard', }, default: ['*'], description: '仅放行指定飞书项目空间的回调,默认通配符表示不限制空间。使用 data.project_key 进行匹配。. Choose from the list, or specify IDs using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.', }, { displayName: '允许的工作项类型', name: 'allowedWorkItemTypes', type: 'string', default: '*', description: '仅放行指定工作项类型的回调,默认通配符 * 表示全部允许。支持逗号分隔、JSON 数组或表达式数组,例如 ["issue","story"]、issue,story 或 {{ ["issue","story"] }}。使用 data.work_item_type_key 进行匹配。', }, { displayName: '允许的工作项字段', name: 'allowedWorkItemFields', type: 'string', default: '*', description: '仅放行指定工作项字段的回调,默认通配符 * 表示全部允许。仅在「修改字段 (1009)」事件下生效,其它事件不做该过滤。支持逗号分隔、JSON 数组或表达式数组,例如 ["name","role_owners","watchers","field_36d783","is_frozen"]、name,role_owners 或 {{ ["name","is_frozen"] }}。使用 data.field_info[].field_key 进行匹配,只要有任一变更字段命中即放行。', }, ], }, ], }; this.methods = { loadOptions: { async loadSpacesWithWildcard() { const wildcard = { name: '所有空间 (*)', value: '*', }; try { const spaceIds = await GenericFunctions_1.getSpaceList.call(this); if (!spaceIds || spaceIds.length === 0) { return [wildcard]; } const spaceDetails = await GenericFunctions_1.getSpaceDetails.call(this, spaceIds); return [ wildcard, ...spaceDetails.map((space) => ({ name: `${space.name} (${space.project_key})`, value: space.project_key, })), ]; } catch { return [wildcard]; } }, }, }; } async webhook() { var _a, _b, _c, _d, _e; const body = this.getBodyData(); const res = this.getResponseObject(); const events = this.getNodeParameter('events', []); const verifySignature = this.getNodeParameter('verifySignature', false); const options = this.getNodeParameter('options', {}); if (verifySignature) { const callbackToken = this.getNodeParameter('callbackToken', ''); const credentials = await this.getCredentials("feishuProjectApi"); const pluginId = (_a = credentials.pluginId) !== null && _a !== void 0 ? _a : ''; const requestTime = (_b = body.request_time) !== null && _b !== void 0 ? _b : ''; const expectedSignature = (0, crypto_1.createHash)('sha256') .update(`${pluginId}${requestTime}${callbackToken}`, 'utf-8') .digest('hex'); if (expectedSignature !== body.signature) { res.status(401).json({ error: 'Invalid signature' }); return { noWebhookResponse: true }; } } const allowedSources = (_c = options.allowedSources) !== null && _c !== void 0 ? _c : []; if (allowedSources.length > 0 && body.source !== undefined && !allowedSources.includes(String(body.source))) { res.status(200).json({ code: 0, msg: 'ignored' }); return { noWebhookResponse: true }; } const data = (_d = body.data) !== null && _d !== void 0 ? _d : {}; const eventType = data.event_type; const isWildcard = events.includes('*'); if (!isWildcard && eventType !== undefined && !events.includes(String(eventType))) { res.status(200).json({ code: 0, msg: 'ignored' }); return { noWebhookResponse: true }; } const allowedSpaces = (_e = options.allowedSpaces) !== null && _e !== void 0 ? _e : []; if (allowedSpaces.length > 0 && !allowedSpaces.includes('*') && data.project_key !== undefined && !allowedSpaces.includes(String(data.project_key))) { res.status(200).json({ code: 0, msg: 'ignored' }); return { noWebhookResponse: true }; } const allowedWorkItemTypes = parseAllowedList(options.allowedWorkItemTypes); if (allowedWorkItemTypes.length > 0 && !allowedWorkItemTypes.includes('*') && data.work_item_type_key !== undefined && !allowedWorkItemTypes.includes(String(data.work_item_type_key))) { res.status(200).json({ code: 0, msg: 'ignored' }); return { noWebhookResponse: true }; } if (String(eventType) === '1009') { const allowedWorkItemFields = parseAllowedList(options.allowedWorkItemFields); const fieldInfo = Array.isArray(data.field_info) ? data.field_info : []; const changedFieldKeys = fieldInfo .map((item) => (item && item.field_key !== undefined ? String(item.field_key) : '')) .filter((key) => key !== ''); if (allowedWorkItemFields.length > 0 && !allowedWorkItemFields.includes('*') && changedFieldKeys.length > 0 && !changedFieldKeys.some((key) => allowedWorkItemFields.includes(key))) { res.status(200).json({ code: 0, msg: 'ignored' }); return { noWebhookResponse: true }; } } return { workflowData: [this.helpers.returnJsonArray(body)], }; } } exports.FeishuProjectTrigger = FeishuProjectTrigger; //# sourceMappingURL=FeishuProjectTrigger.node.js.map