@luka-cat-mimi/n8n-nodes-feishu-project
Version:
飞书项目 N8N 集成插件,支持附件管理等功能
313 lines • 15 kB
JavaScript
;
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