Repository: Yoctol/bottender
Pull request #824 (2020-07-01 11:57:09)

Line context getMembersCount function
LineContext.ts getMemberCount function

codecov-commenter: # [Codecov](https://codecov.io/gh/Yoctol/bottender/pull/824?src=pr&el=h1) Report
> Merging [#824](https://codecov.io/gh/Yoctol/bottender/pull/824?src=pr&el=desc) into [release/1.5](https://codecov.io/gh/Yoctol/bottender/commit/534b336f5027a35cc1486c934b9494cc8d8dffe1&el=desc) will **increase** coverage by `0.02%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/Yoctol/bottender/pull/824/graphs/tree.svg?width=650&height=150&src=pr&token=JX3EGtxYH3)](https://codecov.io/gh/Yoctol/bottender/pull/824?src=pr&el=tree)

```diff
@@               Coverage Diff               @@
##           release/1.5     #824      +/-   ##
===============================================
+ Coverage        83.95%   83.98%   +0.02%     
===============================================
  Files              120      120              
  Lines             6194     6205      +11     
  Branches          1536     1540       +4     
===============================================
+ Hits              5200     5211      +11     
  Misses             990      990              
  Partials             4        4              
```


| [Impacted Files](https://codecov.io/gh/Yoctol/bottender/pull/824?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [packages/bottender/src/line/LineContext.ts](https://codecov.io/gh/Yoctol/bottender/pull/824/diff?src=pr&el=tree#diff-cGFja2FnZXMvYm90dGVuZGVyL3NyYy9saW5lL0xpbmVDb250ZXh0LnRz) | `75.73% <100.00%> (+1.17%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/Yoctol/bottender/pull/824?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/Yoctol/bottender/pull/824?src=pr&el=footer). Last update [534b336...7cfb75c](https://codecov.io/gh/Yoctol/bottender/pull/824?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/examples/slack-home-tab/.env b/examples/slack-home-tab/.env
new file mode 100644
index 00000000..c31340e0
--- /dev/null
+++ b/examples/slack-home-tab/.env
@@ -0,0 +1,2 @@
+SLACK_ACCESS_TOKEN=
+SLACK_SIGNING_SECRET=
diff --git a/examples/slack-home-tab/README.md b/examples/slack-home-tab/README.md
new file mode 100644
index 00000000..85e17456
--- /dev/null
+++ b/examples/slack-home-tab/README.md
@@ -0,0 +1,58 @@
+# Slack Home Tab
+
+## Install and Run
+
+Download this example or clone [bottender](https://github.com/Yoctol/bottender).
+
+```sh
+curl https://codeload.github.com/Yoctol/bottender/tar.gz/master | tar -xz --strip=2 bottender-master/examples/slack-home-tab
+cd slack-home-tab
+```
+
+Install dependencies:
+
+```sh
+npm install
+```
+
+You must fill `SLACK_ACCESS_TOKEN` and `SLACK_SIGNING_SECRET` in your `.env` file.
+
+If you are not familiar with Slack Bot, you may refer to Bottender's doc, [Slack Setup](https://bottender.js.org/docs/channel-slack-setup), to find detailed instructions.
+
+After that, you can run the bot with this npm script:
+
+```sh
+npm run dev
+```
+
+This command starts a server listening at `http://localhost:5000` for bot development.
+
+If you successfully start the server, you get a webhook URL in the format of `https://xxxxxxxx.ngrok.io/webhooks/slack` from your terminal.
+
+## Slack App Settings
+
+To enable the home tab, go to [Slack Developer Console](https://api.slack.com/apps) / [YourApp] / App Home / Show Tabs, and enable the home tab.
+
+## Set Webhook
+
+To set the webhook, go to [Slack Developer Console](https://api.slack.com/apps) / [YourApp] / Event Subscriptions and [Slack Developer Console](https://api.slack.com/apps) / [YourApp] / Interactivity & Shortcuts, and use the webhook URL you got from running `npm run dev` to edit Request URLs for your bot.
+
+You must subscribed the `app_home_opened` event.
+
+## Idea of This Example
+
+This example is a bot running on [Slack](https://slack.com/).
+
+This example contains the following topics:
+
+- How to use home tab.
+- How to update home tab view.
+
+For more information, check our [Slack guides](https://bottender.js.org/docs/en/channel-slack-block-kit).
+
+## Related Examples
+
+- [slack-modal-update](../slack-modal-update)
+- [slack-modal-push](../slack-modal-push)
+- [slack-modal-form](../slack-modal-form)
+- [slack-modal-on-home](../slack-modal-on-home)
diff --git a/examples/slack-home-tab/bottender.config.js b/examples/slack-home-tab/bottender.config.js
new file mode 100644
index 00000000..9578fa52
--- /dev/null
+++ b/examples/slack-home-tab/bottender.config.js
@@ -0,0 +1,10 @@
+module.exports = {
+  channels: {
+    slack: {
+      enabled: true,
+      path: '/webhooks/slack',
+      accessToken: process.env.SLACK_ACCESS_TOKEN,
+      signingSecret: process.env.SLACK_SIGNING_SECRET,
+    },
+  },
+};
diff --git a/examples/slack-home-tab/index.js b/examples/slack-home-tab/index.js
new file mode 100644
index 00000000..82945be0
--- /dev/null
+++ b/examples/slack-home-tab/index.js
@@ -0,0 +1,58 @@
+const { router, route, slack } = require('bottender/router');
+
+let counter = 0;
+
+function getBlocks(text) {
+  return [
+    {
+      type: 'section',
+      text: {
+        type: 'mrkdwn',
+        text,
+      },
+    },
+    {
+      type: 'actions',
+      elements: [
+        {
+          type: 'button',
+          text: {
+            type: 'plain_text',
+            text: 'add count',
+            emoji: true,
+          },
+          value: 'add count',
+        },
+      ],
+    },
+  ];
+}
+
+async function Home(context) {
+  await context.views.publish({
+    userId: context.session.user.id,
+    view: {
+      type: 'home',
+      blocks: getBlocks(`click count: ${counter}`),
+    },
+  });
+}
+
+async function OnBlockActions(context) {
+  if (context.event.action.value === 'add count') {
+    counter += 1;
+    return Home;
+  }
+}
+
+async function Default(context) {
+  await context.sendText('Hello World');
+}
+
+module.exports = async function App(context) {
+  return router([
+    slack.event('app_home_opened', Home),
+    slack.event('block_actions', OnBlockActions),
+    route('*', Default),
+  ]);
+};
diff --git a/examples/slack-home-tab/package.json b/examples/slack-home-tab/package.json
new file mode 100644
index 00000000..d39ea1d4
--- /dev/null
+++ b/examples/slack-home-tab/package.json
@@ -0,0 +1,9 @@
+{
+  "scripts": {
+    "dev": "bottender dev",
+    "start": "bottender start"
+  },
+  "dependencies": {
+    "bottender": "1.5.1-alpha.4"
+  }
+}
diff --git a/examples/slack-modal-form/.env b/examples/slack-modal-form/.env
new file mode 100644
index 00000000..c31340e0
--- /dev/null
+++ b/examples/slack-modal-form/.env
@@ -0,0 +1,2 @@
+SLACK_ACCESS_TOKEN=
+SLACK_SIGNING_SECRET=
diff --git a/examples/slack-modal-form/README.md b/examples/slack-modal-form/README.md
new file mode 100644
index 00000000..89bea9c5
--- /dev/null
+++ b/examples/slack-modal-form/README.md
@@ -0,0 +1,51 @@
+# Slack Modal Form
+
+## Install and Run
+
+Download this example or clone [bottender](https://github.com/Yoctol/bottender).
+
+```sh
+curl https://codeload.github.com/Yoctol/bottender/tar.gz/master | tar -xz --strip=2 bottender-master/examples/slack-modal-form
+cd slack-modal-form
+```
+
+Install dependencies:
+
+```sh
+npm install
+```
+
+You must fill `SLACK_ACCESS_TOKEN` and `SLACK_SIGNING_SECRET` in your `.env` file.
+
+If you are not familiar with Slack Bot, you may refer to Bottender's doc, [Slack Setup](https://bottender.js.org/docs/channel-slack-setup), to find detailed instructions.
+
+After that, you can run the bot with this npm script:
+
+```sh
+npm run dev
+```
+
+This command starts a server listening at `http://localhost:5000` for bot development.
+
+If you successfully start the server, you get a webhook URL in the format of `https://xxxxxxxx.ngrok.io/webhooks/slack` from your terminal.
+
+## Set Webhook
+
+To set the webhook, go to [Slack Developer Console](https://api.slack.com/apps) / [YourApp] / Event Subscriptions and [Slack Developer Console](https://api.slack.com/apps) / [YourApp] / Interactivity & Shortcuts, and use the webhook URL you got from running `npm run dev` to edit Request URLs for your bot.
+
+## Idea of This Example
+
+This example is a bot running on [Slack](https://slack.com/).
+
+This example contains the following topics:
+
+- How to use modal as a form.
+
+For more information, check our [Slack guides](https://bottender.js.org/docs/en/channel-slack-block-kit).
+
+## Related Examples
+
+- [slack-home-tab](../slack-home-tab)
+- [slack-modal-update](../slack-modal-update)
+- [slack-modal-push](../slack-modal-push)
+- [slack-modal-on-home](../slack-modal-on-home)
diff --git a/examples/slack-modal-form/bottender.config.js b/examples/slack-modal-form/bottender.config.js
new file mode 100644
index 00000000..9578fa52
--- /dev/null
+++ b/examples/slack-modal-form/bottender.config.js
@@ -0,0 +1,10 @@
+module.exports = {
+  channels: {
+    slack: {
+      enabled: true,
+      path: '/webhooks/slack',
+      accessToken: process.env.SLACK_ACCESS_TOKEN,
+      signingSecret: process.env.SLACK_SIGNING_SECRET,
+    },
+  },
+};
diff --git a/examples/slack-modal-form/index.js b/examples/slack-modal-form/index.js
new file mode 100644
index 00000000..9124b47e
--- /dev/null
+++ b/examples/slack-modal-form/index.js
@@ -0,0 +1,221 @@
+const { router, route, slack } = require('bottender/router');
+
+function getBlocks(text, buttonValue) {
+  return [
+    {
+      type: 'section',
+      text: {
+        type: 'mrkdwn',
+        text,
+      },
+    },
+    {
+      type: 'actions',
+      elements: [
+        {
+          type: 'button',
+          text: {
+            type: 'plain_text',
+            text: buttonValue,
+            emoji: true,
+          },
+          value: buttonValue,
+        },
+      ],
+    },
+  ];
+}
+
+async function ShowModal(context) {
+  const { triggerId } = context.event.rawEvent;
+  await context.views.open({
+    triggerId,
+    view: {
+      type: 'modal',
+      title: {
+        type: 'plain_text',
+        text: 'Form',
+        emoji: true,
+      },
+      submit: {
+        type: 'plain_text',
+        text: 'Submit',
+        emoji: true,
+      },
+      close: {
+        type: 'plain_text',
+        text: 'Cancel',
+        emoji: true,
+      },
+      notifyOnClose: true,
+      blocks: [
+        {
+          blockId: 'date',
+          type: 'input',
+          element: {
+            actionId: 'element',
+            type: 'datepicker',
+            initialDate: '2020-07-01',
+            placeholder: {
+              type: 'plain_text',
+              text: 'Date',
+              emoji: true,
+            },
+          },
+          label: {
+            type: 'plain_text',
+            text: 'Date',
+            emoji: true,
+          },
+        },
+        {
+          blockId: 'time',
+          type: 'input',
+          element: {
+            actionId: 'element',
+            type: 'plain_text_input',
+            initialValue: '10:00',
+          },
+          label: {
+            type: 'plain_text',
+            text: 'Time',
+            emoji: true,
+          },
+        },
+        {
+          blockId: 'checked',
+          type: 'input',
+          element: {
+            actionId: 'element',
+            type: 'checkboxes',
+            options: [
+              {
+                text: {
+                  type: 'plain_text',
+                  text: '1',
+                  emoji: true,
+                },
+                value: '1',
+              },
+              {
+                text: {
+                  type: 'plain_text',
+                  text: '2',
+                  emoji: true,
+                },
+                value: '2',
+              },
+              {
+                text: {
+                  type: 'plain_text',
+                  text: '3',
+                  emoji: true,
+                },
+                value: '3',
+              },
+            ],
+          },
+          label: {
+            type: 'plain_text',
+            text: 'Check',
+            emoji: true,
+          },
+        },
+        {
+          blockId: 'radio',
+          type: 'input',
+          element: {
+            actionId: 'element',
+            type: 'radio_buttons',
+            options: [
+              {
+                text: {
+                  type: 'plain_text',
+                  text: '1',
+                  emoji: true,
+                },
+                value: '1',
+              },
+              {
+                text: {
+                  type: 'plain_text',
+                  text: '2',
+                  emoji: true,
+                },
+                value: '2',
+              },
+              {
+                text: {
+                  type: 'plain_text',
+                  text: '3',
+                  emoji: true,
+                },
+                value: '3',
+              },
+            ],
+          },
+          label: {
+            type: 'plain_text',
+            text: 'Radio',
+            emoji: true,
+          },
+        },
+      ],
+    },
+  });
+}
+
+function getFormValues(view) {
+  const v = view.state.values;
+  return {
+    date: v.date && v.date.element.selectedDate,
+    time: v.time && v.time.element.value,
+    checked: v.checked && v.checked.element.selectedOptions.map(o => o.value),
+    radio: v.radio && v.radio.element.selectedOption.value,
+  };
+}
+
+async function OnBlockActions(context) {
+  if (context.event.action.value === 'show modal') {
+    return ShowModal;
+  }
+}
+
+async function OnViewSubmission(context) {
+  const values = getFormValues(context.event.rawEvent.view);
+  await context.chat.postMessage({
+    text: `You submited the form. The contents you provided as follow:
+      date: ${values.date}
+      time: ${values.time}
+      checked: ${values.checked}
+      radio: ${values.radio}
+    `,
+  });
+}
+
+async function OnFormClosed(context) {
+  const values = getFormValues(context.event.rawEvent.view);
+  await context.chat.postMessage({
+    text: `You closed the form. The contents you provided as follow:
+    date: ${values.date}
+    time: ${values.time}
+    checked: ${values.checked}
+    radio: ${values.radio}
+    `,
+  });
+}
+
+async function Default(context) {
+  await context.chat.postMessage({
+    blocks: getBlocks('message', 'show modal'),
+  });
+}
+
+module.exports = async function App(context) {
+  return router([
+    slack.event('block_actions', OnBlockActions),
+    slack.event('view_submission', OnFormSubmitted),
+    slack.event('view_closed', OnFormClosed),
+    route('*', Default),
+  ]);
+};
diff --git a/examples/slack-modal-form/package.json b/examples/slack-modal-form/package.json
new file mode 100644
index 00000000..d39ea1d4
--- /dev/null
+++ b/examples/slack-modal-form/package.json
@@ -0,0 +1,9 @@
+{
+  "scripts": {
+    "dev": "bottender dev",
+    "start": "bottender start"
+  },
+  "dependencies": {
+    "bottender": "1.5.1-alpha.4"
+  }
+}
diff --git a/examples/slack-modal-on-home/.env b/examples/slack-modal-on-home/.env
new file mode 100644
index 00000000..c31340e0
--- /dev/null
+++ b/examples/slack-modal-on-home/.env
@@ -0,0 +1,2 @@
+SLACK_ACCESS_TOKEN=
+SLACK_SIGNING_SECRET=
diff --git a/examples/slack-modal-on-home/README.md b/examples/slack-modal-on-home/README.md
new file mode 100644
index 00000000..e7b905fb
--- /dev/null
+++ b/examples/slack-modal-on-home/README.md
@@ -0,0 +1,57 @@
+# Slack Modal on Home
+
+## Install and Run
+
+Download this example or clone [bottender](https://github.com/Yoctol/bottender).
+
+```sh
+curl https://codeload.github.com/Yoctol/bottender/tar.gz/master | tar -xz --strip=2 bottender-master/examples/slack-modal-on-home
+cd slack-modal-on-home
+```
+
+Install dependencies:
+
+```sh
+npm install
+```
+
+You must fill `SLACK_ACCESS_TOKEN` and `SLACK_SIGNING_SECRET` in your `.env` file.
+
+If you are not familiar with Slack Bot, you may refer to Bottender's doc, [Slack Setup](https://bottender.js.org/docs/channel-slack-setup), to find detailed instructions.
+
+After that, you can run the bot with this npm script:
+
+```sh
+npm run dev
+```
+
+This command starts a server listening at `http://localhost:5000` for bot development.
+
+If you successfully start the server, you get a webhook URL in the format of `https://xxxxxxxx.ngrok.io/webhooks/slack` from your terminal.
+
+## Slack App Settings
+
+To enable the home tab, go to [Slack Developer Console](https://api.slack.com/apps) / [YourApp] / App Home / Show Tabs, and enable the home tab.
+
+## Set Webhook
+
+To set the webhook, go to [Slack Developer Console](https://api.slack.com/apps) / [YourApp] / Event Subscriptions and [Slack Developer Console](https://api.slack.com/apps) / [YourApp] / Interactivity & Shortcuts, and use the webhook URL you got from running `npm run dev` to edit Request URLs for your bot.
+
+You must subscribed the `app_home_opened` event.
+
+## Idea of This Example
+
+This example is a bot running on [Slack](https://slack.com/).
+
+This example contains the following topics:
+
+- How to use modal on home tab.
+
+For more information, check our [Slack guides](https://bottender.js.org/docs/en/channel-slack-block-kit).
+
+## Related Examples
+
+- [slack-home-tab](../slack-home-tab)
+- [slack-modal-update](../slack-modal-update)
+- [slack-modal-push](../slack-modal-push)
+- [slack-modal-form](../slack-modal-form)
diff --git a/examples/slack-modal-on-home/bottender.config.js b/examples/slack-modal-on-home/bottender.config.js
new file mode 100644
index 00000000..9578fa52
--- /dev/null
+++ b/examples/slack-modal-on-home/bottender.config.js
@@ -0,0 +1,10 @@
+module.exports = {
+  channels: {
+    slack: {
+      enabled: true,
+      path: '/webhooks/slack',
+      accessToken: process.env.SLACK_ACCESS_TOKEN,
+      signingSecret: process.env.SLACK_SIGNING_SECRET,
+    },
+  },
+};
diff --git a/examples/slack-modal-on-home/index.js b/examples/slack-modal-on-home/index.js
new file mode 100644
index 00000000..fb6b8c1b
--- /dev/null
+++ b/examples/slack-modal-on-home/index.js
@@ -0,0 +1,103 @@
+const { router, route, slack } = require('bottender/router');
+
+function getBlocks(text, buttonValue) {
+  return [
+    {
+      type: 'section',
+      text: {
+        type: 'mrkdwn',
+        text,
+      },
+    },
+    {
+      type: 'actions',
+      elements: [
+        {
+          type: 'button',
+          text: {
+            type: 'plain_text',
+            text: buttonValue,
+            emoji: true,
+          },
+          value: buttonValue,
+        },
+      ],
+    },
+  ];
+}
+
+let userName = 'home';
+
+async function Home(context) {
+  await context.views.publish({
+    userId: context.session.user.id,
+    view: {
+      type: 'home',
+      blocks: getBlocks(`Hello, *${userName}*.`, 'show modal'),
+    },
+  });
+}
+
+async function ShowModal(context) {
+  const { triggerId } = context.event.rawEvent;
+  await context.views.open({
+    triggerId,
+    view: {
+      type: 'modal',
+      title: {
+        type: 'plain_text',
+        text: 'What is your name?',
+        emoji: true,
+      },
+      submit: {
+        type: 'plain_text',
+        text: 'Submit',
+        emoji: true,
+      },
+      blocks: [
+        {
+          blockId: 'name',
+          type: 'input',
+          element: {
+            actionId: 'element',
+            type: 'plain_text_input',
+            placeholder: {
+              type: 'plain_text',
+              text: 'type your name here',
+            },
+          },
+          label: {
+            type: 'plain_text',
+            text: 'Name',
+            emoji: true,
+          },
+        },
+      ],
+    },
+  });
+}
+
+async function OnBlockActions(context) {
+  if (context.event.action.value === 'show modal') {
+    return ShowModal;
+  }
+}
+
+async function OnViewSubmission(context) {
+  const v = context.event.rawEvent.view.state.values;
+  userName = (v.name && v.name.element.value) || userName;
+  return Home;
+}
+
+async function Default(context) {
+  await context.sendText('Hello World');
+}
+
+module.exports = async function App(context) {
+  return router([
+    slack.event('app_home_opened', Home),
+    slack.event('block_actions', OnBlockActions),
+    slack.event('view_submission', OnViewSubmission),
+    route('*', Default),
+  ]);
+};
diff --git a/examples/slack-modal-on-home/package.json b/examples/slack-modal-on-home/package.json
new file mode 100644
index 00000000..d39ea1d4
--- /dev/null
+++ b/examples/slack-modal-on-home/package.json
@@ -0,0 +1,9 @@
+{
+  "scripts": {
+    "dev": "bottender dev",
+    "start": "bottender start"
+  },
+  "dependencies": {
+    "bottender": "1.5.1-alpha.4"
+  }
+}
diff --git a/examples/slack-modal-update/.env b/examples/slack-modal-update/.env
new file mode 100644
index 00000000..c31340e0
--- /dev/null
+++ b/examples/slack-modal-update/.env
@@ -0,0 +1,2 @@
+SLACK_ACCESS_TOKEN=
+SLACK_SIGNING_SECRET=
diff --git a/examples/slack-modal-update/README.md b/examples/slack-modal-update/README.md
new file mode 100644
index 00000000..00c719e9
--- /dev/null
+++ b/examples/slack-modal-update/README.md
@@ -0,0 +1,51 @@
+# Slack Modal Update
+
+## Install and Run
+
+Download this example or clone [bottender](https://github.com/Yoctol/bottender).
+
+```sh
+curl https://codeload.github.com/Yoctol/bottender/tar.gz/master | tar -xz --strip=2 bottender-master/examples/slack-modal-update
+cd slack-modal-update
+```
+
+Install dependencies:
+
+```sh
+npm install
+```
+
+You must fill `SLACK_ACCESS_TOKEN` and `SLACK_SIGNING_SECRET` in your `.env` file.
+
+If you are not familiar with Slack Bot, you may refer to Bottender's doc, [Slack Setup](https://bottender.js.org/docs/channel-slack-setup), to find detailed instructions.
+
+After that, you can run the bot with this npm script:
+
+```sh
+npm run dev
+```
+
+This command starts a server listening at `http://localhost:5000` for bot development.
+
+If you successfully start the server, you get a webhook URL in the format of `https://xxxxxxxx.ngrok.io/webhooks/slack` from your terminal.
+
+## Set Webhook
+
+To set the webhook, go to [Slack Developer Console](https://api.slack.com/apps) / [YourApp] / Event Subscriptions and [Slack Developer Console](https://api.slack.com/apps) / [YourApp] / Interactivity & Shortcuts, and use the webhook URL you got from running `npm run dev` to edit Request URLs for your bot.
+
+## Idea of This Example
+
+This example is a bot running on [Slack](https://slack.com/).
+
+This example contains the following topics:
+
+- How to update a modal view.
+
+For more information, check our [Slack guides](https://bottender.js.org/docs/en/channel-slack-block-kit).
+
+## Related Examples
+
+- [slack-home-tab](../slack-home-tab)
+- [slack-modal-push](../slack-modal-push)
+- [slack-modal-form](../slack-modal-form)
+- [slack-modal-on-home](../slack-modal-on-home)
diff --git a/examples/slack-modal-update/bottender.config.js b/examples/slack-modal-update/bottender.config.js
new file mode 100644
index 00000000..9578fa52
--- /dev/null
+++ b/examples/slack-modal-update/bottender.config.js
@@ -0,0 +1,10 @@
+module.exports = {
+  channels: {
+    slack: {
+      enabled: true,
+      path: '/webhooks/slack',
+      accessToken: process.env.SLACK_ACCESS_TOKEN,
+      signingSecret: process.env.SLACK_SIGNING_SECRET,
+    },
+  },
+};
diff --git a/examples/slack-modal-update/index.js b/examples/slack-modal-update/index.js
new file mode 100644
index 00000000..f091fee9
--- /dev/null
+++ b/examples/slack-modal-update/index.js
@@ -0,0 +1,79 @@
+const { router, route, slack } = require('bottender/router');
+
+function getBlocks(text, buttonValue) {
+  return [
+    {
+      type: 'section',
+      text: {
+        type: 'mrkdwn',
+        text,
+      },
+    },
+    {
+      type: 'actions',
+      elements: [
+        {
+          type: 'button',
+          text: {
+            type: 'plain_text',
+            text: buttonValue,
+            emoji: true,
+          },
+          value: buttonValue,
+        },
+      ],
+    },
+  ];
+}
+
+async function ShowModal(context) {
+  const { triggerId } = context.event.rawEvent;
+  await context.views.open({
+    triggerId,
+    view: {
+      type: 'modal',
+      title: {
+        type: 'plain_text',
+        text: 'Modal Title',
+      },
+      blocks: getBlocks('in modal', 'update modal'),
+    },
+  });
+}
+
+async function UpdateModal(context) {
+  const viewId = context.event.rawEvent.view.id;
+  await context.views.update({
+    viewId,
+    view: {
+      type: 'modal',
+      title: {
+        type: 'plain_text',
+        text: 'Modal Title',
+      },
+      blocks: getBlocks('modal updated', 'update modal again'),
+    },
+  });
+}
+
+async function OnBlockActions(context) {
+  if (context.event.action.value === 'show modal') {
+    return ShowModal;
+  }
+  if (context.event.action.value === 'update modal') {
+    return UpdateModal;
+  }
+}
+
+async function Default(context) {
+  await context.chat.postMessage({
+    blocks: getBlocks('message', 'show modal'),
+  });
+}
+
+module.exports = async function App(context) {
+  return router([
+    slack.event('block_actions', OnBlockActions),
+    route('*', Default),
+  ]);
+};
diff --git a/examples/slack-modal-update/package.json b/examples/slack-modal-update/package.json
new file mode 100644
index 00000000..d39ea1d4
--- /dev/null
+++ b/examples/slack-modal-update/package.json
@@ -0,0 +1,9 @@
+{
+  "scripts": {
+    "dev": "bottender dev",
+    "start": "bottender start"
+  },
+  "dependencies": {
+    "bottender": "1.5.1-alpha.4"
+  }
+}
diff --git a/packages/bottender/src/line/LineContext.ts b/packages/bottender/src/line/LineContext.ts
index cc8960dc..9315f6eb 100644
--- a/packages/bottender/src/line/LineContext.ts
+++ b/packages/bottender/src/line/LineContext.ts
@@ -242,6 +242,29 @@ class LineContext extends Context<LineClient, LineEvent> {
     }
   }
 
+  /**
+   * Gets the member count in group/room.
+   *
+   */
+  async getMembersCount(): Promise<number | null> {
+    if (!this._session) {
+      warning(
+        false,
+        'getMembersCount: should not be called in context without session'
+      );
+      return null;
+    }
+
+    switch (this._session.type) {
+      case 'room':
+        return this._client.getRoomMembersCount(this._session.room.id);
+      case 'group':
+        return this._client.getGroupMembersCount(this._session.group.id);
+      default:
+        return 1;
+    }
+  }
+
   /**
    * 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.


Test patch:
diff --git a/packages/bottender/src/line/__tests__/LineContext.spec.ts b/packages/bottender/src/line/__tests__/LineContext.spec.ts
index abfd2f0d..c2380a17 100644
--- a/packages/bottender/src/line/__tests__/LineContext.spec.ts
+++ b/packages/bottender/src/line/__tests__/LineContext.spec.ts
@@ -1285,6 +1285,44 @@ describe('member IDs APIs', () => {
       expect(warning).toBeCalled();
     });
   });
+
+  describe('#getMembersCount', () => {
+    it('not get profile without session', async () => {
+      const { context, client } = setup({ session: null });
+
+      await context.getMembersCount();
+
+      expect(client.getGroupMembersCount).not.toBeCalled();
+      expect(client.getRoomMembersCount).not.toBeCalled();
+      expect(warning).toBeCalled();
+    });
+
+    it('get member count in group', async () => {
+      const { context, client } = setup({ session: groupSession });
+
+      await context.getMembersCount();
+
+      expect(client.getGroupMembersCount).toBeCalledWith('fakeGroupId');
+    });
+
+    it('get member count in room', async () => {
+      const { context, client } = setup({ session: roomSession });
+
+      await context.getMembersCount();
+
+      expect(client.getRoomMembersCount).toBeCalledWith('fakeRoomId');
+    });
+
+    it('get member count = 1 in private chat', async () => {
+      const { context, client } = setup({ session: userSession });
+
+      const res = await context.getMembersCount();
+
+      expect(client.getGroupMembersCount).not.toBeCalled();
+      expect(client.getRoomMembersCount).not.toBeCalled();
+      expect(res).toBe(1);
+    });
+  });
 });
 
 describe('ruchmenu APIs', () => {