Repository: Yoctol/bottender
Pull request #212 (2018-03-19 08:06:35)

add more LINE context methods
getUserProfile
getMemberProfile
getMemberIds
getAllMemberIds


Patch:
diff --git a/src/context/LineContext.js b/src/context/LineContext.js
index e4668ab6..9442c934 100644
--- a/src/context/LineContext.js
+++ b/src/context/LineContext.js
@@ -98,6 +98,133 @@ class LineContext extends Context implements PlatformContext {
     }
   }
 
+  /**
+   * Gets profile information of current user.
+   *
+   */
+  async getUserProfile(): Promise<?Object> {
+    if (!this._session) {
+      warning(
+        false,
+        'getUserProfile: should not be called in context without session'
+      );
+      return null;
+    }
+
+    if (!this._session.user) {
+      warning(
+        false,
+        'getUserProfile: should not be called in context without user in session'
+      );
+      return null;
+    }
+
+    switch (this._session.type) {
+      case 'room':
+        return this._client.getRoomMemberProfile(
+          this._session.room.id,
+          this._session.user.id
+        );
+      case 'group':
+        return this._client.getGroupMemberProfile(
+          this._session.group.id,
+          this._session.user.id
+        );
+      case 'user':
+      default:
+        return this._client.getUserProfile(this._session.user.id);
+    }
+  }
+
+  /**
+   * Gets the user profile of a member of the group/room that the bot is in.
+   * This includes the user IDs of users who has not added the bot as a friend or has blocked the bot.
+   *
+   */
+  async getMemberProfile(userId: string): Promise<?Object> {
+    if (!this._session) {
+      warning(
+        false,
+        'getMemberProfile: should not be called in context without session'
+      );
+      return null;
+    }
+
+    switch (this._session.type) {
+      case 'room':
+        return this._client.getRoomMemberProfile(this._session.room.id, userId);
+      case 'group':
+        return this._client.getGroupMemberProfile(
+          this._session.group.id,
+          userId
+        );
+      default:
+        warning(
+          false,
+          'getMemberProfile: should not be called in context which is not room or group session'
+        );
+        return null;
+    }
+  }
+
+  /**
+   * Gets the ID of the users of the members of the group/room that the bot is in.
+   * This includes the user IDs of users who have not added the bot as a friend or has blocked the bot.
+   *
+   * This feature is only available for LINE@ Approved accounts or official accounts.
+   *
+   */
+  async getMemberIds(start: string): Promise<?Object> {
+    if (!this._session) {
+      warning(
+        false,
+        'getMemberIds: should not be called in context without session'
+      );
+      return null;
+    }
+
+    switch (this._session.type) {
+      case 'room':
+        return this._client.getRoomMemberIds(this._session.room.id, start);
+      case 'group':
+        return this._client.getGroupMemberIds(this._session.group.id, start);
+      default:
+        warning(
+          false,
+          'getMemberIds: should not be called in context which is not room or group session'
+        );
+        return null;
+    }
+  }
+
+  /**
+   * Recursively gets the ID of the users of the members of the group/room that the bot is in using cursors.
+   *
+   * This feature is only available for LINE@ Approved accounts or official accounts.
+   *
+   */
+  async getAllMemberIds(): Promise<?Object> {
+    if (!this._session) {
+      warning(
+        false,
+        'getAllMemberIds: should not be called in context without session'
+      );
+      return;
+    }
+
+    switch (this._session.type) {
+      case 'room':
+        return this._client.getAllRoomMemberIds(this._session.room.id);
+      case 'group':
+        return this._client.getAllGroupMemberIds(this._session.group.id);
+      default:
+        warning(
+          false,
+          'getAllMemberIds: should not be called in context which is not room or group session'
+        );
+    }
+  }
+
   /**
    * Gets the ID of the rich menu linked to the user.
    *


Test patch:
diff --git a/src/context/__tests__/LineContext.spec.js b/src/context/__tests__/LineContext.spec.js
index 292209f8..71a07412 100644
--- a/src/context/__tests__/LineContext.spec.js
+++ b/src/context/__tests__/LineContext.spec.js
@@ -43,6 +43,9 @@ const roomSession = {
   room: {
     id: 'fakeRoomId',
   },
+  user: {
+    id: 'fakeUserId',
+  },
 };
 
 const groupSession = {
@@ -50,6 +53,9 @@ const groupSession = {
   group: {
     id: 'fakeGroupId',
   },
+  user: {
+    id: 'fakeUserId',
+  },
 };
 
 const setup = ({ session } = { session: userSession }) => {
@@ -67,6 +73,13 @@ const setup = ({ session } = { session: userSession }) => {
     pushConfirmTemplate: jest.fn(),
     pushImagemap: jest.fn(),
     pushCarouselTemplate: jest.fn(),
+    getUserProfile: jest.fn(),
+    getGroupMemberProfile: jest.fn(),
+    getRoomMemberProfile: jest.fn(),
+    getGroupMemberIds: jest.fn(),
+    getAllGroupMemberIds: jest.fn(),
+    getRoomMemberIds: jest.fn(),
+    getAllRoomMemberIds: jest.fn(),
     getLinkedRichMenu: jest.fn(),
     linkRichMenu: jest.fn(),
     unlinkRichMenu: jest.fn(),
@@ -212,52 +225,6 @@ describe('#reply', () => {
   });
 });
 
-describe('#send', () => {
-  it('is not defined', () => {
-    const { context } = setup();
-
-    expect(context.send).not.toBeDefined();
-  });
-});
-
-describe('#sendText', () => {
-  it('should call client.pushText', async () => {
-    const { context, client, session } = setup();
-
-    await context.sendText('xxx.com');
-
-    expect(client.pushText).toBeCalledWith(session.user.id, 'xxx.com');
-  });
-
-  it('should work with room session', async () => {
-    const { context, client, session } = setup({
-      session: roomSession,
-    });
-
-    await context.sendText('xxx.com');
-
-    expect(client.pushText).toBeCalledWith(session.room.id, 'xxx.com');
-  });
-
-  it('should work with group session', async () => {
-    const { context, client, session } = setup({
-      session: groupSession,
-    });
-
-    await context.sendText('xxx.com');
-
-    expect(client.pushText).toBeCalledWith(session.group.id, 'xxx.com');
-  });
-
-  it('should mark context as handled', async () => {
-    const { context } = setup();
-
-    await context.sendText('xxx.com');
-
-    expect(context.isHandled).toBe(true);
-  });
-});
-
 describe('#push', () => {
   it('should call client.push', async () => {
     const { context, client, session } = setup();
@@ -325,640 +292,908 @@ describe('#pushText', () => {
   });
 });
 
-describe('#sendImage', () => {
-  it('should call client.pushImage', async () => {
-    const { context, client, session } = setup();
-
-    await context.sendImage('xxx.jpg', 'yyy.jpg');
+describe('send APIs', () => {
+  describe('#send', () => {
+    it('is not defined', () => {
+      const { context } = setup();
 
-    expect(client.pushImage).toBeCalledWith(
-      session.user.id,
-      'xxx.jpg',
-      'yyy.jpg'
-    );
+      expect(context.send).not.toBeDefined();
+    });
   });
 
-  it('should work with room session', async () => {
-    const { context, client, session } = setup({
-      session: roomSession,
+  describe('#sendText', () => {
+    it('should call client.pushText', async () => {
+      const { context, client, session } = setup();
+
+      await context.sendText('xxx.com');
+
+      expect(client.pushText).toBeCalledWith(session.user.id, 'xxx.com');
     });
 
-    await context.sendImage('xxx.jpg', 'yyy.jpg');
+    it('should work with room session', async () => {
+      const { context, client, session } = setup({
+        session: roomSession,
+      });
 
-    expect(client.pushImage).toBeCalledWith(
-      session.room.id,
-      'xxx.jpg',
-      'yyy.jpg'
-    );
-  });
+      await context.sendText('xxx.com');
 
-  it('should work with group session', async () => {
-    const { context, client, session } = setup({
-      session: groupSession,
+      expect(client.pushText).toBeCalledWith(session.room.id, 'xxx.com');
     });
 
-    await context.sendImage('xxx.jpg', 'yyy.jpg');
+    it('should work with group session', async () => {
+      const { context, client, session } = setup({
+        session: groupSession,
+      });
 
-    expect(client.pushImage).toBeCalledWith(
-      session.group.id,
-      'xxx.jpg',
-      'yyy.jpg'
-    );
-  });
+      await context.sendText('xxx.com');
 
-  it('should mark context as handled', async () => {
-    const { context } = setup();
+      expect(client.pushText).toBeCalledWith(session.group.id, 'xxx.com');
+    });
 
-    await context.sendImage('xxx.jpg', 'yyy.jpg');
+    it('should mark context as handled', async () => {
+      const { context } = setup();
 
-    expect(context.isHandled).toBe(true);
-  });
+      await context.sendText('xxx.com');
 
-  it('should call warning and not to send if dont have session', async () => {
-    const { context, client } = setup({ session: false });
+      expect(context.isHandled).toBe(true);
+    });
+  });
 
-    await context.sendImage('xxx.jpg', 'yyy.jpg');
+  describe('#sendImage', () => {
+    it('should call client.pushImage', async () => {
+      const { context, client, session } = setup();
 
-    expect(warning).toBeCalled();
-    expect(client.pushImage).not.toBeCalled();
-  });
-});
+      await context.sendImage('xxx.jpg', 'yyy.jpg');
 
-describe('#sendAudio', () => {
-  it('should call client.pushAudio', async () => {
-    const { context, client, session } = setup();
+      expect(client.pushImage).toBeCalledWith(
+        session.user.id,
+        'xxx.jpg',
+        'yyy.jpg'
+      );
+    });
 
-    await context.sendAudio('xxx.mp3', 240000);
+    it('should work with room session', async () => {
+      const { context, client, session } = setup({
+        session: roomSession,
+      });
 
-    expect(client.pushAudio).toBeCalledWith(session.user.id, 'xxx.mp3', 240000);
-  });
+      await context.sendImage('xxx.jpg', 'yyy.jpg');
 
-  it('should work with room session', async () => {
-    const { context, client, session } = setup({
-      session: roomSession,
+      expect(client.pushImage).toBeCalledWith(
+        session.room.id,
+        'xxx.jpg',
+        'yyy.jpg'
+      );
     });
 
-    await context.sendAudio('xxx.mp3', 240000);
+    it('should work with group session', async () => {
+      const { context, client, session } = setup({
+        session: groupSession,
+      });
 
-    expect(client.pushAudio).toBeCalledWith(session.room.id, 'xxx.mp3', 240000);
-  });
+      await context.sendImage('xxx.jpg', 'yyy.jpg');
 
-  it('should work with group session', async () => {
-    const { context, client, session } = setup({
-      session: groupSession,
+      expect(client.pushImage).toBeCalledWith(
+        session.group.id,
+        'xxx.jpg',
+        'yyy.jpg'
+      );
     });
 
-    await context.sendAudio('xxx.mp3', 240000);
+    it('should mark context as handled', async () => {
+      const { context } = setup();
 
-    expect(client.pushAudio).toBeCalledWith(
-      session.group.id,
-      'xxx.mp3',
-      240000
-    );
-  });
+      await context.sendImage('xxx.jpg', 'yyy.jpg');
 
-  it('should mark context as handled', async () => {
-    const { context } = setup();
+      expect(context.isHandled).toBe(true);
+    });
 
-    await context.sendAudio('xxx.mp3', 240000);
+    it('should call warning and not to send if dont have session', async () => {
+      const { context, client } = setup({ session: false });
 
-    expect(context.isHandled).toBe(true);
+      await context.sendImage('xxx.jpg', 'yyy.jpg');
+
+      expect(warning).toBeCalled();
+      expect(client.pushImage).not.toBeCalled();
+    });
   });
-});
 
-describe('#sendVideo', () => {
-  it('should call client.pushVideo', async () => {
-    const { context, client, session } = setup();
+  describe('#sendAudio', () => {
+    it('should call client.pushAudio', async () => {
+      const { context, client, session } = setup();
 
-    await context.sendVideo('xxx.mp4', 'yyy.jpg');
+      await context.sendAudio('xxx.mp3', 240000);
 
-    expect(client.pushVideo).toBeCalledWith(
-      session.user.id,
-      'xxx.mp4',
-      'yyy.jpg'
-    );
-  });
+      expect(client.pushAudio).toBeCalledWith(
+        session.user.id,
+        'xxx.mp3',
+        240000
+      );
+    });
 
-  it('should work with room session', async () => {
-    const { context, client, session } = setup({
-      session: roomSession,
+    it('should work with room session', async () => {
+      const { context, client, session } = setup({
+        session: roomSession,
+      });
+
+      await context.sendAudio('xxx.mp3', 240000);
+
+      expect(client.pushAudio).toBeCalledWith(
+        session.room.id,
+        'xxx.mp3',
+        240000
+      );
     });
 
-    await context.sendVideo('xxx.mp4', 'yyy.jpg');
+    it('should work with group session', async () => {
+      const { context, client, session } = setup({
+        session: groupSession,
+      });
 
-    expect(client.pushVideo).toBeCalledWith(
-      session.room.id,
-      'xxx.mp4',
-      'yyy.jpg'
-    );
-  });
+      await context.sendAudio('xxx.mp3', 240000);
 
-  it('should work with group session', async () => {
-    const { context, client, session } = setup({
-      session: groupSession,
+      expect(client.pushAudio).toBeCalledWith(
+        session.group.id,
+        'xxx.mp3',
+        240000
+      );
     });
 
-    await context.sendVideo('xxx.mp4', 'yyy.jpg');
+    it('should mark context as handled', async () => {
+      const { context } = setup();
+
+      await context.sendAudio('xxx.mp3', 240000);
 
-    expect(client.pushVideo).toBeCalledWith(
-      session.group.id,
-      'xxx.mp4',
-      'yyy.jpg'
-    );
+      expect(context.isHandled).toBe(true);
+    });
   });
 
-  it('should mark context as handled', async () => {
-    const { context } = setup();
+  describe('#sendVideo', () => {
+    it('should call client.pushVideo', async () => {
+      const { context, client, session } = setup();
 
-    await context.sendVideo('xxx.mp4', 'yyy.jpg');
+      await context.sendVideo('xxx.mp4', 'yyy.jpg');
 
-    expect(context.isHandled).toBe(true);
-  });
-});
+      expect(client.pushVideo).toBeCalledWith(
+        session.user.id,
+        'xxx.mp4',
+        'yyy.jpg'
+      );
+    });
 
-describe('#sendLocation', () => {
-  it('should call client.pushLocation', async () => {
-    const { context, client, session } = setup();
+    it('should work with room session', async () => {
+      const { context, client, session } = setup({
+        session: roomSession,
+      });
+
+      await context.sendVideo('xxx.mp4', 'yyy.jpg');
 
-    await context.sendLocation({
-      title: 'my location',
-      address: '〒150-0002 東京都渋谷区渋谷２丁目２１−１',
-      latitude: 35.65910807942215,
-      longitude: 139.70372892916203,
+      expect(client.pushVideo).toBeCalledWith(
+        session.room.id,
+        'xxx.mp4',
+        'yyy.jpg'
+      );
     });
 
-    expect(client.pushLocation).toBeCalledWith(session.user.id, {
-      title: 'my location',
-      address: '〒150-0002 東京都渋谷区渋谷２丁目２１−１',
-      latitude: 35.65910807942215,
-      longitude: 139.70372892916203,
+    it('should work with group session', async () => {
+      const { context, client, session } = setup({
+        session: groupSession,
+      });
+
+      await context.sendVideo('xxx.mp4', 'yyy.jpg');
+
+      expect(client.pushVideo).toBeCalledWith(
+        session.group.id,
+        'xxx.mp4',
+        'yyy.jpg'
+      );
+    });
+
+    it('should mark context as handled', async () => {
+      const { context } = setup();
+
+      await context.sendVideo('xxx.mp4', 'yyy.jpg');
+
+      expect(context.isHandled).toBe(true);
     });
   });
 
-  it('should work with room session', async () => {
-    const { context, client, session } = setup({
-      session: roomSession,
+  describe('#sendLocation', () => {
+    it('should call client.pushLocation', async () => {
+      const { context, client, session } = setup();
+
+      await context.sendLocation({
+        title: 'my location',
+        address: '〒150-0002 東京都渋谷区渋谷２丁目２１−１',
+        latitude: 35.65910807942215,
+        longitude: 139.70372892916203,
+      });
+
+      expect(client.pushLocation).toBeCalledWith(session.user.id, {
+        title: 'my location',
+        address: '〒150-0002 東京都渋谷区渋谷２丁目２１−１',
+        latitude: 35.65910807942215,
+        longitude: 139.70372892916203,
+      });
     });
 
-    await context.sendLocation({
-      title: 'my location',
-      address: '〒150-0002 東京都渋谷区渋谷２丁目２１−１',
-      latitude: 35.65910807942215,
-      longitude: 139.70372892916203,
+    it('should work with room session', async () => {
+      const { context, client, session } = setup({
+        session: roomSession,
+      });
+
+      await context.sendLocation({
+        title: 'my location',
+        address: '〒150-0002 東京都渋谷区渋谷２丁目２１−１',
+        latitude: 35.65910807942215,
+        longitude: 139.70372892916203,
+      });
+
+      expect(client.pushLocation).toBeCalledWith(session.room.id, {
+        title: 'my location',
+        address: '〒150-0002 東京都渋谷区渋谷２丁目２１−１',
+        latitude: 35.65910807942215,
+        longitude: 139.70372892916203,
+      });
     });
 
-    expect(client.pushLocation).toBeCalledWith(session.room.id, {
-      title: 'my location',
-      address: '〒150-0002 東京都渋谷区渋谷２丁目２１−１',
-      latitude: 35.65910807942215,
-      longitude: 139.70372892916203,
+    it('should work with group session', async () => {
+      const { context, client, session } = setup({
+        session: groupSession,
+      });
+
+      await context.sendLocation({
+        title: 'my location',
+        address: '〒150-0002 東京都渋谷区渋谷２丁目２１−１',
+        latitude: 35.65910807942215,
+        longitude: 139.70372892916203,
+      });
+
+      expect(client.pushLocation).toBeCalledWith(session.group.id, {
+        title: 'my location',
+        address: '〒150-0002 東京都渋谷区渋谷２丁目２１−１',
+        latitude: 35.65910807942215,
+        longitude: 139.70372892916203,
+      });
     });
-  });
 
-  it('should work with group session', async () => {
-    const { context, client, session } = setup({
-      session: groupSession,
+    it('should mark context as handled', async () => {
+      const { context } = setup();
+
+      await context.sendLocation({
+        title: 'my location',
+        address: '〒150-0002 東京都渋谷区渋谷２丁目２１−１',
+        latitude: 35.65910807942215,
+        longitude: 139.70372892916203,
+      });
+
+      expect(context.isHandled).toBe(true);
     });
+  });
+
+  describe('#sendSticker', () => {
+    it('should call client.pushSticker', async () => {
+      const { context, client, session } = setup();
 
-    await context.sendLocation({
-      title: 'my location',
-      address: '〒150-0002 東京都渋谷区渋谷２丁目２１−１',
-      latitude: 35.65910807942215,
-      longitude: 139.70372892916203,
+      await context.sendSticker('1', '1');
+
+      expect(client.pushSticker).toBeCalledWith(session.user.id, '1', '1');
     });
 
-    expect(client.pushLocation).toBeCalledWith(session.group.id, {
-      title: 'my location',
-      address: '〒150-0002 東京都渋谷区渋谷２丁目２１−１',
-      latitude: 35.65910807942215,
-      longitude: 139.70372892916203,
+    it('should work with room session', async () => {
+      const { context, client, session } = setup({
+        session: roomSession,
+      });
+
+      await context.sendSticker('1', '1');
+
+      expect(client.pushSticker).toBeCalledWith(session.room.id, '1', '1');
     });
-  });
 
-  it('should mark context as handled', async () => {
-    const { context } = setup();
+    it('should work with group session', async () => {
+      const { context, client, session } = setup({
+        session: groupSession,
+      });
 
-    await context.sendLocation({
-      title: 'my location',
-      address: '〒150-0002 東京都渋谷区渋谷２丁目２１−１',
-      latitude: 35.65910807942215,
-      longitude: 139.70372892916203,
+      await context.sendSticker('1', '1');
+
+      expect(client.pushSticker).toBeCalledWith(session.group.id, '1', '1');
     });
 
-    expect(context.isHandled).toBe(true);
+    it('should mark context as handled', async () => {
+      const { context } = setup();
+
+      await context.sendSticker('1', '1');
+
+      expect(context.isHandled).toBe(true);
+    });
   });
-});
 
-describe('#sendSticker', () => {
-  it('should call client.pushSticker', async () => {
-    const { context, client, session } = setup();
+  describe('#sendImagemap', () => {
+    const template = {
+      baseUrl: 'https://example.com/bot/images/rm001',
+      baseHeight: 1040,
+      baseWidth: 1040,
+      actions: [
+        {
+          type: 'uri',
+          linkUri: 'https://example.com/',
+          area: {
+            x: 0,
+            y: 0,
+            width: 520,
+            height: 1040,
+          },
+        },
+        {
+          type: 'message',
+          text: 'hello',
+          area: {
+            x: 520,
+            y: 0,
+            width: 520,
+            height: 1040,
+          },
+        },
+      ],
+    };
 
-    await context.sendSticker('1', '1');
+    it('should call client.pushImagemap', async () => {
+      const { context, client, session } = setup();
 
-    expect(client.pushSticker).toBeCalledWith(session.user.id, '1', '1');
-  });
+      await context.sendImagemap('this is an imagemap', template);
 
-  it('should work with room session', async () => {
-    const { context, client, session } = setup({
-      session: roomSession,
+      expect(client.pushImagemap).toBeCalledWith(
+        session.user.id,
+        'this is an imagemap',
+        template
+      );
     });
 
-    await context.sendSticker('1', '1');
+    it('should work with room session', async () => {
+      const { context, client, session } = setup({
+        session: roomSession,
+      });
 
-    expect(client.pushSticker).toBeCalledWith(session.room.id, '1', '1');
-  });
+      await context.sendImagemap('this is an imagemap', template);
 
-  it('should work with group session', async () => {
-    const { context, client, session } = setup({
-      session: groupSession,
+      expect(client.pushImagemap).toBeCalledWith(
+        session.room.id,
+        'this is an imagemap',
+        template
+      );
     });
 
-    await context.sendSticker('1', '1');
+    it('should work with group session', async () => {
+      const { context, client, session } = setup({
+        session: groupSession,
+      });
 
-    expect(client.pushSticker).toBeCalledWith(session.group.id, '1', '1');
-  });
+      await context.sendImagemap('this is an imagemap', template);
 
-  it('should mark context as handled', async () => {
-    const { context } = setup();
+      expect(client.pushImagemap).toBeCalledWith(
+        session.group.id,
+        'this is an imagemap',
+        template
+      );
+    });
 
-    await context.sendSticker('1', '1');
+    it('should mark context as handled', async () => {
+      const { context } = setup();
 
-    expect(context.isHandled).toBe(true);
+      await context.sendImagemap('this is an imagemap', template);
+
+      expect(context.isHandled).toBe(true);
+    });
   });
-});
 
-describe('#sendImagemap', () => {
-  const template = {
-    baseUrl: 'https://example.com/bot/images/rm001',
-    baseHeight: 1040,
-    baseWidth: 1040,
-    actions: [
-      {
-        type: 'uri',
-        linkUri: 'https://example.com/',
-        area: {
-          x: 0,
-          y: 0,
-          width: 520,
-          height: 1040,
+  describe('#sendButtonTemplate', () => {
+    const template = {
+      thumbnailImageUrl: 'https://example.com/bot/images/image.jpg',
+      title: 'Menu',
+      text: 'Please select',
+      actions: [
+        {
+          type: 'postback',
+          label: 'Buy',
+          data: 'action=buy&itemid=123',
         },
-      },
-      {
-        type: 'message',
-        text: 'hello',
-        area: {
-          x: 520,
-          y: 0,
-          width: 520,
-          height: 1040,
+        {
+          type: 'postback',
+          label: 'Add to cart',
+          data: 'action=add&itemid=123',
         },
-      },
-    ],
-  };
+        {
+          type: 'uri',
+          label: 'View detail',
+          uri: 'http://example.com/page/123',
+        },
+      ],
+    };
 
-  it('should call client.pushImagemap', async () => {
-    const { context, client, session } = setup();
+    it('should call client.pushButtonTemplate', async () => {
+      const { context, client, session } = setup();
 
-    await context.sendImagemap('this is an imagemap', template);
+      await context.sendButtonTemplate('this is a button template', template);
 
-    expect(client.pushImagemap).toBeCalledWith(
-      session.user.id,
-      'this is an imagemap',
-      template
-    );
-  });
+      expect(client.pushButtonTemplate).toBeCalledWith(
+        session.user.id,
+        'this is a button template',
+        template
+      );
+    });
 
-  it('should work with room session', async () => {
-    const { context, client, session } = setup({
-      session: roomSession,
+    it('should work with room session', async () => {
+      const { context, client, session } = setup({
+        session: roomSession,
+      });
+
+      await context.sendButtonTemplate('this is a button template', template);
+
+      expect(client.pushButtonTemplate).toBeCalledWith(
+        session.room.id,
+        'this is a button template',
+        template
+      );
     });
 
-    await context.sendImagemap('this is an imagemap', template);
+    it('should work with group session', async () => {
+      const { context, client, session } = setup({
+        session: groupSession,
+      });
 
-    expect(client.pushImagemap).toBeCalledWith(
-      session.room.id,
-      'this is an imagemap',
-      template
-    );
-  });
+      await context.sendButtonTemplate('this is a button template', template);
 
-  it('should work with group session', async () => {
-    const { context, client, session } = setup({
-      session: groupSession,
+      expect(client.pushButtonTemplate).toBeCalledWith(
+        session.group.id,
+        'this is a button template',
+        template
+      );
     });
 
-    await context.sendImagemap('this is an imagemap', template);
+    it('should mark context as handled', async () => {
+      const { context } = setup();
+
+      await context.sendButtonTemplate('this is a button template', template);
 
-    expect(client.pushImagemap).toBeCalledWith(
-      session.group.id,
-      'this is an imagemap',
-      template
-    );
+      expect(context.isHandled).toBe(true);
+    });
   });
 
-  it('should mark context as handled', async () => {
-    const { context } = setup();
+  describe('#sendConfirmTemplate', () => {
+    const template = {
+      text: 'Are you sure?',
+      actions: [
+        {
+          type: 'message',
+          label: 'Yes',
+          text: 'yes',
+        },
+        {
+          type: 'message',
+          label: 'No',
+          text: 'no',
+        },
+      ],
+    };
 
-    await context.sendImagemap('this is an imagemap', template);
+    it('should call client.pushConfirmTemplate', async () => {
+      const { context, client, session } = setup();
 
-    expect(context.isHandled).toBe(true);
+      await context.sendConfirmTemplate('this is a confirm template', template);
+
+      expect(client.pushConfirmTemplate).toBeCalledWith(
+        session.user.id,
+        'this is a confirm template',
+        template
+      );
+    });
+
+    it('should work with room session', async () => {
+      const { context, client, session } = setup({
+        session: roomSession,
+      });
+
+      await context.sendConfirmTemplate('this is a confirm template', template);
+
+      expect(client.pushConfirmTemplate).toBeCalledWith(
+        session.room.id,
+        'this is a confirm template',
+        template
+      );
+    });
+
+    it('should work with group session', async () => {
+      const { context, client, session } = setup({
+        session: groupSession,
+      });
+
+      await context.sendConfirmTemplate('this is a confirm template', template);
+
+      expect(client.pushConfirmTemplate).toBeCalledWith(
+        session.group.id,
+        'this is a confirm template',
+        template
+      );
+    });
+
+    it('should mark context as handled', async () => {
+      const { context } = setup();
+
+      await context.sendConfirmTemplate('this is a confirm template', template);
+
+      expect(context.isHandled).toBe(true);
+    });
   });
-});
 
-describe('#sendButtonTemplate', () => {
-  const template = {
-    thumbnailImageUrl: 'https://example.com/bot/images/image.jpg',
-    title: 'Menu',
-    text: 'Please select',
-    actions: [
+  describe('#sendCarouselTemplate', () => {
+    const template = [
       {
-        type: 'postback',
-        label: 'Buy',
-        data: 'action=buy&itemid=123',
+        thumbnailImageUrl: 'https://example.com/bot/images/item1.jpg',
+        title: 'this is menu',
+        text: 'description',
+        actions: [
+          {
+            type: 'postback',
+            label: 'Buy',
+            data: 'action=buy&itemid=111',
+          },
+          {
+            type: 'postback',
+            label: 'Add to cart',
+            data: 'action=add&itemid=111',
+          },
+          {
+            type: 'uri',
+            label: 'View detail',
+            uri: 'http://example.com/page/111',
+          },
+        ],
       },
       {
-        type: 'postback',
-        label: 'Add to cart',
-        data: 'action=add&itemid=123',
+        thumbnailImageUrl: 'https://example.com/bot/images/item2.jpg',
+        title: 'this is menu',
+        text: 'description',
+        actions: [
+          {
+            type: 'postback',
+            label: 'Buy',
+            data: 'action=buy&itemid=222',
+          },
+          {
+            type: 'postback',
+            label: 'Add to cart',
+            data: 'action=add&itemid=222',
+          },
+          {
+            type: 'uri',
+            label: 'View detail',
+            uri: 'http://example.com/page/222',
+          },
+        ],
       },
-      {
-        type: 'uri',
-        label: 'View detail',
-        uri: 'http://example.com/page/123',
-      },
-    ],
-  };
+    ];
 
-  it('should call client.pushButtonTemplate', async () => {
-    const { context, client, session } = setup();
+    it('should call client.pushCarouselTemplate', async () => {
+      const { context, client, session } = setup();
 
-    await context.sendButtonTemplate('this is a button template', template);
+      await context.sendCarouselTemplate(
+        'this is a carousel template',
+        template
+      );
 
-    expect(client.pushButtonTemplate).toBeCalledWith(
-      session.user.id,
-      'this is a button template',
-      template
-    );
-  });
+      expect(client.pushCarouselTemplate).toBeCalledWith(
+        session.user.id,
+        'this is a carousel template',
+        template
+      );
+    });
 
-  it('should work with room session', async () => {
-    const { context, client, session } = setup({
-      session: roomSession,
+    it('should work with room session', async () => {
+      const { context, client, session } = setup({
+        session: roomSession,
+      });
+
+      await context.sendConfirmTemplate(
+        'this is a carousel template',
+        template
+      );
+
+      expect(client.pushConfirmTemplate).toBeCalledWith(
+        session.room.id,
+        'this is a carousel template',
+        template
+      );
+    });
+
+    it('should work with group session', async () => {
+      const { context, client, session } = setup({
+        session: groupSession,
+      });
+
+      await context.sendConfirmTemplate(
+        'this is a carousel template',
+        template
+      );
+
+      expect(client.pushConfirmTemplate).toBeCalledWith(
+        session.group.id,
+        'this is a carousel template',
+        template
+      );
     });
 
-    await context.sendButtonTemplate('this is a button template', template);
+    it('should mark context as handled', async () => {
+      const { context } = setup();
 
-    expect(client.pushButtonTemplate).toBeCalledWith(
-      session.room.id,
-      'this is a button template',
-      template
-    );
+      await context.sendCarouselTemplate(
+        'this is a carousel template',
+        template
+      );
+
+      expect(context.isHandled).toBe(true);
+    });
   });
+});
 
-  it('should work with group session', async () => {
-    const { context, client, session } = setup({
-      session: groupSession,
+describe('profile APIs', () => {
+  describe('#getUserProfile', () => {
+    it('not get profile without session', async () => {
+      const { context, client } = setup({ session: null });
+
+      await context.getUserProfile();
+
+      expect(client.getUserProfile).not.toBeCalled();
+      expect(client.getGroupMemberProfile).not.toBeCalled();
+      expect(client.getRoomMemberProfile).not.toBeCalled();
+      expect(warning).toBeCalled();
+      expect(context.isHandled).toBe(false);
     });
 
-    await context.sendButtonTemplate('this is a button template', template);
+    it('not get profile without user in session', async () => {
+      const { context, client } = setup({
+        type: 'room',
+        room: {
+          id: 'fakeRoomId',
+        },
+        user: null,
+      });
 
-    expect(client.pushButtonTemplate).toBeCalledWith(
-      session.group.id,
-      'this is a button template',
-      template
-    );
-  });
+      await context.getUserProfile();
 
-  it('should mark context as handled', async () => {
-    const { context } = setup();
+      expect(client.getUserProfile).not.toBeCalled();
+      expect(client.getGroupMemberProfile).not.toBeCalled();
+      expect(client.getRoomMemberProfile).not.toBeCalled();
+      expect(warning).toBeCalled();
+      expect(context.isHandled).toBe(false);
+    });
 
-    await context.sendButtonTemplate('this is a button template', template);
+    it('get user profile in group', async () => {
+      const { context, client } = setup({ session: groupSession });
 
-    expect(context.isHandled).toBe(true);
-  });
-});
+      await context.getUserProfile();
 
-describe('#sendConfirmTemplate', () => {
-  const template = {
-    text: 'Are you sure?',
-    actions: [
-      {
-        type: 'message',
-        label: 'Yes',
-        text: 'yes',
-      },
-      {
-        type: 'message',
-        label: 'No',
-        text: 'no',
-      },
-    ],
-  };
+      expect(client.getGroupMemberProfile).toBeCalledWith(
+        'fakeGroupId',
+        'fakeUserId'
+      );
+    });
 
-  it('should call client.pushConfirmTemplate', async () => {
-    const { context, client, session } = setup();
+    it('get user profile in room', async () => {
+      const { context, client } = setup({ session: roomSession });
+
+      await context.getUserProfile();
 
-    await context.sendConfirmTemplate('this is a confirm template', template);
+      expect(client.getRoomMemberProfile).toBeCalledWith(
+        'fakeRoomId',
+        'fakeUserId'
+      );
+    });
 
-    expect(client.pushConfirmTemplate).toBeCalledWith(
-      session.user.id,
-      'this is a confirm template',
-      template
-    );
+    it('get user profile', async () => {
+      const { context, client } = setup({ session: userSession });
+
+      await context.getUserProfile();
+
+      expect(client.getUserProfile).toBeCalledWith('fakeUserId');
+    });
   });
 
-  it('should work with room session', async () => {
-    const { context, client, session } = setup({
-      session: roomSession,
+  describe('#getMemberProfile', () => {
+    it('not get profile without session', async () => {
+      const { context, client } = setup({ session: null });
+
+      await context.getMemberProfile('anotherUser');
+
+      expect(client.getGroupMemberProfile).not.toBeCalled();
+      expect(client.getRoomMemberProfile).not.toBeCalled();
+      expect(warning).toBeCalled();
+      expect(context.isHandled).toBe(false);
     });
 
-    await context.sendConfirmTemplate('this is a confirm template', template);
+    it('get member profile in group', async () => {
+      const { context, client } = setup({ session: groupSession });
 
-    expect(client.pushConfirmTemplate).toBeCalledWith(
-      session.room.id,
-      'this is a confirm template',
-      template
-    );
-  });
+      await context.getMemberProfile('anotherUser');
 
-  it('should work with group session', async () => {
-    const { context, client, session } = setup({
-      session: groupSession,
+      expect(client.getGroupMemberProfile).toBeCalledWith(
+        'fakeGroupId',
+        'anotherUser'
+      );
     });
 
-    await context.sendConfirmTemplate('this is a confirm template', template);
+    it('get member profile in room', async () => {
+      const { context, client } = setup({ session: roomSession });
 
-    expect(client.pushConfirmTemplate).toBeCalledWith(
-      session.group.id,
-      'this is a confirm template',
-      template
-    );
-  });
+      await context.getMemberProfile('anotherUser');
 
-  it('should mark context as handled', async () => {
-    const { context } = setup();
+      expect(client.getRoomMemberProfile).toBeCalledWith(
+        'fakeRoomId',
+        'anotherUser'
+      );
+    });
 
-    await context.sendConfirmTemplate('this is a confirm template', template);
+    it('not get member profile in user session', async () => {
+      const { context, client } = setup({ session: userSession });
 
-    expect(context.isHandled).toBe(true);
+      await context.getMemberProfile('anotherUser');
+
+      expect(client.getGroupMemberProfile).not.toBeCalled();
+      expect(client.getRoomMemberProfile).not.toBeCalled();
+      expect(warning).toBeCalled();
+      expect(context.isHandled).toBe(false);
+    });
   });
 });
 
-describe('#sendCarouselTemplate', () => {
-  const template = [
-    {
-      thumbnailImageUrl: 'https://example.com/bot/images/item1.jpg',
-      title: 'this is menu',
-      text: 'description',
-      actions: [
-        {
-          type: 'postback',
-          label: 'Buy',
-          data: 'action=buy&itemid=111',
-        },
-        {
-          type: 'postback',
-          label: 'Add to cart',
-          data: 'action=add&itemid=111',
-        },
-        {
-          type: 'uri',
-          label: 'View detail',
-          uri: 'http://example.com/page/111',
-        },
-      ],
-    },
-    {
-      thumbnailImageUrl: 'https://example.com/bot/images/item2.jpg',
-      title: 'this is menu',
-      text: 'description',
-      actions: [
-        {
-          type: 'postback',
-          label: 'Buy',
-          data: 'action=buy&itemid=222',
-        },
-        {
-          type: 'postback',
-          label: 'Add to cart',
-          data: 'action=add&itemid=222',
-        },
-        {
-          type: 'uri',
-          label: 'View detail',
-          uri: 'http://example.com/page/222',
-        },
-      ],
-    },
-  ];
+describe('member IDs APIs', () => {
+  describe('#getMemberIds', () => {
+    it('not get memeber ids without session', async () => {
+      const { context, client } = setup({ session: null });
 
-  it('should call client.pushCarouselTemplate', async () => {
-    const { context, client, session } = setup();
+      await context.getMemberIds('startToken');
+
+      expect(client.getRoomMemberIds).not.toBeCalled();
+      expect(client.getGroupMemberIds).not.toBeCalled();
+      expect(warning).toBeCalled();
+      expect(context.isHandled).toBe(false);
+    });
 
-    await context.sendCarouselTemplate('this is a carousel template', template);
+    it('get memeber ids in group', async () => {
+      const { context, client } = setup({ session: groupSession });
 
-    expect(client.pushCarouselTemplate).toBeCalledWith(
-      session.user.id,
-      'this is a carousel template',
-      template
-    );
-  });
+      await context.getMemberIds('startToken');
 
-  it('should work with room session', async () => {
-    const { context, client, session } = setup({
-      session: roomSession,
+      expect(client.getGroupMemberIds).toBeCalledWith(
+        'fakeGroupId',
+        'startToken'
+      );
     });
 
-    await context.sendConfirmTemplate('this is a carousel template', template);
+    it('get memeber ids in room', async () => {
+      const { context, client } = setup({ session: roomSession });
 
-    expect(client.pushConfirmTemplate).toBeCalledWith(
-      session.room.id,
-      'this is a carousel template',
-      template
-    );
-  });
+      await context.getMemberIds('startToken');
 
-  it('should work with group session', async () => {
-    const { context, client, session } = setup({
-      session: groupSession,
+      expect(client.getRoomMemberIds).toBeCalledWith(
+        'fakeRoomId',
+        'startToken'
+      );
     });
 
-    await context.sendConfirmTemplate('this is a carousel template', template);
+    it('not get user profile in user session', async () => {
+      const { context, client } = setup({ session: userSession });
 
-    expect(client.pushConfirmTemplate).toBeCalledWith(
-      session.group.id,
-      'this is a carousel template',
-      template
-    );
+      await context.getMemberIds('startToken');
+
+      expect(client.getRoomMemberIds).not.toBeCalled();
+      expect(client.getGroupMemberIds).not.toBeCalled();
+      expect(warning).toBeCalled();
+      expect(context.isHandled).toBe(false);
+    });
   });
 
-  it('should mark context as handled', async () => {
-    const { context } = setup();
+  describe('#getAllMemberIds', () => {
+    it('not get memeber ids without session', async () => {
+      const { context, client } = setup({ session: null });
 
-    await context.sendCarouselTemplate('this is a carousel template', template);
+      await context.getAllMemberIds();
 
-    expect(context.isHandled).toBe(true);
-  });
-});
+      expect(client.getAllRoomMemberIds).not.toBeCalled();
+      expect(client.getAllGroupMemberIds).not.toBeCalled();
+      expect(warning).toBeCalled();
+      expect(context.isHandled).toBe(false);
+    });
 
-describe('#getLinkedRichMenu', () => {
-  it('should call client.getLinkedRichMenu', async () => {
-    const { context, client, session } = setup();
+    it('get memeber ids in group', async () => {
+      const { context, client } = setup({ session: groupSession });
 
-    client.getLinkedRichMenu.mockReturnValue(
-      Promise.resolve({
-        richMenuId: 'richMenuId',
-      })
-    );
+      await context.getAllMemberIds();
 
-    const result = await context.getLinkedRichMenu();
+      expect(client.getAllGroupMemberIds).toBeCalledWith('fakeGroupId');
+    });
 
-    expect(client.getLinkedRichMenu).toBeCalledWith(session.user.id);
-    expect(result.richMenuId).toEqual('richMenuId');
-  });
+    it('get memeber ids in room', async () => {
+      const { context, client } = setup({ session: roomSession });
 
-  it('should warn without user', async () => {
-    const { context } = setup({ session: {} });
+      await context.getAllMemberIds();
 
-    await context.getLinkedRichMenu();
+      expect(client.getAllRoomMemberIds).toBeCalledWith('fakeRoomId');
+    });
 
-    expect(warning).toBeCalled();
+    it('not get user profile in user session', async () => {
+      const { context, client } = setup({ session: userSession });
+
+      await context.getAllMemberIds();
+
+      expect(client.getAllRoomMemberIds).not.toBeCalled();
+      expect(client.getAllGroupMemberIds).not.toBeCalled();
+      expect(warning).toBeCalled();
+      expect(context.isHandled).toBe(false);
+    });
   });
 });
 
-describe('#linkRichMenu', () => {
-  it('should call client.linkRichMenu', async () => {
-    const { context, client, session } = setup();
+describe('ruchmenu APIs', () => {
+  describe('#getLinkedRichMenu', () => {
+    it('should call client.getLinkedRichMenu', async () => {
+      const { context, client, session } = setup();
 
-    await context.linkRichMenu('richMenuId');
+      client.getLinkedRichMenu.mockReturnValue(
+        Promise.resolve({
+          richMenuId: 'richMenuId',
+        })
+      );
 
-    expect(client.linkRichMenu).toBeCalledWith(session.user.id, 'richMenuId');
-  });
+      const result = await context.getLinkedRichMenu();
 
-  it('should warn without user', async () => {
-    const { context } = setup({ session: {} });
+      expect(client.getLinkedRichMenu).toBeCalledWith(session.user.id);
+      expect(result.richMenuId).toEqual('richMenuId');
+    });
 
-    await context.linkRichMenu('richMenuId');
+    it('should warn without user', async () => {
+      const { context } = setup({ session: {} });
 
-    expect(warning).toBeCalled();
+      await context.getLinkedRichMenu();
+
+      expect(warning).toBeCalled();
+    });
   });
-});
 
-describe('#unlinkRichMenu', () => {
-  it('should call client.unlinkRichMenu', async () => {
-    const { context, client, session } = setup();
+  describe('#linkRichMenu', () => {
+    it('should call client.linkRichMenu', async () => {
+      const { context, client, session } = setup();
 
-    await context.unlinkRichMenu();
+      await context.linkRichMenu('richMenuId');
 
-    expect(client.unlinkRichMenu).toBeCalledWith(session.user.id);
+      expect(client.linkRichMenu).toBeCalledWith(session.user.id, 'richMenuId');
+    });
+
+    it('should warn without user', async () => {
+      const { context } = setup({ session: {} });
+
+      await context.linkRichMenu('richMenuId');
+
+      expect(warning).toBeCalled();
+    });
   });
 
-  it('should warn without user', async () => {
-    const { context } = setup({ session: {} });
+  describe('#unlinkRichMenu', () => {
+    it('should call client.unlinkRichMenu', async () => {
+      const { context, client, session } = setup();
 
-    await context.unlinkRichMenu();
+      await context.unlinkRichMenu();
 
-    expect(warning).toBeCalled();
+      expect(client.unlinkRichMenu).toBeCalledWith(session.user.id);
+    });
+
+    it('should warn without user', async () => {
+      const { context } = setup({ session: {} });
+
+      await context.unlinkRichMenu();
+
+      expect(warning).toBeCalled();
+    });
   });
 });