Feishu Open Platform provides a series of atomic APIs on the server side to realize diversified functions, but the actual coding process is not very smooth: when using these APIs to complete operation, a lot of extra work needs to be considered, such as token acquisition and maintenance, data encryption and decryption, request signature verification, etc.; in the actual coding process, the semantics of function calls are missing, cause mental burden.
All of these make the overall development experience poor. In order to make the open capability easy to use, we have written this SDK, which integrates all the verbose logic into built-in processing, provides a complete type hints, and provides external semantics. Programming interface to improve coding experience.
Here are some official tutorials based on the sdk:
- Quickly develop interactive cards
- Quick Start of Personnel and Attendance Management System
- Quick access to Base
- Department personnel management based on web app
- Quick access to Contact API
- Automatic attendance management based on approval
-
Development documentation: A reference to the open interface of the open platform, a must-see for developers, you can use the search function to query documents efficiently. More introduction instructions .
-
Developer background: The management background of the developer's development application, more introduction.
-
Enterprise self-built Application: The application can only be installed and used within the enterprise, more introduction.
-
ISV Application: The app will be listed in the ISV Application Display, each enterprise can choose to install, more introduction instructions.
npm
npm install @larksuiteoapi/node-sdk
yarn
yarn add @larksuiteoapi/node-sdk
Provides two versions of ECMAScript and CommonJS, and supports the use of native Javascript and Typescript. The examples are all taking Typescript as an example.
Typescript
import * as lark from '@larksuiteoapi/node-sdk';
CommonJS
const lark = require('@larksuiteoapi/node-sdk');
ECMAScript
import * as lark from '@larksuiteoapi/node-sdk';
The list of all APIs on Feishu Open Platform: click here.
The SDK provides a semantic calling method. You only need to construct a client instance according to the relevant parameters, and then use the semantic method (client.business domain.resource.method) on it to complete the API call, the calling process and the calling result. There are complete types for prompting, such as sending a message to a group chat:
import * as lark from '@larksuiteoapi/node-sdk';
const client = new lark. Client({
appId: 'app id',
appSecret: 'app secret',
appType: lark.AppType.SelfBuild,
domain: lark.Domain.Feishu,
});
const res = await client.im.message.create({
params: {
receive_id_type: 'chat_id',
},
data: {
receive_id: 'receive_id',
content: JSON.stringify({text: 'hello world'}),
msg_type: 'text',
},
});
tips:
- If you want to debug an API, you can click the link in the comment to enter the API debugging platform for debugging:
- How to obtain the semantic call interface:Click here
For self-built applications, you can use the following code to create a client:
import * as lark from '@larksuiteoapi/node-sdk';
const client = new lark. Client({
appId: 'app id',
appSecret: 'app secret'
});
For store apps, the specified appType that needs to be displayed is lark.AppType.ISV:
import * as lark from '@larksuiteoapi/node-sdk';
const client = new lark.Client({
appId: 'app id',
appSecret: 'app secret',
appType: lark.AppType.ISV,
});
When using the client of the created store application to initiate an API call, you need to manually pass the tenant_key, you can Use lark.withTenantKey
to do it:
client.im.message.create({
params: {
receive_id_type: 'chat_id',
},
data: {
receive_id: 'chat_id',
content: JSON.stringify({text: 'hello world'}),
msg_type: 'text'
},
}, lark.withTenantKey('tenant key'));
Parameter | Description | Type | Required | Default |
---|---|---|---|---|
appId | app id | string | yes | - |
appSecret | app secret | string | yes | - |
domain | The domain of the application, divided into Feishu (https://open.feishu.cn), lark (https://open.larksuite.com), others (the complete domain name needs to be passed) | Domain | string | no | Domain.Feishu |
httpInstance | The http instance of the sdk sending request。By default, the sdk uses axios.create() to construct a defaultHttpInstance to make http calls. | HttpInstance | No | defaultHttpInstance。It can be imported from sdk and add interceptors on it. |
loggerLevel | Log Level | LoggerLevel | No | info |
logger | - | Logger | No | - |
cache | Cache | Cache | No | - |
disableTokenCache | Whether to disable the cache, if disabled, the token will not be cached, and it will be re-pulled every time it needs to be used | boolean | No | false |
appType | The type of application, divided into store application or self-built application | AppType | No | AppType.SelfBuild |
helpDeskId | helpdesk id | string | no | - |
helpDeskToken | helpdesk token | string | no | - |
For the interface whose return value is presented in the form of pagination, it provides iterator encapsulation (the method name suffix is WithIterator), which improves the usability and eliminates the tedious operation of repeatedly obtaining data according to page_token, such as obtaining the user list:
// Process 20 pieces of data each time
for await (const items of await client.contact.user.listWithIterator({
params: {
department_id: '0',
page_size: 20,
},
})) {
console.log(items);
}
// You can also use next to manually control the iteration, fetching 20 pieces of data each time
const listIterator = await SDKClient.contact.user.listWithIterator({
params: {
department_id: '0',
page_size: 20,
},
});
const { value } = await listIterator[Symbol.asyncIterator]().next();
console.log(value);
- Of course, you can also use the version without iterator encapsulation. In this case, you need to manually perform paging calls each time according to the returned page_token. *
In the same way as calling ordinary API, you can pass the parameters according to the type prompt, and the processing of file upload is encapsulated inside, such as:
const res = await client.im.file.create({
data: {
file_type: 'mp4',
file_name: 'test.mp4',
file: fs.readFileSync('file path'),
},
});
The returned binary stream is encapsulated, eliminating the processing of the stream itself, just call the writeFile method to write the data to the file, such as:
const resp = await client.im.file.get({
path: {
file_key: 'file key',
},
});
await resp.writeFile(`filepath.suffix`);
If you want to customize the processing of the stream, you can call the getReadableStream method to obtain the stream, such as writing the stream to a file:
import * as fs from 'fs';
const resp = await client.im.file.get({
path: {
file_key: 'file key',
},
});
const readableStream = resp.getReadableStream();
const writableStream = fs.createWriteStream('file url');
readableStream.pipe(writableStream);
Note: The stream can only be consumed once, if writeFile is used to consume the stream, getReadableStream will report an error when obtaining the stream/the obtained stream is empty; If you need to consume multiple streams, you can use getReadableStream to obtain the stream, then read the data in the stream for caching, and use the cached data to the consumer.
Some old versions of the open interface cannot generate corresponding semantic calling methods, and you need to use the request method on the client to make manual calls:
import * as lark from '@larksuiteoapi/node-sdk';
const client = new lark. Client({
appId: 'app id',
appSecret: 'app secret',
appType: lark.AppType.SelfBuild,
domain: lark.Domain.Feishu,
});
const res = await client. request({
method: 'POST',
url: 'xxx',
data: {},
params: {},
});
When sending message card, it will first be in the message card builder to build a message card template, get the generated template json, replace the content-related parts with data, and use the result as a parameter to support the message card api. Such as sending a simple message card with title
and content
:
client.im.message.create({
params: {
receive_id_type: 'chat_id',
},
data: {
receive_id: 'your receive_id',
content: JSON.stringify({
"config": {
"wide_screen_mode": true
},
"elements": [
{
"tag": "markdown",
"content": "Card Content"
}
],
"header": {
"template": "blue",
"title": {
"content": "Card Title",
"tag": "plain_text"
}
}
}
),
msg_type: 'interactive'
}
})
There will be a problem: If the content of the message card is relatively rich, the generated template json is relatively large, and there will be more content that needs to be filled with data, and manual maintenance is more cumbersome. To solve this problem, The Open-Platform provides the ability of Template Message.When sending a message card, you only need to provide the template id and the data content of the template. The sdk encapsulates this ability in terms of calling, and the interface that supports message cards will synchronously add a ByCard calling method, only need to pass template_id
and template_variable
. The above call can be rewritten as:
client.im.message.createByCard({
params: {
receive_id_type: 'chat_id',
},
data: {
receive_id: 'your receive_id',
template_id: 'your template_id',
template_variable: {
content: "Card Content",
title: "Card Title"
}
}
});
If you want to quickly experience Message Card, you can use a basic card built into the sdk:
import * as lark from '@larksuiteoapi/node-sdk';
client.im.message.create({
params: {
receive_id_type: 'chat_id',
},
data: {
receive_id: 'your receive_id',
content: lark.messageCard.defaultCard({
title: 'Card Title',
content: 'Card Content'
}),
msg_type: 'interactive'
}
})
If you want to modify the parameters of the request during the API call, such as carrying some headers, custom tenantToken, etc., you can use the second parameter of the request method to modify:
await client.im.message.create({
params: {
receive_id_type: 'chat_id',
},
data: {
receive_id: 'receive_id',
content: JSON.stringify({text: 'hello world'}),
msg_type: 'text',
},
}, {
headers: {
customizedHeaderKey: 'customizedHeaderValue'
}
});
The SDK also encapsulates commonly used modification operations into methods, which can be used:
Method | Description |
---|---|
withTenantKey | Set tenant key |
withTenantToken | Set tenant token |
withHelpDeskCredential | Whether to bring in the Service Desk token |
withUserAccessToken | Set access token |
withAll | Combines the results of the above methods |
await client.im.message.create({
params: {
receive_id_type: 'chat_id',
},
data: {
receive_id: 'receive_id',
content: JSON.stringify({text: 'hello world'}),
msg_type: 'text',
},
}, lark.withTenantToken('tenant token'));
await client.im.message.create({
params: {
receive_id_type: 'chat_id',
},
data: {
receive_id: 'receive_id',
content: JSON.stringify({text: 'hello world'}),
msg_type: 'text',
},
}, lark.withAll([
lark.withTenantToken('tenant token'),
lark.withTenantKey('tenant key')
]));
For a list of all events opened on Feishu Open Platform, please click here.
For the event processing scenario, we care about is only what kind of events to listen for, and what we do after the event occurs. other work such as data decryption we don't want to care about. The SDK provides an intuitive way to describe this part of the logic:
- Construct an instance of the event handler
EventDispatcher
; - Register the events to be monitored and their handler functions on the instance;
- Bind the instance to the service;
EventDispatcher
will perform operations such as data decryption internally. If no relevant parameters are passed, it will be automatically ignored.
import http from 'http';
import * as lark from '@larksuiteoapi/node-sdk';
const eventDispatcher = new lark.EventDispatcher({
encryptKey: 'encrypt key'
}).register({
'im.message.receive_v1': async (data) => {
const chatId = data.message.chat_id;
const res = await client.im.message.create({
params: {
receive_id_type: 'chat_id',
},
data: {
receive_id: chatId,
content: JSON.stringify({text: 'hello world'}),
msg_type: 'text'
},
});
return res;
}
});
const server = http.createServer();
server.on('request', lark.adaptDefault('/webhook/event', eventDispatcher));
server.listen(3000);
Parameter | Description | Type | Required | Default |
---|---|---|---|---|
encryptKey | Push data encryption key, required when enabling encrypted push use for data decryption | string | no | - |
loggerLevel | log level | LoggerLevel | no | lark.LoggerLevel.info |
logger | - | Logger | No | - |
cache | Cache | Cache | No | - |
Note: Some events are v1.0 version and are no longer maintained. The SDK retains support for them. It is strongly recommended to use new versions of events that are consistent with their functions. Move the mouse to the corresponding event subscription function to see the relevant documents:
The SDK provides an adapter for experss to convert eventDispatcher into express middleware, which can be seamlessly combined with services written using express (The use of bodyParser in the example is not necessary, but the community mostly uses it to format body data):
import * as lark from '@larksuiteoapi/node-sdk';
import express from 'express';
import bodyParser from 'body-parser';
const server = express();
server.use(bodyParser.json());
const eventDispatcher = new lark.EventDispatcher({
encryptKey: 'encryptKey',
}).register({
'im.message.receive_v1': async (data) => {
const chatId = data.message.chat_id;
const res = await client.im.message.create({
params: {
receive_id_type: 'chat_id',
},
data: {
receive_id: chatId,
content: JSON.stringify({text: 'hello world'}),
msg_type: 'text'
},
});
return res;
}
});
server.use('/webhook/event', lark.adaptExpress(eventDispatcher));
server.listen(3000);
The SDK provides an adapter for Koa to convert eventDispatcher into Koa middleware, which can be seamlessly combined with services written using Koa (The use of koa-body in the example is not necessary, but the community mostly uses it to format body data):
import * as lark from '@larksuiteoapi/node-sdk';
import Koa from 'koa';
import koaBody from 'koa-body';
const server = new Koa();
server.use(koaBody());
const eventDispatcher = new lark.EventDispatcher({
encryptKey: 'encryptKey',
}).register({
'im.message.receive_v1': async (data) => {
const open_chat_id = data.message.chat_id;
const res = await client.im.message.create({
params: {
receive_id_type: 'chat_id',
},
data: {
receive_id: open_chat_id,
content: JSON.stringify({text: 'hello world'}),
msg_type: 'text'
},
});
return res;
},
});
server.use(lark.adaptKoa('/webhook/event', eventDispatcher));
server.listen(3000);
When using Koa to write services, in most cases, koa-router is used to process routing, so the SDK also provides adaptations for this situation:
import * as lark from '@larksuiteoapi/node-sdk';
import Koa from 'koa';
import Router from '@koa/router';
import koaBody from 'koa-body';
const server = new Koa();
const router = new Router();
server.use(koaBody());
const eventDispatcher = new lark.EventDispatcher({
encryptKey: 'encryptKey',
}).register({
'im.message.receive_v1': async (data) => {
const open_chat_id = data.message.chat_id;
const res = await client.im.message.create({
params: {
receive_id_type: 'chat_id',
},
data: {
receive_id: open_chat_id,
content: JSON.stringify({text: 'hello world'}),
msg_type: 'text'
},
});
return res;
},
});
router.post('/webhook/event', lark.adaptKoaRouter(eventDispatcher));
server.use(router.routes());
server.listen(3000);
If you want to adapt to services written by other libraries, you currently need to encapsulate the corresponding adapter yourself. Pass the received event data to the invoke method of the instantiated eventDispatcher
for event processing:
const data = server.getData();
const result = await dispatcher.invoke(data);
server.sendResult(result);
When configuring the event request address, The Open Platform will push a POST request in application/json
format to the request address. The POST request is used to verify the validity of the configured request address, and the request body will carry a challenge
field , The application needs to return the received challenge value to the Open Platform within 1 second. See: Document
The adapter provided by the sdk above encapsulates the verification logic. Set the autoChallenge
field in the options parameter to true to enable:
// adaptDefault
lark.adaptDefault('/webhook/event', eventDispatcher, {
autoChallenge: true,
});
// express
lark.adaptExpress(eventDispatcher, {
autoChallenge: true,
});
// koa
lark.adaptKoa('/webhook/event', eventDispatcher, {
autoChallenge: true,
});
// koa-router
router.post(
'/webhook/event',
lark.adaptKoaRouter(eventDispatcher, {
autoChallenge: true,
})
);
Official Documentation:Documentation:
Developers establish a WebSocket full-duplex channel with the open platform by integrating the Lark SDK. When an event callback occurs, the open platform will send messages to the developer through this channel.Compared with the traditional Webhook mode, the long connection mode significantly reduces the access cost, reducing the original development cycle of about 1 week to 5 minutes. The specific advantages are as follows:
- There is no need to use intranet penetration tools during the testing phase. Event callbacks can be received in the local development environment through the long connection mode.
- Authentication is only performed when the connection is established. Subsequent event pushes are all plaintext data, and developers do not need to handle decryption and signature verification logic.
- Only need to ensure that the running environment has the ability to access the public network, no need to provide public IP or domain name.
- There is no need to deploy a firewall and configure a whitelist.
Points to Note
- Similar to Webhook, under the long connection mode, developers need to complete processing within 3 seconds after receiving a message, otherwise, a timeout re-push will be triggered.
- Message pushing is in cluster mode, it does not support broadcasting, that is, if multiple clients are deployed for the same application, only one random client will receive the message.
- Currently, the long connection mode only supports event subscriptions and does not support callback subscriptions
SDK supports this function integration,1.24.0
and later versions are available, sample code:
import * as Lark from '@larksuiteoapi/node-sdk';
const baseConfig = {
appId: 'xxx',
appSecret: 'xxx'
}
const client = new Lark.Client(baseConfig);
const wsClient = new Lark.WSClient({...baseConfig, loggerLevel: Lark.LoggerLevel.info});
wsClient.start({
eventDispatcher: new Lark.EventDispatcher({}).register({
'im.message.receive_v1': async (data) => {
const {
message: { chat_id, content}
} = data;
await client.im.v1.message.create({
params: {
receive_id_type: "chat_id"
},
data: {
receive_id: chat_id,
content: Lark.messageCard.defaultCard({
title: `reply: ${JSON.parse(content).text}`,
content: 'hello'
}),
msg_type: 'interactive'
}
});
}
})
});
The processing of the Message Card is also a kind of Event processing. The only difference between the two is that the processor of the Message Card is used to respond to the events generated by the interaction between the user and the Message Card. If the processor has a return value (the value structure should be in line with the structure defined by Message Card Structure), then the return value is used to update the responded message card:
import http from 'http';
import * as lark from '@larksuiteoapi/node-sdk';
import type { InteractiveCardActionEvent, InteractiveCard } from '@larksuiteoapi/node-sdk';
const cardDispatcher = new lark.CardActionHandler(
{
encryptKey: 'encrypt key',
verificationToken: 'verification token'
},
async (data: InteractiveCardActionEvent) => {
console.log(data);
const newCard: InteractiveCard = {
// your new interactive card content
header: {},
elements: []
};
return newCard;
}
);
const server = http.createServer();
server.on('request', lark.adaptDefault('/webhook/card', cardDispatcher));
server.listen(3000);
Parameter | Description | Type | Required | Default |
---|---|---|---|---|
encryptKey | Push data encryption key, required when enabling encrypted push use for data decryption | string | no | - |
verificationToken | Security verification, it needs to be used when enabling message security verification | string | No | - |
loggerLevel | Log Level | LoggerLevel | No | LoggerLevel.info |
logger | - | Logger | No | - |
cache | Cache | Cache | No | - |
Decrypt. If Encrypted Push is configured, the open platform will push encrypted data, At this time, the data needs to be decrypted, and this method can be called for convenient decryption. (In general, the decryption logic is built into the SDK, and no manual processing is required).
import * as lark from '@larksuiteoapi/node-sdk';
new lark.AESCipher('encrypt key').decrypt('content');
Rapidly develop auto-responder bot
ISV Application Development Guide
MIT
Click Server SDK in the upper right corner of the page 【Is this document helpful to you? 】Submit feedback