MNTSQ Techブログ

リーガルテック・カンパニー「MNTSQ(モンテスキュー)」のTechブログです。

MNTSQのSlackに住まうbot - Hot Docs

MNTSQ Tech Blog TOP > 記事一覧 > MNTSQのSlackに住まうbot - Hot Docs

新しい仲間

前回、MNTSQのSlackにいるいくつかのbotを紹介した。

tech.mntsq.co.jp

今日、そこに新しい仲間が加わったので紹介しよう。その名も "Hot Docs" だ。

Hot Docsとは

Hot Docsとは、Google Driveのファイルを再帰的に探索し、短時間にたくさんコメントがついたDocsをSlackに通知するbotである。こんな感じだ。 f:id:takumi-hirata-mntsq:20220310164758p:plain

なぜ作ったか

MNTSQではGoogle Docsを使って非同期でコミュニケーションを取ることがある。誰かが提案やログをDocsで作り、他のメンバーがそこにコメントしまくる、というスタイルだ。 しかしこれでは議論がDocsに閉じてしまい、Docsの存在を知らないメンバーから見えないというOpenness観点の問題がある。 そこで議論が活発なDocsをピックし、Slackに通知する仕組みを作った。

導入してみて

好評だった。 f:id:takumi-hirata-mntsq:20220310174403p:plain f:id:takumi-hirata-mntsq:20220310174326p:plain f:id:takumi-hirata-mntsq:20220310173749p:plain f:id:takumi-hirata-mntsq:20220310173708p:plain

筆者自身、異なるドメインのHot Issueを知ることで事業理解に役立っていると実感する。

Hot Docsの仕組み

Hot DocsはGoogle App Scriptで作られており、30分おきにDriveを探索する。 f:id:takumi-hirata-mntsq:20220310171502p:plain

ソースコードは次のとおり。再帰Generator listFiles で取得したファイルを2users以上かつ5comments以上という条件でフィルタしSlackに投稿する。

function notifyHotDocs() {
  notifyHotFiles("application/vnd.google-apps.document");
}

function notifyHotSlides() {
  notifyHotFiles("application/vnd.google-apps.presentation");
}

function notifyHotFiles(mimeType) {
  const updatedMin = new Date(new Date().getTime() - 30 * 60 * 1000);
  const files = Array.from(listFiles(FOLDER_ID, mimeType, updatedMin), file => {
    const comments = Drive.Comments.list(file.getId(), {
      maxResults: 100,
      updatedMin: updatedMin.toISOString()
    }).items;
    return {
      name: file.getName(),
      url: file.getUrl(),
      comments: comments,
      authors: [...new Set(comments.map(x => x.author.displayName))],
      commentsLength: comments.length + comments.reduce((n, x) => n + x.replies.length, 0)
    };
  })
  .filter(x => x.authors.length >= 2 && x.comments.length >= 5)
  .sort((a, b) => b.comments.length - a.comments.length);
  if (files.length === 0) return;
  const colors = ["#fcc800", "#f3981d", "#ea553a"]; // コメント数に応じて色を変える
  const attachments = files.map(x => {
    let colorIndex = Math.floor(x.comments.length / 10);
    colorIndex = colorIndex < 0 ? 0 : colorIndex > 2 ? 2 : colorIndex;
    return {
      author_name: x.authors.slice(0, 3).join(", ") + (x.authors.length > 3 ? ", etc." : ""),
      title: x.name,
      title_link: x.url,
      color: colors[colorIndex]
    };
  });
  postMessage(CHANNEL_ID, "なんか盛り上がってるみたい!", attachments, ":hotdog:", "Hot Docs");
}

function *listFiles(parentFolderId, mimeType, updatedMin) {
  const folder = DriveApp.getFolderById(parentFolderId);
  const files = folder.getFilesByType(mimeType);
  while (files.hasNext()) {
    const file = files.next();
    if (file.getLastUpdated() > updatedMin) {
      yield file;
    }
  }
  const folders = folder.getFolders();
  while (folders.hasNext()) {
    const folder = folders.next();
    yield* listFiles(folder.getId(), mimeType, updatedMin);
  }
}

function postMessage(channel, text, attachments, icon_emoji, username) {
  const url = "https://slack.com/api/chat.postMessage";
  let options = {
    "method" : "post",
    "contentType": "application/x-www-form-urlencoded",
    "payload" : {
      "token": SLACK_BOT_POST_TOKEN,
      "channel": channel,
      "text": text,
      "attachments": JSON.stringify(attachments),
      "icon_emoji": icon_emoji,
      "username": username
    }
  };
  let response = UrlFetchApp.fetch(url, options);
  let data = JSON.parse(response.getContentText());
  return data;
}

おしまい

あなたの会社でもぜひ試してほしい。