GAS:GoogleAppsScript

プログラミング知識ゼロでもできます!Geminiと一緒に作るChatwork日報の自動集計&ダッシュボード

この記事は約15分で読めます。

GASで作成したChatwork連携のWEBアプリ「日報をChatwork(チャットツール)で報告しているけれど、過去の記録がどんどん流れていってしまい、誰がどれくらい投稿しているのか振り返りにくい…」 そんなお悩みはありませんか?
今回は、Googleの「GAS(Google Apps Script)」とAIアシスタント「Gemini」の力を借りて、Chatworkの日報をGoogleスプレッドシートに自動保存し、さらに見やすいダッシュボード(グラフ付き画面)まで作ってしまう方法をご紹介します。
Geminiに「こういうことがしたい!」と相談しながら進めた結果、あっという間に実用的なシステムが完成してしまいました。
その感動の体験と、実際の作り方をステップバイステップで解説します。

今回作るシステムの完成イメージ

Chatworkの投稿をGASで管理する

  1. 自動蓄積: 社員が特定のChatworkグループに日報を投稿した瞬間、自動的にスプレッドシートへ時系列で保存されます。

  2. ダッシュボード化: 保存されたデータをもとに、「社員別の投稿数ランキング(グラフ)」と「最新100件の投稿一覧」を表示する専用のWEB画面を生成します。

Chatworkの投稿数と内容をWEBアプリで表示

できあがったWEBアプリのダッシュボードはこんな感じです。

準備するもの

  • Googleアカウント(Google Workspace利用なら最適です)

  • Chatworkアカウント(管理者権限、またはAPI申請ができること)

  • Gemini(頼れるAIアシスタント!)

構築ステップ:たったの5ステップで完成!

Step 1:受け皿となるスプレッドシートの準備

  1. Googleスプレッドシートを新規作成し、タイトルを「日報集計システム」などとします。

  2. 画面左下のタブをダブルクリックして、シート名を「日報DB」に変更します。

    • 1行目〜2行目は自由に使ってOKです。(例:「A1: M社日報データベース」など)

  3. 新しいシートを追加し、シート名を「名簿」とします。

    • A列に「ChatworkのアカウントID」、B列に「氏名」を入力しておきます。(こうすることで、システムが自動でIDを名前に変換してくれます)

  4. スプレッドシートのURLから、/d//edit の間にある長い文字列(スプレッドシートID)をコピーしてメモしておきます。

Step 2:Chatworkの情報(APIトークンとルームID)を取得

  1. Chatworkを開き、右上のプロフィール名 >「サービス連携」>「APIトークン」から、APIトークンをコピーしてメモします。

  2. 日報を集約したい対象のグループチャットを開き、URLの #!rid の後ろにある数字(ルームID)をメモします。(例:123456789

Step 3:GAS(Google Apps Script)の設定

ここからがプログラミングの領域ですが、コードはすべてコピペでOKです!

  1. スプレッドシートの上部メニュー「拡張機能」>「Apps Script」をクリックします。

  2. 無題のプロジェクトが開くので、最初から書かれているコードをすべて消し、以下のコードを貼り付けます。

JavaScript

// =========================================================
// ① Chatworkからの自動受信プログラム(Webhook用)
// =========================================================
function doPost(e) {
  var json = JSON.parse(e.postData.contents);
  var message = json.webhook_event.body;
  var accountId = json.webhook_event.account_id;
  var roomId = json.webhook_event.room_id;
  var sendTime = json.webhook_event.send_time;

  // ★あなたの対象ルームIDに変更してください
  var targetRoomId = 123456789;

  if (roomId === targetRoomId) {
    var date = new Date(sendTime * 1000);
    var formattedDate = Utilities.formatDate(date, 'Asia/Tokyo', 'yyyy/MM/dd HH:mm:ss');

    // ★あなたのスプレッドシートIDに変更してください
    var sheetId = 'YOUR_SPREADSHEET_ID_HERE';
    var spreadsheet = SpreadsheetApp.openById(sheetId);
    var sheet = spreadsheet.getSheetByName('日報DB');
    var rosterSheet = spreadsheet.getSheetByName('名簿');

    // 名簿から名前を検索
    var rosterData = rosterSheet.getDataRange().getValues();
    var senderName = "未登録"; 
    for (var i = 1; i < rosterData.length; i++) {
      if (String(rosterData[i][0]) === String(accountId)) {
        senderName = rosterData[i][1];
        break; 
      }
    }

    // シートに書き込み
    sheet.appendRow([formattedDate, accountId, senderName, message]);
  }

  return ContentService.createTextOutput("OK");
}

// =========================================================
// ② ダッシュボード(画面)を表示するプログラム
// =========================================================
function doGet() {
  return HtmlService.createHtmlOutputFromFile('index')
    .setTitle('社内日報ダッシュボード')
    .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}

// =========================================================
// ③ ダッシュボードに集計データを送るプログラム
// =========================================================
function getDashboardData() {
  // ★あなたのスプレッドシートIDに変更してください
  const sheetId = 'YOUR_SPREADSHEET_ID_HERE';
  const spreadsheet = SpreadsheetApp.openById(sheetId);
  const sheet = spreadsheet.getSheetByName('日報DB');
  const rosterSheet = spreadsheet.getSheetByName('名簿');
  
  const rosterData = rosterSheet.getDataRange().getValues();
  const counts = {};
  for (let i = 1; i < rosterData.length; i++) {
    const name = rosterData[i][1];
    if (name) counts[name] = 0; 
  }

  const values = sheet.getDataRange().getValues();
  const dataRows = values.slice(2); // 3行目以降をデータとして扱う
  
  dataRows.forEach(row => {
    const name = row[2]; // C列(名前)
    if (name && counts[name] !== undefined) {
      counts[name] += 1;
    } else if (name && name !== "未登録" && name !== "名前") {
      counts[name] = (counts[name] || 0) + 1;
    }
  });

  let sortedCounts = [];
  for (let name in counts) {
    sortedCounts.push({ name: name, count: counts[name] });
  }
  // 投稿数が多い順(降順)に並び替え
  sortedCounts.sort((a, b) => b.count - a.count);

  const chartData = [['名前', '投稿数']];
  let currentRank = 1;
  let previousCount = -1;
  
  sortedCounts.forEach((item, index) => {
    if (item.count !== previousCount) {
      currentRank = index + 1;
    }
    item.rank = currentRank;
    chartData.push([`${currentRank}${item.name}`, item.count]);
    previousCount = item.count;
  });

  const latestPosts = dataRows.reverse().slice(0, 100).map(row => {
    return {
      date: Utilities.formatDate(new Date(row[0]), "JST", "MM/dd HH:mm"),
      name: row[2],
      body: row[3]
    };
  });

  return { chart: chartData, posts: latestPosts };
}

※コード内の 123456789(ルームID)と YOUR_SPREADSHEET_ID_HERE(スプレッドシートID)を、Step 1・2でメモしたものに書き換えてください。

Step 4:ダッシュボード画面(HTML)の作成

  1. GASエディタの左側「ファイル」の横にある「+」を押し、「HTML」を選択します。

  2. ファイル名を index にします。

  3. 以下のコードをすべて貼り付け、上部の「保存(フロッピーアイコン)」を押します。

HTML(index.html)

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
    <style>
      body { background-color: #f8f9fa; font-family: 'Helvetica Neue', Arial, sans-serif; }
      .container { max-height: 100%; padding-top: 30px; }
      .dashboard-card { background: white; border-radius: 12px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); padding: 20px; margin-bottom: 25px; }
      .table-container { max-height: 500px; overflow-y: auto; }
      h1 { color: #005088; font-weight: bold; margin-bottom: 0; }
      .date-display { font-size: 1.2rem; color: #666; }
    </style>
  </head>
  <body>
    <div class="container">
      <div class="d-flex justify-content-between align-items-center mb-4">
        <div>
          <h1>社内日報ダッシュボード</h1>
          <a href="https://www.chatwork.com/#!rid123456789" target="_blank" class="btn btn-outline-primary btn-sm mt-2">
            Chatwork 日報ルームを開く
          </a>
        </div>
        <div class="date-display" id="todayDate"></div>
      </div>

      <div class="dashboard-card">
        <h3>投稿数ランキング</h3>
        <div id="chart_div" style="width: 100%; height: 400px;"></div>
      </div>

      <div class="dashboard-card">
        <h3>最新100件の投稿(新しい順)</h3>
        <div class="table-container">
          <table class="table table-hover">
            <thead class="table-light" style="position: sticky; top: 0;">
              <tr>
                <th style="width: 20%;">日時</th>
                <th style="width: 15%;">投稿者</th>
                <th>内容</th>
              </tr>
            </thead>
            <tbody id="postList"></tbody>
          </table>
        </div>
      </div>
    </div>

    <script>
      const now = new Date();
      document.getElementById('todayDate').innerText = now.toLocaleDateString('ja-JP', { year: 'numeric', month: 'long', day: 'numeric', weekday: 'long' });

      google.charts.load('current', {'packages':['corechart']});
      google.charts.setOnLoadCallback(refreshData);

      function refreshData() {
        google.script.run.withSuccessHandler(drawChart).getDashboardData();
      }

      function drawChart(data) {
        const chartData = google.visualization.arrayToDataTable(data.chart);
        const options = {
          hAxis: { title: '投稿数', minValue: 0 },
          vAxis: { title: '名前' },
          legend: { position: 'none' },
          colors: ['#005088'],
          animation: { startup: true, duration: 1000, easing: 'out' }
        };
        const chart = new google.visualization.BarChart(document.getElementById('chart_div'));
        chart.draw(chartData, options);

        const listBody = document.getElementById('postList');
        data.posts.forEach(row => {
          const tr = document.createElement('tr');
          tr.innerHTML = `<td>${row.date}</td><td><span class="badge bg-info text-dark">${row.name}</span></td><td>${row.body.replace(/\n/g, '<br>')}</td>`;
          listBody.appendChild(tr);
        });
      }
    </script>
  </body>
</html>
  1. 画面右上の青いボタン「デプロイ」>「新しいデプロイ」をクリック。

  2. 歯車アイコンから「ウェブアプリ」を選択し、「アクセスできるユーザー」を「全員」にして「デプロイ」をクリックします。(アクセス承認が出たら許可します)

  3. 完了画面に表示される「ウェブアプリのURL」をコピーします。これがシステムとダッシュボードの窓口になります。

Step 5:ChatworkにWebhook(自動送信)を設定する

  1. Chatworkの右上の名前 >「サービス連携」>「Webhook」>「新規作成」を開きます。

  2. 以下のように設定します。

    • Webhook URL: Step 4でコピーした「ウェブアプリのURL」を貼り付け。

    • イベント: 「ルームイベント」を選び、「メッセージ作成」にチェック。対象のルームID(例:123456789)を入力。

  3. 「作成」を押せば連携完了です!

試しにChatworkに投稿してみてください。数秒後、スプレッドシートにデータが自動で入り、ダッシュボードのURLを開くとグラフと一覧が表示されるはずです。

専属プログラマー「Gemini」との共同作業で感じたこと

実はこのシステム、私一人で作ったわけではありません。すべてAIアシスタントのGeminiに相談しながら作りました。

  • ざっくりした要望も形にしてくれる: 「ChatworkのIDじゃなくて名前にしたい」「グラフのランキングを1位から順に並べたい」といったフワッとした要望を伝えるだけで、Geminiが意図を汲み取ってコードを修正してくれました。

  • エラーが出ても安心: プログラムを実行して赤いエラー文字が出た時も、そのエラーメッセージをそのままGeminiにコピペするだけで、「これはこういう原因だから、ここを直せば大丈夫ですよ!」と優しく、的確に解決策を教えてくれました。

GASはAIエージェント

まるで、優秀で優しい専属のプログラマーがずっと隣にいて伴走してくれているような感覚です。 「やりたいことはあるけれど、技術的なハードルが高い」と感じている方は、ぜひGoogle Workspaceの環境とGeminiを活用して、業務効率化の第一歩を踏み出してみてはいかがでしょうか?

GASはAIエージェントにできます。