hero_picture
Cover Image for 非エンジニアがWeb会議の議事録作成を自動化してみた

非エンジニアがWeb会議の議事録作成を自動化してみた

いつもクリエイターズブログをご愛読いただいている皆様、初めまして。
業務管理部のkimと申します。

普段は デカメ―ル をはじめとした自社サービスのサポートや、総務・事務対応のほか
社内の情報セキュリティ管理も担当しております。
最近では、社内業務の効率化をDXで実現することにも取り組んでいます。

今回は、このDXの取り組みの一つとして、エンジニアではない私が生成AI技術を活用し
Web会議の議事録作成を自動化することができましたので、ご紹介させていただきます。

今回の目的

弊社では、毎朝Web会議(Google Meet)で朝礼を行い、議事録を作成しています。
しかし、議事録の作成と内容の要約作業に地味な手間がかかっており、課題となっていました。

Generated by ChatGPT
Generated by ChatGPT

そこで今回は、議事録の作成~要約~投稿まで全てAIにお任せしてしまおう!という目的になります。
必要な下準備や作業内容についても、ほぼ全てChatGPTに教えてもらいました。

使用ツール

1Google Meet:ふだんのWeb会議で使用。最近日本語対応したGeminiの「自動文字起こし」を活用します。
2Slack:ふだんの社内コミュニケーションで使用。議事録の投稿先となります。
3ChatGPT:GASスクリプトの作成、および議事録まとめ処理(API使用)を行います。

準備

Google Meet議事録データの仕様を確認

自動文字起こしをオンにした会議で、終了時に会議の主催者のGoogleドライブの「マイドライブ」に保存されます。

マイドライブ直下の「Meet Recordings」フォルダにGoogleドキュメント形式で自動保存されます。
マイドライブ直下の「Meet Recordings」フォルダにGoogleドキュメント形式で自動保存されます。
https://support.google.com/meet/answer/12849897?hl=ja

OpenAI APIでsecret keyを取得

取得した議事録データの内容を読み取り、ChatGPTにAPI経由で要約してもらいます。
※1回当たり 約 $0.05〜$0.10(7〜15円)程度のコストが発生します

💡
注意:ChatGPT Teamなど、有料プランを契約していてもAPIの利用は別途費用が掛かるので
実行回数や文字数が多い場合は、料金に注意しましょう。

OpenAI Platformにログインし、 sk- から始まるAPI用のSecret Keyを作成して控えておきます。
※はじめて利用する場合は、クレジットカードなどの支払い方法の設定が必要です。

https://platform.openai.com/login
Search から「key」と検索すると「API keys」が表示されるのでクリック
Search から「key」と検索すると「API keys」が表示されるのでクリック
「Create new secret key」をクリック。追加したキーは必ず控えておくようにしましょう
「Create new secret key」をクリック。追加したキーは必ず控えておくようにしましょう

Slack botの作成とBot User OAuth Tokenの取得

Slack API画面にログインし、「Create New App」よりSlack botを新規作成します。

https://api.slack.com/apps
「From scratch」を選択
「From scratch」を選択

botが作成できたら、ワークスペースへのインストールを行います。
※「
インストールするボットユーザーがありません」と表示される場合は以下のURLをご参照ください。
https://the-simple.jp/slack-nobotuser

「OAuth & Permissions」に移動し、「Bot Token Scopes」の「Add an OAuth Scope」に以下を設定します。

1channels:read
2chat:write.public
3chat:write

すると、OAuth Tokens に xoxb- から始まる「Bot User OAuth Token」が追加されるので、こちらを控えておきます。

Google Apps Scriptの実装

スクリプトファイルの作成

Googleドライブにログインし、右クリックメニューの「その他」から「Google Apps Script」を選択します。

使用するGoogle APIの設定

作成したGoogle Apps Scriptの編集画面の左横にある「サービス」の+ボタンから
Calendar、Drive、Slides のAPIをそれぞれ追加します。

スクリプトの内容(ChatGPT作成)

作成したGoogle Apps Scriptの「コード.gs」に以下を反映し、保存します。
ここで先ほど控えた OpenAI APIのsecret key(104行目)と、Slack botのBot User OAuth Token(168行目)を適用します。

1// === メイン関数(カレンダー連携+ChatGPT要約付き) ===
2function runDailyMorningReportByCalendar() {
3  try {
4    const event = getTodaysMorningMeetingEvent();
5    if (!event) throw new Error("今日の朝礼イベントが見つかりません");
6
7    const file = getMeetingDocFromDrive();
8    if (!file) throw new Error("マイドライブ内に本日作成された議事録ファイルが見つかりません");
9
10    const plainText = extractPlainText(file.getId());
11    const summary = generateSummaryByChatGPT(plainText); // ChatGPTで要約生成
12    postToSlackAsText(summary); // Slackに投稿
13
14  } catch (e) {
15    Logger.log("❌ 処理中にエラーが発生しました: " + e.message);
16  } finally {
17    createTriggerForNext(); // ← 成功/失敗に関わらず必ず実行される
18  }
19}
20
21// === 明日9:20にこの関数を再実行するトリガーを作成 ===
22function createTriggerForNext() {
23  const tomorrow = new Date();
24  tomorrow.setDate(tomorrow.getDate() + 1);
25  tomorrow.setHours(9, 20, 0, 0);
26
27  ScriptApp.newTrigger("runDailyMorningReportByCalendar")
28    .timeBased()
29    .at(tomorrow)
30    .create();
31}
32
33// === 今日に開始する「全体朝礼」イベントを取得 ===
34function getTodaysMorningMeetingEvent() {
35  const calendarId = 'primary';
36  const today = new Date();
37  const start = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 9, 0, 0);
38  const end = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 9, 30, 0);
39
40  const events = Calendar.Events.list(calendarId, {
41    timeMin: start.toISOString(),
42    timeMax: end.toISOString(),
43    singleEvents: true,
44    maxResults: 5
45  }).items;
46
47  for (let event of events) {
48    if (event.summary.includes('全体朝礼')) {
49      return event;
50    }
51  }
52  return null;
53}
54
55// === マイドライブから本日作成された「Gemini によるメモ」かつ「全体朝礼」かつファイル名に本日の日付を含むドキュメントを取得 ===
56function getMeetingDocFromDrive() {
57  const folderName = "Meet Recordings";
58  const folders = DriveApp.getFoldersByName(folderName);
59  if (!folders.hasNext()) throw new Error(`${folderName}」という名前のフォルダが見つかりませんでした`);
60
61  const folder = folders.next();
62  const files = folder.getFilesByType(MimeType.GOOGLE_DOCS);
63  let latestFile = null;
64  const todayStrForDate = Utilities.formatDate(new Date(), "Asia/Tokyo", "yyyy-MM-dd");
65  const todayStrForName = Utilities.formatDate(new Date(), "Asia/Tokyo", "yyyy/MM/dd");
66
67  while (files.hasNext()) {
68    const file = files.next();
69    if (
70      file.getName().includes("Gemini によるメモ") &&
71      file.getName().includes("全体朝礼") &&
72      file.getName().includes(todayStrForName)
73    ) {
74      const createdStr = Utilities.formatDate(file.getDateCreated(), "Asia/Tokyo", "yyyy-MM-dd");
75      if (createdStr === todayStrForDate) {
76        if (!latestFile || file.getLastUpdated().getTime() > latestFile.getLastUpdated().getTime()) {
77          latestFile = file;
78        }
79      }
80    }
81  }
82
83  if (!latestFile) throw new Error("本日作成された『Gemini によるメモ』かつ『全体朝礼』が含まれる議事録が見つかりませんでした");
84
85  return latestFile;
86}
87
88// === 議事録本文を抽出 ===
89function extractPlainText(docId) {
90  const file = Drive.Files.get(docId, { fields: 'exportLinks' });
91  const exportUrl = file.exportLinks['text/plain'];
92
93  const response = UrlFetchApp.fetch(exportUrl, {
94    headers: {
95      Authorization: 'Bearer ' + ScriptApp.getOAuthToken()
96    }
97  });
98
99  return response.getContentText();
100}
101
102// === ChatGPTで要約を生成(gpt-4-turbo使用) ===
103function generateSummaryByChatGPT(inputText) {
104  const apiKey = 'sk-***'; // ← OpenAI APIで作成したSecret Keyをここに設定
105  const url = "https://api.openai.com/v1/chat/completions";
106  const today = Utilities.formatDate(new Date(), "Asia/Tokyo", "yyyy/MM/dd");
107
108  const prompt = `以下の文字起こしを元に、Slack投稿向けの「本日の朝礼要約(${today})」形式でカテゴリ別に要約してください。
109
110🔽 出力フォーマットは以下のように統一してください:
111・各カテゴリに「【カテゴリ名】」をタイトルとして明記すること
112・カテゴリは以下の順で並べてください:
113  1. 勤怠状況の確認
114  2. 全体共有
115  3. リリース報告
116  4. セキュリティニュースの共有
117  5. LT発表
118  6. その他
119
120🔽 注意点:
121・「Gemini」や「文字起こし」などの自動処理に関するメモや注意書きは含めないでください
122・実際の発表内容を基に、箇条書きでわかりやすく表現してください
123・「その他」の部分では、LTの感想を交えたユーモアある一言を添えてください
124・その他、任意の追加指示を記載
125
126--- 以下、文字起こし本文 ---
127
128${inputText}`;
129
130  const payload = {
131    model: "gpt-4-turbo",
132    messages: [
133      {
134        role: "system",
135        content: "あなたはSlack投稿形式の朝礼要約を作成するアシスタントです。"
136      },
137      {
138        role: "user",
139        content: prompt
140      }
141    ],
142    temperature: 0.3
143  };
144
145  const options = {
146    method: "post",
147    headers: {
148      Authorization: "Bearer " + apiKey,
149      "Content-Type": "application/json"
150    },
151    payload: JSON.stringify(payload),
152    muteHttpExceptions: true
153  };
154
155  const response = UrlFetchApp.fetch(url, options);
156  const result = JSON.parse(response.getContentText());
157
158  if (!result.choices || result.choices.length === 0) {
159    Logger.log(response.getContentText());
160    throw new Error("❌ ChatGPTから要約が得られませんでした");
161  }
162
163  return result.choices[0].message.content;
164}
165
166// === Slackにテキスト形式で投稿 ===
167function postToSlackAsText(summary) {
168  const slackToken = 'xoxb-***'; // Slack botの「Bot User OAuth Token」をここに設定
169  const channelId = 'C123456789'; // メッセージを投稿するSlackチャンネルのID
170
171  const today = Utilities.formatDate(new Date(), "Asia/Tokyo", "yyyy/MM/dd");
172  const header = `📌 *本日の朝礼要約(${today})*`;
173
174  const payload = {
175    channel: channelId,
176    text: `${header}\n\n\`\`\`\n${summary}\n\`\`\``
177  };
178
179  const response = UrlFetchApp.fetch("https://slack.com/api/chat.postMessage", {
180    method: "post",
181    headers: {
182      Authorization: "Bearer " + slackToken,
183      "Content-Type": "application/json"
184    },
185    payload: JSON.stringify(payload),
186    muteHttpExceptions: false
187  });
188
189  Logger.log("📡 Slack postMessage response: " + response.getContentText());
190}
191

スクリプトの実行:初回操作

実際の運用を始める前に、Google Apps Scriptの編集画面で
「runDailyMorningReportByCalendar」関数を「▶ 実行」で手動実行します。
これで明日以降、同じ時間に議事録の作成が行われるようになります。
(このスクリプトでは 09:20 を指定しています)

なお、Google Workspaceの設定によっては下記のようなエラーが表示される場合があります。

この場合、「エラーの詳細」をクリックすると表示される内容のうち
~.apps.googleusercontent.com (画像の黄色下線部分)を

以下手順の「クライアントID」に 置き換えて設定することで、利用可能となります。

https://support.itmc.i.moneyforward.com/l/ja/article/x6sms6m4ja-google-oauth#2197951945

結果

議事録をSlackに投稿できました!🎉

SlackアプリのアイコンもAI(ChatGPT)で作成!
SlackアプリのアイコンもAI(ChatGPT)で作成!

実際の会議の内容も適切に要約されており、数日使っていますが十分な実用性を感じています。
しかし、

今日のLTはまさに「誰得」の極みでしたね!

といった、失礼な言動もチラホラみられました。まだ調整の余地がありますね…。
ChatGPTへ指示する内容はGASスクリプトの
const prompt 部分(108行目)で変更できるので、ご参考ください!

注意:Google Meetの文字起こしについて

議事録を残したい会議では、必ず文字起こしをオンにしましょう(1敗)。
会議が始まる前に、Meetの画面右上にある鉛筆マークからオンにしておきます。

Meet画面の右上にある鉛筆マークを押し、「メモの作成を開始」をクリックします
Meet画面の右上にある鉛筆マークを押し、「メモの作成を開始」をクリックします
鉛筆マークの色が変わればOK!
鉛筆マークの色が変わればOK!


なお、定期的な会議では、文字起こしボタンの押し忘れを防止するために、あらかじめGoogleカレンダーで文字起こしを設定しておくことも可能です。
しかし、この機能を使うと
デフォルトで会議の使用言語が英語になってしまい、議事録データも英語になってしまうバグが起きるようです。※2025/4/22 現在

ここでオンにしておくことで押し忘れを解消できますが…
ここでオンにしておくことで押し忘れを解消できますが…
なぜか英語になってしまう。違うんですけど…
なぜか英語になってしまう。違うんですけど…

この場合も議事録データは残りますが、日本語を空耳で英語にしたような使い物にならない内容になってしまいます。

Googleのサポートの方に問い合わせたところ、この不具合は 2025/4/23 から2週間ほどかけて解決される見通しとのことです。
https://workspaceupdates.googleblog.com/2025/04/updates-for-configuring-your-preferred-language-take-notes-for-me-recorded-captions-meeting-transcripts-google-meet.html
これが完全自動化の最後のピースになるので、早めに解決してくれることを祈ります…。

さいごに

いかがでしたでしょうか。
私自身はプログラミングの知識が豊富とは言えませんが、生成AIの力を借りることで議事録作成の自動化を実現できました。

最も苦労したのは、AIへの仕様や要望の伝え方、動作検証、そして修正のプロセスでした。
この経験を通じて、
AIへ適切な指示を出す力 = プロンプト力 の重要性を実感しました。

同じような課題をお持ちの方のお役に立てれば幸いです!


👉️「PHPシステム」のことならシーズにお任せください!
👉️「PHPシステム」のことならシーズにお任せください!