Skip to content

ssssyui/node-sdk

 
 

Repository files navigation

Feishu open interface SDK

GitHub Repo stars NPM Downloads NPM License

中文

Overview

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:

Concept

  • 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.

Installation

npm

npm install @larksuiteoapi/node-sdk

yarn

yarn add @larksuiteoapi/node-sdk

How to use

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';

API Call

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

Create Client

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'));

Client construction parameters:

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 -

Pagination

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. *

File upload

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'),
    },
});

File download

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`);

Normal call

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: {},
});

Message Card

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'
  }
})

Configure request options

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')
]));

Events handling

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:

  1. Construct an instance of the event handler EventDispatcher;
  2. Register the events to be monitored and their handler functions on the instance;
  3. 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);

EventDispatcher constructor parameters

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:

Combined with express

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);

Combined with Koa

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);

Combined with koa-router

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);

Custom adapter

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);

Challenge check

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,
    })
);

Subscribing to Events Using Long Connection Mode

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);

CardActionHandler construction parameters

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 -

Tool method

AESCipher

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');

Examples

Rapidly develop auto-responder bot

Blog

ISV Application Development Guide

LICENSE

MIT

Contact Us

Click Server SDK in the upper right corner of the page 【Is this document helpful to you? 】Submit feedback

About

larksuite open sdk for nodejs

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • TypeScript 99.8%
  • JavaScript 0.2%