Просмотр исходного кода

Merge branch 'feat/GW-6710-slackbot-togetter-command' into feat/GW-6710-slackbot-togetter-command-choose-growi

hakumizuki 4 лет назад
Родитель
Сommit
5c605efa8e

+ 3 - 3
src/server/routes/apiv3/slack-integration.js

@@ -157,12 +157,12 @@ module.exports = (crowi) => {
         await togetterHandler.handleCommand(client, body, args, newLimit);
         break;
       }
-      case 'togetterCreatePage': {
+      case 'togetter:createPage': {
         await crowi.slackBotService.togetterCreatePageInGrowi(client, payload);
         break;
       }
-      case 'togetterCancelPageCreation': {
-        console.log('Cancel here');
+      case 'togetter:cancel': {
+        await crowi.slackBotService.togetterCancel(client, payload);
         break;
       }
       default:

+ 19 - 25
src/server/service/slack-command-handler/togetter.js

@@ -1,9 +1,9 @@
 const {
-  inputBlock, actionsBlock, buttonElement, checkboxesElementOption,
+  inputBlock, actionsBlock, buttonElement, markdownSectionBlock,
 } = require('@growi/slack');
-const { fromUnixTime, format } = require('date-fns');
+const { format } = require('date-fns');
 
-module.exports = () => {
+module.exports = (crowi) => {
   const BaseSlackCommandHandler = require('./slack-command-handler');
   const handler = new BaseSlackCommandHandler();
 
@@ -25,34 +25,17 @@ module.exports = () => {
 
   handler.togetterMessageBlocks = function(messages, body, args, limit) {
     return [
-      inputBlock(this.togetterCheckboxesElement(messages), 'selected_messages', 'Select massages to use.'),
-      actionsBlock(buttonElement({ text: 'Show more', actionId: 'togetterShowMore', value: JSON.stringify({ body, args, limit }) })),
+      markdownSectionBlock('Select the oldest and latest datetime of the messages to use.'),
+      inputBlock(this.plainTextInputElementWithInitialTime('oldest'), 'oldest', 'Oldest datetime'),
+      inputBlock(this.plainTextInputElementWithInitialTime('latest'), 'latest', 'Latest datetime'),
       inputBlock(this.togetterInputBlockElement('page_path', '/'), 'page_path', 'Page path'),
       actionsBlock(
-        buttonElement({ text: 'Cancel', actionId: 'togetterCancelPageCreation' }),
-        buttonElement({ text: 'Create page', actionId: 'togetterCreatePage', color: 'primary' }),
+        buttonElement({ text: 'Cancel', actionId: 'togetter:cancel' }),
+        buttonElement({ text: 'Create page', actionId: 'togetter:createPage', style: 'primary' }),
       ),
     ];
   };
 
-  handler.togetterCheckboxesElement = function(messages) {
-    return {
-      type: 'checkboxes',
-      options: this.togetterCheckboxesElementOptions(messages),
-      action_id: 'checkboxes_changed',
-    };
-  };
-
-  handler.togetterCheckboxesElementOptions = function(messages) {
-    const options = messages
-      .sort((a, b) => { return a.ts - b.ts })
-      .map((message, index) => {
-        const date = fromUnixTime(message.ts);
-        return checkboxesElementOption(`*${message.user}*  ${format(new Date(date), 'yyyy/MM/dd HH:mm:ss')}`, message.text, `selected-${index}`);
-      });
-    return options;
-  };
-
   /**
    * Plain-text input element
    * https://api.slack.com/reference/block-kit/block-elements#input
@@ -68,5 +51,16 @@ module.exports = () => {
     };
   };
 
+  handler.plainTextInputElementWithInitialTime = function(actionId) {
+    const tzDateSec = new Date().getTime();
+    const grwTzoffset = crowi.appService.getTzoffset() * 60 * 1000;
+    const initialDateTime = format(new Date(tzDateSec - grwTzoffset), 'yyyy/MM/dd-HH:mm');
+    return {
+      type: 'plain_text_input',
+      action_id: actionId,
+      initial_value: initialDateTime,
+    };
+  };
+
   return handler;
 };

+ 128 - 15
src/server/service/slackbot.js

@@ -5,7 +5,7 @@ const axios = require('axios');
 
 const { markdownSectionBlock, divider } = require('@growi/slack');
 const { reshapeContentsBody } = require('@growi/slack');
-const { formatDistanceStrict } = require('date-fns');
+const { formatDistanceStrict, parse, format } = require('date-fns');
 
 const S2sMessage = require('../models/vo/s2s-message');
 const S2sMessageHandlable = require('./s2s-messaging/handlable');
@@ -415,25 +415,138 @@ class SlackBotService extends S2sMessageHandlable {
   }
 
   async togetterCreatePageInGrowi(client, payload) {
-    const { response_url: responseUrl } = payload;
-    const selectedOptions = payload.state.values.selected_messages.checkboxes_changed.selected_options;
-    const messages = selectedOptions.map((option) => {
-      const header = option.text.text.concat('\n');
-      const body = option.description.text.concat('\n');
-      return header.concat(body);
+    let result = [];
+    const channel = payload.channel.id;
+    try {
+      // validate form
+      const { path, oldest, latest } = await this.togetterValidateForm(client, payload);
+      // get messages
+      result = await this.togetterGetMessages(client, payload, channel, path, latest, oldest);
+      // clean messages
+      const cleanedContents = await this.togetterCleanMessages(result.messages);
+
+      const contentsBody = cleanedContents.join('');
+      // create and send url message
+      await this.togetterCreatePageAndSendPreview(client, payload, path, channel, contentsBody);
+    }
+    catch (err) {
+      await client.chat.postMessage({
+        channel: payload.user.id,
+        text: err.message,
+        blocks: [
+          markdownSectionBlock(err.message),
+        ],
+      });
+      return;
+    }
+  }
+
+  async togetterGetMessages(client, payload, channel, path, latest, oldest) {
+    const result = await client.conversations.history({
+      channel,
+      latest,
+      oldest,
+      limit: 100,
+      inclusive: true,
     });
-    let path = '';
-    let channelId = '';
-    if (payload.type === 'block_actions' && payload.actions[0].action_id === 'togetterCreatePage') {
-      path = payload.state.values.page_path.page_path.value;
-      channelId = payload.channel.id;
+
+    // return if no message found
+    if (!result.messages.length) {
+      throw new Error('No message found from togetter command. Try again.');
+    }
+    return result;
+  }
+
+  async togetterValidateForm(client, payload) {
+    const grwTzoffset = this.crowi.appService.getTzoffset() * 60;
+    const path = payload.state.values.page_path.page_path.value;
+    let oldest = payload.state.values.oldest.oldest.value;
+    let latest = payload.state.values.latest.latest.value;
+    oldest = oldest.trim();
+    latest = latest.trim();
+    if (!path) {
+      throw new Error('Page path is required.');
+    }
+    /**
+     * RegExp for datetime yyyy/MM/dd-HH:mm
+     * @see https://regex101.com/r/xiQoTb/1
+     */
+    const regexpDatetime = new RegExp(/^[12]\d\d\d\/(0[1-9]|1[012])\/(0?[1-9]|[12][0-9]|3[01])-(0[0-9]|1[012]):[0-5][0-9]$/);
+
+    if (!regexpDatetime.test(oldest)) {
+      throw new Error('Datetime format for oldest must be yyyy/MM/dd-HH:mm');
+    }
+    if (!regexpDatetime.test(latest)) {
+      throw new Error('Datetime format for latest must be yyyy/MM/dd-HH:mm');
+    }
+    oldest = parse(oldest, 'yyyy/MM/dd-HH:mm', new Date()).getTime() / 1000 + grwTzoffset;
+    // + 60s in order to include messages between hh:mm.00s and hh:mm.59s
+    latest = parse(latest, 'yyyy/MM/dd-HH:mm', new Date()).getTime() / 1000 + grwTzoffset + 60;
+
+    if (oldest > latest) {
+      throw new Error('Oldest datetime must be older than the latest date time.');
     }
-    const contentsBody = messages.join('');
-    // dismiss
+
+    return { path, oldest, latest };
+  }
+
+  async togetterCleanMessages(messages) {
+    const cleanedContents = [];
+    let lastMessage = {};
+    const grwTzoffset = this.crowi.appService.getTzoffset() * 60;
+    messages
+      .sort((a, b) => {
+        return a.ts - b.ts;
+      })
+      .forEach((message) => {
+        // increment contentsBody while removing the same headers
+        // exclude header
+        const lastMessageTs = Math.floor(lastMessage.ts / 60);
+        const messageTs = Math.floor(message.ts / 60);
+        if (lastMessage.user === message.user && lastMessageTs === messageTs) {
+          cleanedContents.push(`${message.text}\n`);
+        }
+        // include header
+        else {
+          const ts = (parseInt(message.ts) - grwTzoffset) * 1000;
+          const time = format(new Date(ts), 'h:mm a');
+          cleanedContents.push(`${message.user}  ${time}\n${message.text}\n`);
+          lastMessage = message;
+        }
+      });
+    return cleanedContents;
+  }
+
+  async togetterCreatePageAndSendPreview(client, payload, path, channel, contentsBody) {
+    try {
+      await this.createPage(client, payload, path, channel, contentsBody);
+      // send preview to dm
+      await client.chat.postMessage({
+        channel: payload.user.id,
+        text: 'Preview from togetter command',
+        blocks: [
+          markdownSectionBlock('*Preview*'),
+          divider(),
+          markdownSectionBlock(contentsBody),
+          divider(),
+        ],
+      });
+      // dismiss message
+      const responseUrl = payload.response_url;
+      axios.post(responseUrl, {
+        delete_original: true,
+      });
+    }
+    catch (err) {
+      throw new Error('Error occurred while creating a page.');
+    }
+  }
+
+  async togetterCancel(client, payload) {
+    const responseUrl = payload.response_url;
     axios.post(responseUrl, {
       delete_original: true,
     });
-    await this.createPage(client, payload, path, channelId, contentsBody);
   }
 
 }

+ 4 - 20
yarn.lock

@@ -2762,26 +2762,10 @@
     p-queue "^6.6.1"
     p-retry "^4.0.0"
 
-"@slack/web-api@^6.1.0":
-  version "6.1.0"
-  resolved "https://registry.yarnpkg.com/@slack/web-api/-/web-api-6.1.0.tgz#27a17f61eb72100d6722ff17f581349c41d19b5f"
-  integrity sha512-9MVHw+rDBaFvkvzm8lDNH/nlkvJCDKRIjFGMdpbyZlVLsm4rcht4qyiL71bqdyLATHXJnWknb/sl0FQGLLobIA==
-  dependencies:
-    "@slack/logger" ">=1.0.0 <3.0.0"
-    "@slack/types" "^1.7.0"
-    "@types/is-stream" "^1.1.0"
-    "@types/node" ">=12.0.0"
-    axios "^0.21.1"
-    eventemitter3 "^3.1.0"
-    form-data "^2.5.0"
-    is-stream "^1.1.0"
-    p-queue "^6.6.1"
-    p-retry "^4.0.0"
-
-"@slack/web-api@^6.2.3":
-  version "6.2.3"
-  resolved "https://registry.yarnpkg.com/@slack/web-api/-/web-api-6.2.3.tgz#063ca32987587d1879520badab485796be621e77"
-  integrity sha512-Qt0JUSuT1RKFYRpMfbP0xKkqWoXGzmEa4N+7H5oB9eH228ABn1sM3LDNW7ZQiLs5jpDuzHAp7dSxAZpb+ZfO/w==
+"@slack/web-api@^6.1.0", "@slack/web-api@^6.2.3":
+  version "6.3.0"
+  resolved "https://registry.yarnpkg.com/@slack/web-api/-/web-api-6.3.0.tgz#a6e592d2bed49666ac9c31cdc6ab0c04443e935e"
+  integrity sha512-EYJ5PuYtSzbrnFCoVE2+JzdM5NTPBlgJYpPGbiVyAved7if4tnbgZpeQT/Vg6E18w5RrFOZScbQaEU78GWmVDA==
   dependencies:
     "@slack/logger" "^3.0.0"
     "@slack/types" "^2.0.0"