Avatar of the person chatting

我现在要求你现在作为一个经验丰富前端开发工程师。我要求你帮我开发一个钉钉机器人。 有以下要求: 1. 需要使用got请求数据,使用typescript开发,每个接口都需要根据我给的demo定义完整的type 2. 机器人请求的URL是固定的,所以我期望在实例化的时候,传递一次url就好了。 3. 机器人一共支持4中格式。 4. 第一个格式是text,对应的方式名text,需要提交的json demo是:{"at":{"atMobiles":["180xxxxxx"],"atUserIds":["user123"],"isAtAll":false},"text":{"content":"我就是我, @XXX 是不一样的烟火"},"msgtype":"text"} 5. 第二个格式是link,对应的方法名link,需要提交的json demo是:{"msgtype":"link","link":{"text":"这个即将发布的新","title":"时代的火车向前开","picUrl":"","messageUrl":"https://www.dingtalk.com"}} 6. 第三个格式是markdown,方法名是markdown,demo json 是:{"msgtype":"markdown","markdown":{"title":"杭州天气","text":"#### 杭州天气 @150XXXXXXXX \n > 9度"},"at":{"atMobiles":["150XXXXXXXX"],"atUserIds":["user123"],"isAtAll":false}} 7. 第四个是actionCard,方法名是actionCard,demo 数据是:{"actionCard":{"title":"乔布斯 20 年前想打造一间苹果咖啡厅,而它正是 Apple Store 的前身","text":"### 乔布斯 20 年前想打","btnOrientation":"0","singleTitle":"阅读全文","singleURL":"https://www.dingtalk.com/"},"msgtype":"actionCard"} 8. 第五个是feedCard类型,方法名是feedCard,demo数据是:{"msgtype":"feedCard","feedCard":{"links":[{"title":"时代的火车向前开1","messageURL":"https://www.dingtalk.com/","picURL":"https://img.alicdn.com/tfs/TB1NwmBEL9TBuNjy1zbXXXpepXa-2400-1218.png"},{"title":"时代的火车向前开2","messageURL":"https://www.dingtalk.com/","picURL":"https://img.alicdn.com/tfs/TB1NwmBEL9TBuNjy1zbXXXpepXa-2400-1218.png"}]}}1 / 1

首先,我们需要安装got库,用于处理HTTP请求,以及typescript。在项目根目录下执行以下命令:

bashCopy code
npm install got npm install typescript

然后,我们创建一个新的TypeScript文件,如dingtalk-bot.ts。首先导入所需的库和定义一些类型:

typescriptCopy code
import got from "got"; type AtInfo = { atMobiles: string[]; atUserIds: string[]; isAtAll: boolean; }; type TextMessage = { at: AtInfo; text: { content: string }; msgtype: "text"; }; type LinkMessage = { msgtype: "link"; link: { text: string; title: string; picUrl: string; messageUrl: string; }; }; type MarkdownMessage = { msgtype: "markdown"; markdown: { title: string; text: string }; at: AtInfo; }; type ActionCardMessage = { actionCard: { title: string; text: string; btnOrientation: string; singleTitle: string; singleURL: string; }; msgtype: "actionCard"; }; type FeedCardLink = { title: string; messageURL: string; picURL: string; }; type FeedCardMessage = { msgtype: "feedCard"; feedCard: { links: FeedCardLink[] }; }; type DingtalkMessage = TextMessage | LinkMessage | MarkdownMessage | ActionCardMessage | FeedCardMessage;

接下来,我们创建一个DingtalkBot类并实现相关方法:

typescriptCopy code
class DingtalkBot { private apiUrl: string; constructor(apiUrl: string) { this.apiUrl = apiUrl; } async sendMessage(message: DingtalkMessage): Promise<void> { await got.post(this.apiUrl, { json: message, responseType: "json", }); } async text(at: AtInfo, content: string): Promise<void> { const message: TextMessage = { at, text: { content }, msgtype: "text", }; await this.sendMessage(message); } async link(linkInfo: LinkMessage["link"]): Promise<void> { const message: LinkMessage = { link: linkInfo, msgtype: "link", }; await this.sendMessage(message); } async markdown(at: AtInfo, title: string, text: string): Promise<void> { const message: MarkdownMessage = { at, markdown: { title, text }, msgtype: "markdown", }; await this.sendMessage(message); } async actionCard(actionCardInfo: ActionCardMessage["actionCard"]): Promise<void> { const message: ActionCardMessage = { actionCard: actionCardInfo, msgtype: "actionCard", }; await this.sendMessage(message); } async feedCard(links: FeedCardLink[]): Promise<void> { const message: FeedCardMessage = { feedCard: { links }, msgtype: "feedCard", }; await this.sendMessage(message); } }

现在,您可以在其他TypeScript文件中使用DingtalkBot类来创建钉钉机器人并发送不同类型的消息。例如:

typescriptCopy code
import { DingtalkBot } from "./dingtalk-bot"; const dingtalkBot = new DingtalkBot("https://your-webhook-url"); (async () => { await ding
Avatar of the person chatting

上面的代码有几个地方需要修改: 1. at 这个数据是可选的 2. 请求的机器人返回的数据,需要在对应的方法中return 出来。1 / 1

谢谢您的反馈。以下是根据您的要求修改后的代码:

首先,我们需要更新AtInfo类型以使其可选,并更新sendMessage方法以返回响应数据。

typescriptCopy code
type AtInfo = { atMobiles?: string[]; atUserIds?: string[]; isAtAll?: boolean; }; // ... 其他类型定义保持不变 class DingtalkBot { private apiUrl: string; constructor(apiUrl: string) { this.apiUrl = apiUrl; } async sendMessage(message: DingtalkMessage): Promise<any> { const response = await got.post(this.apiUrl, { json: message, responseType: "json", }); return response.body; } async text(at: AtInfo, content: string): Promise<any> { const message: TextMessage = { at, text: { content }, msgtype: "text", }; return await this.sendMessage(message); } async link(linkInfo: LinkMessage["link"]): Promise<any> { const message: LinkMessage = { link: linkInfo, msgtype: "link", }; return await this.sendMessage(message); } async markdown(at: AtInfo, title: string, text: string): Promise<any> { const message: MarkdownMessage = { at, markdown: { title, text }, msgtype: "markdown", }; return await this.sendMessage(message); } async actionCard(actionCardInfo: ActionCardMessage["actionCard"]): Promise<any> { const message: ActionCardMessage = { actionCard: actionCardInfo, msgtype: "actionCard", }; return await this.sendMessage(message); } async feedCard(links: FeedCardLink[]): Promise<any> { const message: FeedCardMessage = { feedCard: { links }, msgtype: "feedCard", }; return await this.sendMessage(message); } }

现在,您可以在其他TypeScript文件中使用DingtalkBot类来创建钉钉机器人并发送不同类型的消息,并处理返回的响应数据。例如:

typescriptCopy code
import { DingtalkBot } from "./dingtalk-bot"; const dingtalkBot = new DingtalkBot("https://your-webhook-url"); (async () => { const textResponse = await dingtalkBot.text({ isAtAll: false }, "这是一条文本消息"); console.log("Text response:", textResponse); const linkResponse = await dingtalkBot.link({ text: "这个即将发布的新", title: "时代的火车向前开", picUrl: "", messageUrl: "https://www.dingtalk.com", }); console.log("Link response:", linkResponse); // ... 其他类型的消息及响应处理 })();
Avatar of the person chatting

请帮我根据上面的代码,写一份readme1 / 1

钉钉机器人 TypeScript SDK

这是一个使用 TypeScript 编写的钉钉机器人SDK,用于发送不同类型的消息到钉钉群聊。

安装

首先,确保你已经安装了Node.jsnpm。然后,在你的项目根目录下执行以下命令来安装所需的依赖:

bashCopy code
npm install got npm install typescript

用法

  1. 将上面提供的dingtalk-bot.ts文件添加到您的项目中。
  2. 在您需要使用钉钉机器人的地方,导入DingtalkBot类并创建一个实例。

下面是一个简单的例子:

typescriptCopy code
import { DingtalkBot } from "./dingtalk-bot"; const dingtalkBot = new DingtalkBot("https://your-webhook-url"); (async () => { // 发送文本消息 const textResponse = await dingtalkBot.text({ isAtAll: false }, "这是一条文本消息"); console.log("Text response:", textResponse); // 发送链接消息 const linkResponse = await dingtalkBot.link({ text: "这个即将发布的新", title: "时代的火车向前开", picUrl: "", messageUrl: "https://www.dingtalk.com", }); console.log("Link response:", linkResponse); // 发送Markdown消息 const markdownResponse = await dingtalkBot.markdown({ isAtAll: false }, "杭州天气", "#### 杭州天气\n > 9度"); console.log("Markdown response:", markdownResponse); // 发送ActionCard消息 const actionCardResponse = await dingtalkBot.actionCard({ title: "乔布斯 20 年前想打造一间苹果咖啡厅,而它正是 Apple Store 的前身", text: "### 乔布斯 20 年前想打", btnOrientation: "0", singleTitle: "阅读全文", singleURL: "https://www.dingtalk.com/", }); console.log("ActionCard response:", actionCardResponse); // 发送FeedCard消息 const feedCardResponse = await dingtalkBot.feedCard([ { title: "时代的火车向前开1", messageURL: "https://www.dingtalk.com/", picURL: "https://img.alicdn.com/tfs/TB1NwmBEL9TBuNjy1zbXXXpepXa-2400-1218.png", }, { title: "时代的火车向前开2", messageURL: "https://www.dingtalk.com/", picURL: "https://img.alicdn.com/tfs/TB1NwmBEL9TBuNjy1zbXXXpepXa-2400-1218.png", }, ]); console.log("FeedCard response:", feedCardResponse); })();

在这个例子中,我们导入了DingtalkBot类,并使用您的钉钉机器人Webhook URL创建了一个实例。然后,我们使用该实例发送不同类型的消息,并处理返回的响应数据。

Avatar of the person chatting

使用jest 测试框架,请帮我基于上面的代码写一下单元测试1 / 1

首先,我们需要安装jest以及相应的TypeScript支持库。在项目根目录下执行以下命令:

bashCopy code
npm install jest ts-jest @types/jest

接下来,创建一个jest.config.js文件,在项目根目录下:

javascriptCopy code
module.exports = { preset: "ts-jest", testEnvironment: "node", testPathIgnorePatterns: ["/node_modules/"], };

现在,我们创建一个新的TypeScript文件,例如dingtalk-bot.test.ts,并编写针对DingtalkBot类的单元测试:

typescriptCopy code
import { DingtalkBot } from "./dingtalk-bot"; import got from "got"; jest.mock("got"); const mockedGot = got as jest.MockedFunction<typeof got>; describe("DingtalkBot", () => { const apiUrl = "https://your-webhook-url"; let dingtalkBot: DingtalkBot; beforeEach(() => { dingtalkBot = new DingtalkBot(apiUrl); }); afterEach(() => { jest.clearAllMocks(); }); it("should send text messages", async () => { mockedGot.post.mockResolvedValue({ body: "success" }); const response = await dingtalkBot.text({ isAtAll: false }, "这是一条文本消息"); expect(response).toBe("success"); expect(mockedGot.post).toHaveBeenCalledWith(apiUrl, expect.objectContaining({ json: expect.any(Object) })); }); it("should send link messages", async () => { mockedGot.post.mockResolvedValue({ body: "success" }); const response = await dingtalkBot.link({ text: "这个即将发布的新", title: "时代的火车向前开", picUrl: "", messageUrl: "https://www.dingtalk.com", }); expect(response).toBe("success"); expect(mockedGot.post).toHaveBeenCalledWith(apiUrl, expect.objectContaining({ json: expect.any(Object) })); }); // 类似地,为其他消息类型编写测试用例 // it("should send markdown messages", async () => { ... }); // it("should send actionCard messages", async () => { ... }); // it("should send feedCard messages", async () => { ... }); });

在这个测试文件中,我们首先导入了DingtalkBot类和got库。然后,我们使用jest.mock模拟了got库的post方法,以便在测试中捕获传递给它的参数。

我们为DingtalkBot类创建了一个测试套件,并在其中定义了几个测试用例,分别针对不同类型的消息。在每个测试用例中,我们调用DingtalkBot的相应方法,并使用expect断言来检查got.post是否被正确调用。

要运行这些测试,只需在项目根目录下执行npx jest命令。Jest将自动检测并运行您的测试用例。

Avatar of the person chatting

上面的DingtalkBot class中, 1. 需要给每一个方法增加类型注释,来说明参数的用途。 2. 方法中如果有at参数,放到最后一个参数上,然后设置为可选类型1 / 1

根据您的要求,我对DingtalkBot类进行了修改,如下所示:

typescriptCopy code
class DingtalkBot { private apiUrl: string; constructor(apiUrl: string) { this.apiUrl = apiUrl; } async sendMessage(message: DingtalkMessage): Promise<any> { const response = await got.post(this.apiUrl, { json: message, responseType: "json", }); return response.body; } /** * 发送文本消息 * @param content 文本消息内容 * @param at 可选,@相关信息 */ async text(content: string, at?: AtInfo): Promise<any> { const message: TextMessage = { at, text: { content }, msgtype: "text", }; return await this.sendMessage(message); } /** * 发送链接消息 * @param linkInfo 链接消息内容 */ async link(linkInfo: LinkMessage["link"]): Promise<any> { const message: LinkMessage = { link: linkInfo, msgtype: "link", }; return await this.sendMessage(message); } /** * 发送Markdown消息 * @param title Markdown消息的标题 * @param text Markdown格式的消息内容 * @param at 可选,@相关信息 */ async markdown(title: string, text: string, at?: AtInfo): Promise<any> { const message: MarkdownMessage = { at, markdown: { title, text }, msgtype: "markdown", }; return await this.sendMessage(message); } /** * 发送ActionCard消息 * @param actionCardInfo ActionCard消息内容 */ async actionCard(actionCardInfo: ActionCardMessage["actionCard"]): Promise<any> { const message: ActionCardMessage = { actionCard: actionCardInfo, msgtype: "actionCard", }; return await this.sendMessage(message); } /** * 发送FeedCard消息 * @param links FeedCard消息中的链接列表 */ async feedCard(links: FeedCardLink[]): Promise<any> { const message: FeedCardMessage = { feedCard: { links }, msgtype: "feedCard", }; return await this.sendMessage(message); } }

在上面的代码中,我为每个方法添加了类型注释,以说明参数的用途。同时,我将at参数移动到了textmarkdown方法的最后一个参数,并将其设置为可选类型。这样,在调用这两个方法时,如果不需要at信息,可以省略该参数。

Avatar of the person chatting

上面的单测中有这个报错:Property 'mockResolvedValue' does not exist on type 'GotRequestFunction'. (tsserver 2339)1 / 1

抱歉给您带来了困扰。在 Jest 中,当我们使用 jest.mock() 之后,我们需要在测试用例中指定函数的行为,这里我们应该使用 mockResolvedValueOnce 方法。

以下是更新后的单元测试代码:

typescriptCopy code
import { DingtalkBot } from "./dingtalk-bot"; import got from "got"; jest.mock("got"); const mockedGot = got as jest.MockedFunction<typeof got>; describe("DingtalkBot", () => { const apiUrl = "https://your-webhook-url"; let dingtalkBot: DingtalkBot; beforeEach(() => { dingtalkBot = new DingtalkBot(apiUrl); }); afterEach(() => { jest.clearAllMocks(); }); it("should send text messages", async () => { mockedGot.post.mockResolvedValueOnce({ body: "success" }); const response = await dingtalkBot.text("这是一条文本消息"); expect(response).toBe("success"); expect(mockedGot.post).toHaveBeenCalledWith(apiUrl, expect.objectContaining({ json: expect.any(Object) })); }); it("should send link messages", async () => { mockedGot.post.mockResolvedValueOnce({ body: "success" }); const response = await dingtalkBot.link({ text: "这个即将发布的新", title: "时代的火车向前开", picUrl: "", messageUrl: "https://www.dingtalk.com", }); expect(response).toBe("success"); expect(mockedGot.post).toHaveBeenCalledWith(apiUrl, expect.objectContaining({ json: expect.any(Object) })); }); // 类似地,为其他消息类型编写测试用例 // it("should send markdown messages", async () => { ... }); // it("should send actionCard messages", async () => { ... }); // it("should send feedCard messages", async () => { ... }); });

在此修改中,我将 mockResolvedValue 替换为 mockResolvedValueOnce。现在,您应该可以运行测试而不会遇到之前的错误。

Avatar of the person chatting

mockResolvedValueOnce这个方法也不存在,请你重新确认一下jest的函数mock用法1 / 1

非常抱歉,我之前的回答有误。在这种情况下,您需要使用mockImplementation来模拟got.post的实现。以下是修复后的单元测试代码:

typescriptCopy code
import { DingtalkBot } from "./dingtalk-bot"; import got from "got"; jest.mock("got"); const mockedGot = got as jest.MockedFunction<typeof got>; describe("DingtalkBot", () => { const apiUrl = "https://your-webhook-url"; let dingtalkBot: DingtalkBot; beforeEach(() => { dingtalkBot = new DingtalkBot(apiUrl); }); afterEach(() => { jest.clearAllMocks(); }); it("should send text messages", async () => { mockedGot.post.mockImplementation(async () => { return { body: "success" }; }); const response = await dingtalkBot.text("这是一条文本消息"); expect(response).toBe("success"); expect(mockedGot.post).toHaveBeenCalledWith(apiUrl, expect.objectContaining({ json: expect.any(Object) })); }); it("should send link messages", async () => { mockedGot.post.mockImplementation(async () => { return { body: "success" }; }); const response = await dingtalkBot.link({ text: "这个即将发布的新", title: "时代的火车向前开", picUrl: "", messageUrl: "https://www.dingtalk.com", }); expect(response).toBe("success"); expect(mockedGot.post).toHaveBeenCalledWith(apiUrl, expect.objectContaining({ json: expect.any(Object) })); }); // 类似地,为其他消息类型编写测试用例 // it("should send markdown messages", async () => { ... }); // it("should send actionCard messages", async () => { ... }); // it("should send feedCard messages", async () => { ... }); });

在这里,我使用mockImplementation方法模拟了got.post函数的实现,并返回一个包含body属性的对象。这样,当调用got.post时,它将返回我们指定的模拟实现,而不是实际实现。现在,您应该可以运行测试而不会遇到之前的错误。

Avatar of the person chatting

还是不对,你是不是应该需要mock 一下got的post 方法1 / 1

非常抱歉给您带来困扰。您是对的,我们需要单独模拟got.post方法。以下是修复后的单元测试代码:

typescriptCopy code
import { DingtalkBot } from "./dingtalk-bot"; import got from "got"; const { post } = got; jest.mock("got"); const mockedGot = got as jest.Mocked<typeof got>; const mockedPost = post as jest.MockedFunction<typeof post>; describe("DingtalkBot", () => { const apiUrl = "https://your-webhook-url"; let dingtalkBot: DingtalkBot; beforeEach(() => { dingtalkBot = new DingtalkBot(apiUrl); }); afterEach(() => { jest.clearAllMocks(); }); it("should send text messages", async () => { mockedPost.mockResolvedValueOnce({ body: "success" } as any); const response = await dingtalkBot.text("这是一条文本消息"); expect(response).toBe("success"); expect(mockedPost).toHaveBeenCalledWith(apiUrl, expect.objectContaining({ json: expect.any(Object) })); }); it("should send link messages", async () => { mockedPost.mockResolvedValueOnce({ body: "success" } as any); const response = await dingtalkBot.link({ text: "这个即将发布的新", title: "时代的火车向前开", picUrl: "", messageUrl: "https://www.dingtalk.com", }); expect(response).toBe("success"); expect(mockedPost).toHaveBeenCalledWith(apiUrl, expect.objectContaining({ json: expect.any(Object) })); }); // 类似地,为其他消息类型编写测试用例 // it("should send markdown messages", async () => { ... }); // it("should send actionCard messages", async () => { ... }); // it("should send feedCard messages", async () => { ... }); });

在这里,我们先解构出got中的post方法。然后,我们使用jest.mock()模拟整个got库。我们分别创建mockedGotmockedPost变量来存储模拟后的gotpost方法。接下来,我们使用mockResolvedValueOnce方法为mockedPost设置返回值。现在,您应该可以运行测试而不会遇到之前的错误。

Avatar of the person chatting

还是不对,你是不是应该需要mock 一下got的post 方法 1 / 1

非常抱歉,我之前的回答没有解决问题。请您再次尝试以下的解决方案。这次,我将使用更简洁的方法来单独模拟 got.post 方法。

typescriptCopy code
import { DingtalkBot } from "./dingtalk-bot"; import got from "got"; jest.mock("got", () => { return { post: jest.fn(), }; }); const mockedPost = got.post as jest.MockedFunction<typeof got.post>; describe("DingtalkBot", () => { const apiUrl = "https://your-webhook-url"; let dingtalkBot: DingtalkBot; beforeEach(() => { dingtalkBot = new DingtalkBot(apiUrl); }); afterEach(() => { jest.clearAllMocks(); }); it("should send text messages", async () => { mockedPost.mockResolvedValueOnce({ body: "success" } as any); const response = await dingtalkBot.text("这是一条文本消息"); expect(response).toBe("success"); expect(mockedPost).toHaveBeenCalledWith(apiUrl, expect.objectContaining({ json: expect.any(Object) })); }); it("should send link messages", async () => { mockedPost.mockResolvedValueOnce({ body: "success" } as any); const response = await dingtalkBot.link({ text: "这个即将发布的新", title: "时代的火车向前开", picUrl: "", messageUrl: "https://www.dingtalk.com", }); expect(response).toBe("success"); expect(mockedPost).toHaveBeenCalledWith(apiUrl, expect.objectContaining({ json: expect.any(Object) })); }); // 类似地,为其他消息类型编写测试用例 // it("should send markdown messages", async () => { ... }); // it("should send actionCard messages", async () => { ... }); // it("should send feedCard messages", async () => { ... }); });

在这里,我们直接在 jest.mock() 中模拟 got 库,并仅模拟 post 方法。然后,我们创建 mockedPost 变量来存储模拟后的 post 方法。接下来,我们使用 mockResolvedValueOnce 方法为 mockedPost 设置返回值。现在,您应该可以运行测试而不会遇到之前的错误。