@lixiongwei/n8n-nodes-feishu 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -40
- package/credentials/FeishuApi.credentials.js +5 -5
- package/nodes/Feishu/Feishu.node.js +37 -43
- package/nodes/Feishu/FeishuTrigger.node.js +10 -11
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,72 +1,66 @@
|
|
|
1
|
-
# n8n-nodes-feishu
|
|
1
|
+
# @lixiongwei/n8n-nodes-feishu
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> Feishu / Lark integration nodes for n8n. Free tier + Pro License. Includes 5 workflow templates.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Install
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
10
|
npm install @lixiongwei/n8n-nodes-feishu
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
n8n 自托管版直接在 n8n 所在目录执行。云版不支持社区节点。
|
|
14
|
-
|
|
15
13
|
---
|
|
16
14
|
|
|
17
|
-
##
|
|
15
|
+
## Free Features 🆓
|
|
18
16
|
|
|
19
|
-
|
|
|
20
|
-
|
|
21
|
-
|
|
|
22
|
-
|
|
|
17
|
+
| Feature | Description |
|
|
18
|
+
|---------|-------------|
|
|
19
|
+
| Send Text Message | Send plain text to users or groups |
|
|
20
|
+
| Webhook Trigger | Receive Feishu event callbacks |
|
|
23
21
|
|
|
24
22
|
---
|
|
25
23
|
|
|
26
|
-
## Pro
|
|
24
|
+
## Pro Features ⭐ (License Key required)
|
|
27
25
|
|
|
28
|
-
|
|
|
29
|
-
|
|
30
|
-
|
|
|
31
|
-
|
|
|
32
|
-
|
|
|
33
|
-
|
|
|
26
|
+
| Feature | Description |
|
|
27
|
+
|---------|-------------|
|
|
28
|
+
| Send Card Message | Rich interactive cards with Markdown |
|
|
29
|
+
| Read Bitable | Query records from a Bitable |
|
|
30
|
+
| Write to Bitable | Insert new records into a Bitable |
|
|
31
|
+
| Get Pending Approvals | List pending approval instances |
|
|
34
32
|
|
|
35
33
|
---
|
|
36
34
|
|
|
37
|
-
##
|
|
38
|
-
|
|
39
|
-
👉 **[在 Gumroad 购买](https://gumroad.com/l/feishu-pro)** — $49 一次性,永久使用。
|
|
35
|
+
## Get a License Key
|
|
40
36
|
|
|
41
|
-
|
|
37
|
+
👉 **[Buy on Gumroad](https://1717465779306.gumroad.com/l/feishu-pro)** — $49 one-time, lifetime license.
|
|
42
38
|
|
|
43
39
|
---
|
|
44
40
|
|
|
45
|
-
##
|
|
41
|
+
## Included Workflow Templates
|
|
46
42
|
|
|
47
|
-
|
|
43
|
+
Import from `workflows/`:
|
|
48
44
|
|
|
49
|
-
|
|
|
50
|
-
|
|
51
|
-
| `template-01-send-message.json` | Webhook →
|
|
52
|
-
| `template-02-cron-reminder.json` |
|
|
53
|
-
| `template-03-website-monitor.json` |
|
|
54
|
-
| `template-04-form-notify.json` |
|
|
55
|
-
| `template-05-api-to-feishu.json` | API
|
|
45
|
+
| Template | Scenario |
|
|
46
|
+
|----------|----------|
|
|
47
|
+
| `template-01-send-message.json` | Webhook → Feishu message |
|
|
48
|
+
| `template-02-cron-reminder.json` | Scheduled → Feishu reminder |
|
|
49
|
+
| `template-03-website-monitor.json` | Website monitor → Feishu alert |
|
|
50
|
+
| `template-04-form-notify.json` | Form submission → Card notification |
|
|
51
|
+
| `template-05-api-to-feishu.json` | API data → Feishu push |
|
|
56
52
|
|
|
57
53
|
---
|
|
58
54
|
|
|
59
|
-
##
|
|
55
|
+
## Feishu App Setup
|
|
60
56
|
|
|
61
|
-
1.
|
|
62
|
-
2.
|
|
63
|
-
3.
|
|
64
|
-
4.
|
|
65
|
-
5.
|
|
57
|
+
1. Create an **Enterprise Internal App** at [Feishu Developer Console](https://open.feishu.cn/)
|
|
58
|
+
2. Enable **Bot** capability
|
|
59
|
+
3. Add permissions: `im:message:send_as_bot`
|
|
60
|
+
4. Publish the app
|
|
61
|
+
5. In n8n credentials, fill in App ID + App Secret + License Key
|
|
66
62
|
|
|
67
63
|
---
|
|
68
64
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
- n8n 版本要求:1.x+
|
|
72
|
-
- 飞书 API 版本:v3
|
|
65
|
+
- n8n version: 1.x+
|
|
66
|
+
- Feishu API version: v3
|
|
@@ -4,7 +4,7 @@ exports.FeishuApi = void 0;
|
|
|
4
4
|
class FeishuApi {
|
|
5
5
|
constructor() {
|
|
6
6
|
this.name = 'feishuApi';
|
|
7
|
-
this.displayName = '
|
|
7
|
+
this.displayName = 'Feishu (Lark) API';
|
|
8
8
|
this.documentationUrl = 'https://open.feishu.cn/document/home/getting-started';
|
|
9
9
|
this.properties = [
|
|
10
10
|
{
|
|
@@ -12,7 +12,7 @@ class FeishuApi {
|
|
|
12
12
|
name: 'appId',
|
|
13
13
|
type: 'string',
|
|
14
14
|
default: '',
|
|
15
|
-
description: '
|
|
15
|
+
description: 'Feishu App ID (from Developer Console → Credentials)',
|
|
16
16
|
required: true,
|
|
17
17
|
},
|
|
18
18
|
{
|
|
@@ -21,15 +21,15 @@ class FeishuApi {
|
|
|
21
21
|
type: 'string',
|
|
22
22
|
typeOptions: { password: true },
|
|
23
23
|
default: '',
|
|
24
|
-
description: '
|
|
24
|
+
description: 'Feishu App Secret',
|
|
25
25
|
required: true,
|
|
26
26
|
},
|
|
27
27
|
{
|
|
28
|
-
displayName: 'License Key',
|
|
28
|
+
displayName: 'License Key (optional)',
|
|
29
29
|
name: 'licenseKey',
|
|
30
30
|
type: 'string',
|
|
31
31
|
default: '',
|
|
32
|
-
description: 'Pro License Key
|
|
32
|
+
description: 'Pro License Key from Gumroad. Leave empty for free features only. Get one at: https://1717465779306.gumroad.com/l/feishu-pro',
|
|
33
33
|
required: false,
|
|
34
34
|
},
|
|
35
35
|
];
|
|
@@ -6,7 +6,7 @@ const license_1 = require("./license");
|
|
|
6
6
|
// --- Operation definitions ---
|
|
7
7
|
const FREE_OPERATIONS = ['sendTextMessage'];
|
|
8
8
|
const PRO_OPERATIONS = ['sendCardMessage', 'readBitable', 'writeBitable', 'getApprovals'];
|
|
9
|
-
// --- Token cache
|
|
9
|
+
// --- Token cache ---
|
|
10
10
|
let cachedToken = null;
|
|
11
11
|
let tokenExpiry = 0;
|
|
12
12
|
async function getFeishuToken(appId, appSecret) {
|
|
@@ -21,7 +21,7 @@ async function getFeishuToken(appId, appSecret) {
|
|
|
21
21
|
json: true,
|
|
22
22
|
});
|
|
23
23
|
if (response.code !== 0) {
|
|
24
|
-
throw new n8n_workflow_1.NodeOperationError(this.getNode(),
|
|
24
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Feishu token failed: ${response.msg} (code: ${response.code})`);
|
|
25
25
|
}
|
|
26
26
|
cachedToken = response.tenant_access_token;
|
|
27
27
|
tokenExpiry = Date.now() + (response.expire - 300) * 1000;
|
|
@@ -31,98 +31,98 @@ async function getFeishuToken(appId, appSecret) {
|
|
|
31
31
|
class Feishu {
|
|
32
32
|
constructor() {
|
|
33
33
|
this.description = {
|
|
34
|
-
displayName: '
|
|
34
|
+
displayName: 'Feishu / Lark',
|
|
35
35
|
name: 'feishu',
|
|
36
36
|
icon: 'file:feishu.svg',
|
|
37
37
|
group: ['transform'],
|
|
38
38
|
version: 1,
|
|
39
39
|
subtitle: '={{ $parameter["operation"] }}',
|
|
40
|
-
description: '
|
|
41
|
-
defaults: { name: '
|
|
40
|
+
description: 'Feishu/Lark integration: send messages, manage Bitable, process approvals',
|
|
41
|
+
defaults: { name: 'Feishu / Lark' },
|
|
42
42
|
inputs: ["main"],
|
|
43
43
|
outputs: ["main"],
|
|
44
44
|
credentials: [{ name: 'feishuApi', required: true }],
|
|
45
45
|
properties: [
|
|
46
46
|
// --- Operation selector ---
|
|
47
47
|
{
|
|
48
|
-
displayName: '
|
|
48
|
+
displayName: 'Operation',
|
|
49
49
|
name: 'operation',
|
|
50
50
|
type: 'options',
|
|
51
51
|
noDataExpression: true,
|
|
52
52
|
options: [
|
|
53
|
-
{ name: '🆓
|
|
54
|
-
{ name: '⭐
|
|
55
|
-
{ name: '⭐
|
|
56
|
-
{ name: '⭐
|
|
57
|
-
{ name: '⭐
|
|
53
|
+
{ name: '🆓 Send Text Message (Free)', value: 'sendTextMessage' },
|
|
54
|
+
{ name: '⭐ Send Card Message (Pro)', value: 'sendCardMessage' },
|
|
55
|
+
{ name: '⭐ Read Bitable (Pro)', value: 'readBitable' },
|
|
56
|
+
{ name: '⭐ Write to Bitable (Pro)', value: 'writeBitable' },
|
|
57
|
+
{ name: '⭐ Get Pending Approvals (Pro)', value: 'getApprovals' },
|
|
58
58
|
],
|
|
59
59
|
default: 'sendTextMessage',
|
|
60
|
-
description: 'Pro
|
|
60
|
+
description: 'Pro features require a License Key',
|
|
61
61
|
},
|
|
62
62
|
// --- Send Text Message (Free) ---
|
|
63
63
|
{
|
|
64
|
-
displayName: '
|
|
64
|
+
displayName: 'Recipient ID Type',
|
|
65
65
|
name: 'receiveIdType',
|
|
66
66
|
type: 'options',
|
|
67
67
|
options: [
|
|
68
|
-
{ name: '
|
|
69
|
-
{ name: '
|
|
70
|
-
{ name: '
|
|
71
|
-
{ name: '
|
|
68
|
+
{ name: 'Open ID', value: 'open_id' },
|
|
69
|
+
{ name: 'User ID', value: 'user_id' },
|
|
70
|
+
{ name: 'Email', value: 'email' },
|
|
71
|
+
{ name: 'Chat ID', value: 'chat_id' },
|
|
72
72
|
],
|
|
73
73
|
default: 'open_id',
|
|
74
74
|
displayOptions: { show: { operation: ['sendTextMessage', 'sendCardMessage'] } },
|
|
75
75
|
},
|
|
76
76
|
{
|
|
77
|
-
displayName: '
|
|
77
|
+
displayName: 'Recipient',
|
|
78
78
|
name: 'receiveId',
|
|
79
79
|
type: 'string',
|
|
80
80
|
default: '',
|
|
81
81
|
required: true,
|
|
82
82
|
displayOptions: { show: { operation: ['sendTextMessage', 'sendCardMessage'] } },
|
|
83
|
-
placeholder: 'ou_xxx
|
|
84
|
-
description: '
|
|
83
|
+
placeholder: 'ou_xxx or chat_xxx or email@example.com',
|
|
84
|
+
description: 'Recipient open_id, user_id, email, or chat_id',
|
|
85
85
|
},
|
|
86
86
|
{
|
|
87
|
-
displayName: '
|
|
87
|
+
displayName: 'Message Content',
|
|
88
88
|
name: 'textContent',
|
|
89
89
|
type: 'string',
|
|
90
90
|
typeOptions: { rows: 4 },
|
|
91
91
|
default: '',
|
|
92
92
|
required: true,
|
|
93
93
|
displayOptions: { show: { operation: ['sendTextMessage'] } },
|
|
94
|
-
placeholder: '
|
|
94
|
+
placeholder: 'Type your message...',
|
|
95
95
|
},
|
|
96
96
|
// --- Send Card Message (Pro) ---
|
|
97
97
|
{
|
|
98
|
-
displayName: '
|
|
98
|
+
displayName: 'Card Title',
|
|
99
99
|
name: 'cardTitle',
|
|
100
100
|
type: 'string',
|
|
101
|
-
default: '📢
|
|
101
|
+
default: '📢 Notification',
|
|
102
102
|
displayOptions: { show: { operation: ['sendCardMessage'] } },
|
|
103
103
|
},
|
|
104
104
|
{
|
|
105
|
-
displayName: '
|
|
105
|
+
displayName: 'Card Body (Markdown supported)',
|
|
106
106
|
name: 'cardContent',
|
|
107
107
|
type: 'string',
|
|
108
108
|
typeOptions: { rows: 6 },
|
|
109
109
|
default: '',
|
|
110
110
|
required: true,
|
|
111
111
|
displayOptions: { show: { operation: ['sendCardMessage'] } },
|
|
112
|
-
placeholder: '
|
|
112
|
+
placeholder: '**Title**\nContent...',
|
|
113
113
|
},
|
|
114
114
|
// --- Read Bitable (Pro) ---
|
|
115
115
|
{
|
|
116
|
-
displayName: '
|
|
116
|
+
displayName: 'Bitable App Token',
|
|
117
117
|
name: 'bitableAppToken',
|
|
118
118
|
type: 'string',
|
|
119
119
|
default: '',
|
|
120
120
|
required: true,
|
|
121
121
|
displayOptions: { show: { operation: ['readBitable', 'writeBitable'] } },
|
|
122
|
-
description: '
|
|
122
|
+
description: 'The app token from your Bitable URL (the XXX in /base/XXX)',
|
|
123
123
|
},
|
|
124
124
|
{
|
|
125
|
-
displayName: '
|
|
125
|
+
displayName: 'Table ID',
|
|
126
126
|
name: 'tableId',
|
|
127
127
|
type: 'string',
|
|
128
128
|
default: '',
|
|
@@ -132,13 +132,13 @@ class Feishu {
|
|
|
132
132
|
},
|
|
133
133
|
// --- Write Bitable (Pro) ---
|
|
134
134
|
{
|
|
135
|
-
displayName: '
|
|
135
|
+
displayName: 'Fields (JSON)',
|
|
136
136
|
name: 'bitableFields',
|
|
137
137
|
type: 'json',
|
|
138
|
-
default: '{"
|
|
138
|
+
default: '{"Field Name": "Value"}',
|
|
139
139
|
required: true,
|
|
140
140
|
displayOptions: { show: { operation: ['writeBitable'] } },
|
|
141
|
-
description: '
|
|
141
|
+
description: 'Fields and values to write, in JSON format',
|
|
142
142
|
},
|
|
143
143
|
],
|
|
144
144
|
};
|
|
@@ -147,7 +147,6 @@ class Feishu {
|
|
|
147
147
|
async execute() {
|
|
148
148
|
const items = this.getInputData();
|
|
149
149
|
const returnData = [];
|
|
150
|
-
// Get credentials
|
|
151
150
|
const credentials = await this.getCredentials('feishuApi');
|
|
152
151
|
const appId = credentials.appId;
|
|
153
152
|
const appSecret = credentials.appSecret;
|
|
@@ -155,21 +154,18 @@ class Feishu {
|
|
|
155
154
|
for (let i = 0; i < items.length; i++) {
|
|
156
155
|
try {
|
|
157
156
|
const operation = this.getNodeParameter('operation', i);
|
|
158
|
-
//
|
|
157
|
+
// License check for Pro operations
|
|
159
158
|
if (PRO_OPERATIONS.includes(operation)) {
|
|
160
159
|
if (!licenseKey) {
|
|
161
|
-
throw new n8n_workflow_1.NodeOperationError(this.getNode(), '⭐
|
|
162
|
-
'👉
|
|
163
|
-
'👉 获取免费 Key 试用 7 天:https://gumroad.com/l/feishu-pro');
|
|
160
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), '⭐ This is a Pro feature. Please add a License Key in your credentials.\n' +
|
|
161
|
+
'👉 Get a License Key: https://1717465779306.gumroad.com/l/feishu-pro');
|
|
164
162
|
}
|
|
165
163
|
const validation = await license_1.validateLicense.call(this, licenseKey, appId);
|
|
166
164
|
if (!validation.valid) {
|
|
167
|
-
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `License Key
|
|
165
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `License Key invalid or expired: ${validation.reason}`);
|
|
168
166
|
}
|
|
169
167
|
}
|
|
170
|
-
// Get token
|
|
171
168
|
const token = await getFeishuToken.call(this, appId, appSecret);
|
|
172
|
-
// Execute operation
|
|
173
169
|
switch (operation) {
|
|
174
170
|
case 'sendTextMessage': {
|
|
175
171
|
const receiveIdType = this.getNodeParameter('receiveIdType', i);
|
|
@@ -278,9 +274,7 @@ class Feishu {
|
|
|
278
274
|
returnData.push({ json: { error: error.message }, error });
|
|
279
275
|
}
|
|
280
276
|
else {
|
|
281
|
-
returnData.push({
|
|
282
|
-
json: { error: error.message || 'Unknown error' },
|
|
283
|
-
});
|
|
277
|
+
returnData.push({ json: { error: error.message || 'Unknown error' } });
|
|
284
278
|
}
|
|
285
279
|
}
|
|
286
280
|
}
|
|
@@ -4,14 +4,14 @@ exports.FeishuTrigger = void 0;
|
|
|
4
4
|
class FeishuTrigger {
|
|
5
5
|
constructor() {
|
|
6
6
|
this.description = {
|
|
7
|
-
displayName: '
|
|
7
|
+
displayName: 'Feishu / Lark Trigger',
|
|
8
8
|
name: 'feishuTrigger',
|
|
9
9
|
icon: 'file:feishu.svg',
|
|
10
10
|
group: ['trigger'],
|
|
11
11
|
version: 1,
|
|
12
12
|
subtitle: '={{ $parameter["event"] }}',
|
|
13
|
-
description: '
|
|
14
|
-
defaults: { name: '
|
|
13
|
+
description: 'Listen for Feishu events: message received, approval status changed',
|
|
14
|
+
defaults: { name: 'Feishu / Lark Trigger' },
|
|
15
15
|
inputs: [],
|
|
16
16
|
outputs: ["main"],
|
|
17
17
|
credentials: [{ name: 'feishuApi', required: true }],
|
|
@@ -25,22 +25,22 @@ class FeishuTrigger {
|
|
|
25
25
|
],
|
|
26
26
|
properties: [
|
|
27
27
|
{
|
|
28
|
-
displayName: '
|
|
28
|
+
displayName: 'Event Type',
|
|
29
29
|
name: 'event',
|
|
30
30
|
type: 'options',
|
|
31
31
|
options: [
|
|
32
|
-
{ name: '🆓
|
|
33
|
-
{ name: '⭐
|
|
32
|
+
{ name: '🆓 Message Received (Free)', value: 'message_receive' },
|
|
33
|
+
{ name: '⭐ Approval Status Changed (Pro)', value: 'approval_change' },
|
|
34
34
|
],
|
|
35
35
|
default: 'message_receive',
|
|
36
|
-
description: 'Pro
|
|
36
|
+
description: 'Pro events require a License Key',
|
|
37
37
|
},
|
|
38
38
|
{
|
|
39
|
-
displayName: '
|
|
39
|
+
displayName: 'Verification Token',
|
|
40
40
|
name: 'verificationToken',
|
|
41
41
|
type: 'string',
|
|
42
42
|
default: '',
|
|
43
|
-
description: '
|
|
43
|
+
description: 'Feishu Event Subscription Verification Token',
|
|
44
44
|
},
|
|
45
45
|
],
|
|
46
46
|
};
|
|
@@ -48,14 +48,13 @@ class FeishuTrigger {
|
|
|
48
48
|
async webhook() {
|
|
49
49
|
const req = this.getRequestObject();
|
|
50
50
|
const body = req.body;
|
|
51
|
-
//
|
|
51
|
+
// Feishu URL verification challenge
|
|
52
52
|
if (body?.type === 'url_verification') {
|
|
53
53
|
return {
|
|
54
54
|
webhookResponse: { challenge: body.challenge },
|
|
55
55
|
workflowData: [],
|
|
56
56
|
};
|
|
57
57
|
}
|
|
58
|
-
// Handle event
|
|
59
58
|
const event = body?.event || {};
|
|
60
59
|
const header = body?.header || {};
|
|
61
60
|
return {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lixiongwei/n8n-nodes-feishu",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "Feishu/Lark nodes for n8n — send messages, manage Bitables, process approvals. Free tier + Pro license.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"build": "tsc",
|