Giter Club home page Giter Club logo

node-sdk's Introduction

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

node-sdk's People

Contributors

lockingreal avatar mazhe-nerd avatar noahziheng avatar yaohui-wyh avatar yunnysunny avatar zhangruize avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

node-sdk's Issues

为什么飞书会回复多次呢?

image

我按照readme.md 里的方式进行编写的,现在不知道是哪里出现了问题。无论是本地还是线上,飞书都会推送多次。导致重复回复

Uncaught TypeError TypeError: Cannot read properties of undefined (reading 'tenantAccessTokenInternal')

client.auth.auth.tenantAccessTokenInternal({
data: {
app_id: 'cli_xxxxxxxxx',
app_secret: 'CXTxxxxxxxxxxxxxxxxx',
},
},
lark.withTenantToken("")
).then(res => {
console.log(res);
});

根据app_id, app_secret获取tenantAccessToken,提示没有定义tenantAccessTokenInternal。我看node-sdk-main-branch的代码,里面确实没有定义tenantAccessTokenInternal。但这个调用我是参考的飞书服务器api文档

Wrong typescript declarations for bitable.appTableRecord.list

The API bitable.appTableRecord.list (and listWithIterator) returns a plain object in data.items.fields, but the typescript declaration said it is a Map<string, ...>, which is incorrect, it should be a Record<...>.

fields: Map<string, string | boolean | {
    text?: string;
    link?: string;
} | {
    location?: string;
    pname?: string;
    cityname?: string;
    adname?: string;
    address?: string;
    name?: string;
    full_address?: string;
} | Array<string> | Array<{
    id?: string;
    name?: string;
    en_name?: string;
    email?: string;
}> | Array<{
    file_token?: string;
    name?: string;
    type?: string;
    size?: number;
    url?: string;
    tmp_url?: string;
}>>

TokanManager 中 Cache 过期时间要不要略低于得到的 expire

https://github.com/larksuite/node-sdk/blob/main/client/token-manager.ts#L79

在配置 cache 时,过期时间是服务器得到的时间,但是请求 tenant_access_token 这个过程以及配置 cache 的这个过程也会浪费些许时间,导致 cache 晚于服务器的真正过期时间。

Bug 复现在我每天有 10 点和12点的两个定时任务,12点的定时任务,将拿到一个过期的 token 发送请求,请求失败。

期望:此时应该使得客户端 cache 的 token 过期删除,将会重新去拿新的 token。

所以需不需要配置过期时间时,比真正的过期时间短一点,或者可以配置参数传递。

const { tenant_access_token, expire } = await http
            .post(
                `${this.domain}/open-apis/auth/v3/tenant_access_token/internal`,
                {
                    app_id: this.appId,
                    app_secret: this.appSecret,
                }
            )
            .catch((e) => {
                this.logger.error(e);
            });

        await this.cache?.set(
            CTenantAccessToken,
            tenant_access_token,
            new Date().getTime() + expire * 1000
        );

Client.cache 错误复用

Problem

目前的 Token 获取后都会存储在 Client.cache 中,其值源为:
6d63c4b6-a135-4f82-9b29-e87e1acaec3f

其中 internalCache 是个全局单例,会造成 Token 在同一个进程的多个 Client 之间被错误复用(有一个 Token 后将不再获取,即便是调用的另一个 Client/Tenant)

同时,当试图关闭缓存以绕过问题时会发现:

  • 因为 ||,cache 字段实际上不能通过传 null 实现不缓存
  • disableTokenCache 字段语义不符,会直接不获取 Token

Solution

Client.cache 调整为每次 Client 实例化时新初始化一个 DefaultCache,以保证 Client 间信息隔离

如何语义化获取接口

我查阅了飞书开发者文档和README,但不清楚如何获取接口。业务域.资源.方法,有与飞书文档的对应关系吗?例如发送消息接口。

支持的openapi范围咨询

看起来目前没有支持全量的接口,比如招聘下面只有两个:获取职位详情、获取招聘人员。

  • 但实际上有更多:新建职位等。为什么没有支持呢?
  • 灰度的接口会支持吗?比如获取职位列表?

本地调用sdk,更新多维表格数据。返回:Code :91403

网页在线调试中,可以更新对应多维表格数据。但本地代码调用API,返回Code :91403
经测试:网页在线调试里把tenant_access_token 换成代码请求的tenant_access_token ,返回值为 Code :91403
看上去应该是token被缓存了?没有及时更新导致鉴权失败。配置disableTokenCache:true。也没有起到作用。
求解何解决?

图片上传报错 `TypeError: source.on is not a function`

写了个简单的图片批量上传脚本, 有比较奇怪的错误,想要求助一下。
client 其他的消息发送指令都正常使用没问题,就是图片上传这个有点异常。

@larksuiteoapi/node-sdk 版本 1.12.0

[error]: [
  [
    TypeError: source.on is not a function
        at Function.DelayedStream.create (/Users/tzwm/projects/MTRPG-AI/node_modules/delayed-stream/lib/delayed_stream.js:33:10)
        at FormData.CombinedStream.append (/Users/tzwm/projects/MTRPG-AI/node_modules/combined-stream/lib/combined_stream.js:45:37)
        at FormData.append (/Users/tzwm/projects/MTRPG-AI/node_modules/@larksuiteoapi/node-sdk/node_modules/form-data/lib/form_data.js:75:3)
        at build (/Users/tzwm/projects/MTRPG-AI/node_modules/@larksuiteoapi/node-sdk/node_modules/axios/lib/helpers/toFormData.js:63:16)
        at each (/Users/tzwm/projects/MTRPG-AI/node_modules/@larksuiteoapi/node-sdk/node_modules/axios/lib/helpers/toFormData.js:58:9)
        at Object.forEach (/Users/tzwm/projects/MTRPG-AI/node_modules/@larksuiteoapi/node-sdk/node_modules/axios/lib/utils.js:276:12)
        at build (/Users/tzwm/projects/MTRPG-AI/node_modules/@larksuiteoapi/node-sdk/node_modules/axios/lib/helpers/toFormData.js:40:13)
        at toFormData (/Users/tzwm/projects/MTRPG-AI/node_modules/@larksuiteoapi/node-sdk/node_modules/axios/lib/helpers/toFormData.js:67:3)
        at Object.transformRequest (/Users/tzwm/projects/MTRPG-AI/node_modules/@larksuiteoapi/node-sdk/node_modules/axios/lib/defaults/index.js:80:14)
        at transform (/Users/tzwm/projects/MTRPG-AI/node_modules/@larksuiteoapi/node-sdk/node_modules/axios/lib/core/transformData.js:18:15)
  ]
]
fs.readdir(directory, (err, files) => {
  // 过滤出所有图片文件
  const imageFiles = files.filter(file => {
    const extension = file.split('.').pop()?.toLowerCase();
    return extension === 'jpg' || extension === 'jpeg' || extension === 'png' || extension === 'gif';
  });

  // 输出所有图片文件的文件名
  console.log('Image files in directory:');
  imageFiles.forEach(file => {
    console.log(file);
    const buffer = fs.readFileSync(directory + "/" + file);
    //console.log(buffer);
    client.im.image.create({
      "data": {
        "image_type": "message",
        "image": buffer,
      }
    }).then(res => {
      console.log(res);
    }).catch(err => console.log("errorrrrrrrr", err))
  });
});

开启加密后出现verification failed event,疑似代码逻辑问题

复现流程:

  • 打开飞书的事件加密功能
  • 使用"自定义适配器"模式
  • 发送事件,出现verification failed event

代码片段:

    @Post()
    async on_event(@Body() body,@Headers() headers){
        const data = {
            reqData:body,
            headers
        }
        const { isChallenge, challenge } = generateChallenge(body, {
            encryptKey: this.lark_dispatcher.encryptKey
        });
        if(isChallenge){
            return challenge
        }
        const result = await this.lark_dispatcher.invoke(data)
        if(result instanceof CardActionHandler)
            return result
        return {}
    }

经过追踪,疑似checkIsEventValidated函数中(

timestamp + nonce + this.encryptKey + JSON.stringify(data);
),直接对data作了Hash运算,而此时的data中存在“headers”对象,因此Hash不一致

请问接收消息v1不支持吗

消息体的格式如下
{ "uuid": "54b83e3cac29aa5635a4b540ac8a9882", "event": { "app_id": "cli_a3210d06667a5062", "chat_type": "group", "employee_id": "xx", "is_mention": true, "message_id": "", "msg_type": "text", "open_chat_id": "xx", "open_id": "xx", "open_message_id": "om_6063760aecb6cac19dd3e6ac5dec5478", "parent_id": "", "root_id": "", "tenant_key": "xx", "text": "<at open_id=\"xx\">机器人</at> ddd", "text_without_at_bot": " ddd", "type": "message", "union_id": "on_cdff065033c515aa1abd4d67c3768c73", "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 12_4_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.99 Safari/537.36 Lark/5.10.13 LarkLocale/zh_CN ttnet SDK-Version/5.10.49", "user_open_id": "xx" }, "token": "xx", "ts": "1672757393.949837", "type": "event_callback" }

image

不支持 deno

在代码里import * as lark from "https://esm.sh/@larksuiteoapi/[email protected]";执行时报错

error: Uncaught SyntaxError: The requested module '/v128/[email protected]/denonext/axios.mjs' does not provide an export named 'AxiosError'
    at <anonymous> (https://esm.sh/v128/@larksuiteoapi/[email protected]/denonext/node-sdk.mjs:3:74)

好像是这个版本 axios 不支持 esm

使用sdk 调试平台的接口与页面实际请求接口不符合

使用client.bitable.appTableRecord.create click to debug。调试平台的接口是/open-apis/bitable/v1/apps/:app_token/tables/:table_id/records
但是实际页面请求https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal

curl 'https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal'
-H 'sec-ch-ua: "Not_A Brand";v="99", "Google Chrome";v="109", "Chromium";v="109"'
-H 'Accept: application/json, text/plain, /'
-H 'Content-Type: application/json'
-H 'Referer: http://127.0.0.1:5173/'
-H 'sec-ch-ua-mobile: ?0'
-H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'
-H 'sec-ch-ua-platform: "macOS"'
--data-raw '{"app_id":"app id","app_secret":"app secret"}'
--compressed

参数格式问题

client.im.message.create 里面 data 的 content 参数格式必须是个 JSON.String 格式才可以,另外,如果要在 BOE 环境测试,要设置 初始化 Client 的时候,把 domain 切到 BOE 。这文档里写的操作说明太少了,不靠谱,至少应该找个新人按照这个跑一遍看下缺啥再补全了放出来。

Axios版本更新导致larksuite/node-sdk返回数据乱码

axios在最近更新到了1.2.0版本(https://www.npmjs.com/package/axios?activeTab=versions)
在@larksuiteoapi/node-sdk的package.json中配置"axios": ">=0.27.0",
新部署的@larksuiteoapi/node-sdk环境,自动安装1.2.0版本的axios后,调用接口会返回乱码数据,测试代码:

const lark = require("@larksuiteoapi/node-sdk");
const client = new lark.Client({
    appId: "********",
    appSecret: "********",
});
client.im.chat.list({
        params: {
            page_size: 20,
        },
    })
    .then((res) => {
        console.log(res);
    });

手动替换node_modules中的axios版本为1.1.3可恢复正常:
image

fields 类型官方文档不匹配

这里 fields 类型和官方文档上不匹配,缺少了 number 类型

文档:

创建时间 number Unix 时间戳,单位是毫秒
最后更新时间 number Unix 时间戳,单位是毫秒
                    fields: Record<string, string | boolean | {
                        text?: string;
                        link?: string;
                    } | {
                        location?: string;
                        pname?: string;
                        cityname?: string;
                        adname?: string;
                        address?: string;
                        name?: string;
                        full_address?: string;
                    } | Array<string> | Array<{
                        id?: string;
                        name?: string;
                        en_name?: string;
                        email?: string;
                    }> | Array<{
                        file_token?: string;
                        name?: string;
                        type?: string;
                        size?: number;
                        url?: string;
                        tmp_url?: string;
                    }>>;

另外,方不方便提供类型导出,有时候会使用到一些类型

上传图片失败code: 234001, msg: 'Invalid request param.'

使用下面api上传图片
larkClient.im.image.create({ data: { image_type: 'message', image: fs.readFileSync(path) }, })
图片大小1.5M,header信息如下:
'POST /open-apis/im/v1/images HTTP/1.1\r\n' +
'Accept: application/json, text/plain, /\r\n' +
'content-type: multipart/form-data; boundary=--------------------------547342431807760474424597\r\n' +
'Authorization: Bearer \r\n' +
'User-Agent: oapi-node-sdk/1.0.0\r\n' +
'Host: open.feishu.cn\r\n'
同一个图片作为file上传能够成功

调用报错: TypeError: adapter is not a function

版本:1.17.1
复现代码:

const client = new lark.Client({
		appId: 'sdfsdf',
		appSecret: 'sdfsdfsdf'
	});
// const tenantAccessToken = await client.tokenManager.getTenantAccessToken()
	
const result = await client.bitable.appTableRecord.list({
	path: {
		app_token: appToken,
		table_id: tableId
	}
})
[error]: [
  TypeError: adapter is not a function
      at dispatchRequest (/Users/elric/playground/daily-report-v2-worker/node_modules/axios/lib/core/dispatchRequest.js:58:10)
      at Axios.request (/Users/elric/playground/daily-report-v2-worker/node_modules/axios/lib/core/Axios.js:109:15)
      at Axios.httpMethod [as post] (/Users/elric/playground/daily-report-v2-worker/node_modules/axios/lib/core/Axios.js:144:19)
      at Function.wrap (/Users/elric/playground/daily-report-v2-worker/node_modules/axios/lib/helpers/bind.js:9:15)
      at TokenManager.<anonymous> (/Users/elric/playground/daily-report-v2-worker/node_modules/@larksuiteoapi/node-sdk/es/index.js:22372:18)
      at Generator.next (<anonymous>)
      at fulfilled (/Users/elric/playground/daily-report-v2-worker/node_modules/@larksuiteoapi/node-sdk/es/index.js:52:58)
      at processTicksAndRejections (node:internal/process/task_queues:95:5)
]
[mf:err] POST /: TypeError: Cannot destructure property 'tenant_access_token' of '(intermediate value)' as it is undefined.
    at TokenManager.<anonymous> (/Users/elric/playground/daily-report-v2-worker/node_modules/@larksuiteoapi/node-sdk/es/index.js:22371:21)
    at Generator.next (<anonymous>)
    at fulfilled (/Users/elric/playground/daily-report-v2-worker/node_modules/@larksuiteoapi/node-sdk/es/index.js:52:58)
    at processTicksAndRejections (node:internal/process/task_queues:95:5)

多维表格新建数据表报400

只要把类型17那个field注释掉就能创建成功,不然就会报400,code是99992402
const res = await this.larkClient.bitable.appTable.create({ path: { app_token: body.app_token, }, data: { table: { name: 结果 - ${new Date().valueOf()}, fields: [ { field_name: '接口', type: 1, }, { field_name: '影响程度', property: { options: [ { color: 0, name: 'P0', }, { color: 1, name: 'P1', }, { color: 2, name: 'P2', }, ], }, type: 3, }, { field_name: '修复优先级', property: { options: [ { color: 0, name: 'P0', }, { color: 1, name: 'P1', }, { color: 2, name: 'P2', }, ], }, type: 3, }, { field_name: '结果', property: { options: [ { color: 36, name: '通过', }, { color: 22, name: '失败', }, { color: 0, name: '无效', }, ], }, type: 3, }, // { // field_name: '截图', // type: 17, // }, { field_name: '备注', type: 1, }, { field_name: '场景', property: { options: [], }, type: 3, }, { field_name: '链接', type: 15, }, ], }, }, });

请问如何调试这个项目

初学者,想请通过这个项目学习typescript. 请问如何入手?
VS Code 打开了,却不知如何下手。还请不吝赐教!

设置的自定义请求头不起作用

版本:^1.8.1
设置的自定义请求头不起作用

import { data } from '../../../env';
import * as lark from '@larksuiteoapi/node-sdk';

const client = new lark.Client({
  appId: data.appId,
  appSecret: data.appSecret,
});

class Feishu {
  public accessToken = 't-g1041gdxODD4F2FX5VTFKDXFNG3DO24Z5MDPLBBF';
  public getChatIdByRobotId() {
    return client.im.chat.list(undefined, {
      headers: {
        Authorization: `Bearer ${this.accessToken}`,
      },
    }
    // lark.withTenantToken(`Bearer ${this.accessToken}`)
  );
  }
}
const service =  new Feishu();
service.getChatIdByRobotId();

image

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.