いつもクリエイターズブログをご愛読いただいている皆様、初めまして。
業務管理部のkimと申します。
普段は デカメ―ル をはじめとした自社サービスのサポートや、総務・事務対応のほか
社内の情報セキュリティ管理も担当しております。
最近では、社内業務の効率化をDXで実現することにも取り組んでいます。
今回は、このDXの取り組みの一つとして、エンジニアではない私が生成AI技術を活用し
Web会議の議事録作成を自動化することができましたので、ご紹介させていただきます。
今回の目的
弊社では、毎朝Web会議(Google Meet)で朝礼を行い、議事録を作成しています。
しかし、議事録の作成と内容の要約作業に地味な手間がかかっており、課題となっていました。
そこで今回は、議事録の作成~要約~投稿まで全てAIにお任せしてしまおう!という目的になります。
必要な下準備や作業内容についても、ほぼ全てChatGPTに教えてもらいました。
使用ツール
1Google Meet:ふだんのWeb会議で使用。最近日本語対応したGeminiの「自動文字起こし」を活用します。
2Slack:ふだんの社内コミュニケーションで使用。議事録の投稿先となります。
3ChatGPT:GASスクリプトの作成、および議事録まとめ処理(API使用)を行います。
準備
Google Meet議事録データの仕様を確認
自動文字起こしをオンにした会議で、終了時に会議の主催者のGoogleドライブの「マイドライブ」に保存されます。
OpenAI APIでsecret keyを取得
取得した議事録データの内容を読み取り、ChatGPTにAPI経由で要約してもらいます。
※1回当たり 約 $0.05〜$0.10(7〜15円)程度のコストが発生します。
実行回数や文字数が多い場合は、料金に注意しましょう。
OpenAI Platformにログインし、 sk- から始まるAPI用のSecret Keyを作成して控えておきます。
※はじめて利用する場合は、クレジットカードなどの支払い方法の設定が必要です。
Slack botの作成とBot User OAuth Tokenの取得
Slack API画面にログインし、「Create New App」よりSlack botを新規作成します。
https://api.slack.com/appsbotが作成できたら、ワークスペースへのインストールを行います。
※「インストールするボットユーザーがありません」と表示される場合は以下の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に投稿できました!🎉
実際の会議の内容も適切に要約されており、数日使っていますが十分な実用性を感じています。
しかし、
今日のLTはまさに「誰得」の極みでしたね!
といった、失礼な言動もチラホラみられました。まだ調整の余地がありますね…。
ChatGPTへ指示する内容はGASスクリプトの const prompt 部分(108行目)で変更できるので、ご参考ください!
注意:Google Meetの文字起こしについて
議事録を残したい会議では、必ず文字起こしをオンにしましょう(1敗)。
会議が始まる前に、Meetの画面右上にある鉛筆マークからオンにしておきます。
なお、定期的な会議では、文字起こしボタンの押し忘れを防止するために、あらかじめ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へ適切な指示を出す力 = プロンプト力 の重要性を実感しました。
同じような課題をお持ちの方のお役に立てれば幸いです!