はじめに
過去に書いた記事 【LINE × ChatGPT 完全解説】非エンジニアでもAIチャットボット開発|スプシとGAS(Google Apps Script)で作る方法 では、テキスト情報のみ対応していました。
今回は、音声入力でも返答できたら良いなと思い、簡単に作れたので、YouTube動画とQiita記事を公開しました。
YouTube 動画解説
プログラム
スプレッドシートを新規作成 して、「拡張機能」から「Apps Script」を開き、下記をコピペしてください。
// ====== 設定 ======
const OPENAI_APIKEY = "**********************"; // OpenAI API Key
const LINE_ACCESS_TOKEN = "**********************"; // LINE チャネルアクセストークン
// LINE API
const LINE_REPLY_URL = 'https://api.line.me/v2/bot/message/reply';
const LINE_CONTENT_URL = 'https://api-data.line.me/v2/bot/message/'; // + {messageId}/content
// Chat API(既存のまま)
const CHAT_GPT_URL = "https://api.openai.com/v1/chat/completions";
const CHAT_GPT_VER = "gpt-5-nano";
// 音声→テキスト(STT)API
// Whisper系 or 4o系STTモデルを指定。組織に合わせて変更可。
const OPENAI_STT_URL = "https://api.openai.com/v1/audio/transcriptions";
const OPENAI_STT_MODEL = "whisper-1"; // うまく動く定番。4o系のSTTを使うなら差し替え
// スプレッドシート
const SS = SpreadsheetApp.getActiveSpreadsheet();
const SHEET = SS.getSheetByName('シート1');
function doPost(e) {
const body = JSON.parse(e.postData.contents);
const event = body.events && body.events[0];
if (!event) return ContentService.createTextOutput("ok");
const replyToken = event.replyToken;
const lineUserId = event.source && event.source.userId;
// イベント種別チェック
if (event.type !== 'message') {
return sendMessage(replyToken, "今はメッセージのみ対応してるよ!");
}
const msg = event.message;
const type = msg.type;
try {
if (type === 'text') {
// 既存のテキスト→チャット
const userText = msg.text;
const replyText = chatGPT(userText);
return sendMessage(replyToken, replyText);
} else if (type === 'audio') {
// 1) LINEから音声バイナリ取得
const audioBlob = fetchLineAudioBlob(msg.id);
// 2) OpenAIで文字起こし
const transcript = transcribeAudio(audioBlob);
// 3) 既存チャットへ投げて回答
const replyText = chatGPT(transcript);
// 4) テキストで返信(TTSで音声返しも可能。要URLホスティング)
return sendMessage(replyToken, replyText);
} else {
// 画像/スタンプ等は未対応
return sendMessage(replyToken, "今はテキストと音声に対応してるよ(画像・スタンプは未対応🙏)");
}
} catch (err) {
console.error(err);
return sendMessage(replyToken, "ごめん、処理でエラーが出たみたい。もう一度試してみてね。");
}
}
// ====== 既存:Chat回答生成 ======
function chatGPT(prompt) {
let constraints = "";
try {
constraints = SHEET.getRange(1, 1).getValue() || "";
} catch (e) {
console.log("制約なし");
}
const requestOptions = {
"method": "post",
"headers": {
"Authorization": "Bearer "+ OPENAI_APIKEY,
"Content-Type": "application/json"
},
"payload": JSON.stringify({
"model": CHAT_GPT_VER,
"messages": [
{"role": "system", "content": constraints},
{"role": "user", "content": prompt}
]
}),
"muteHttpExceptions": true
};
const response = UrlFetchApp.fetch(CHAT_GPT_URL, requestOptions);
const json = JSON.parse(response.getContentText());
const text = json.choices && json.choices[0] && json.choices[0].message && json.choices[0].message.content;
return (text || "(回答が空でした)").trim();
}
// ====== 音声取得:LINEのメッセージコンテンツAPI ======
// 参考: GET /v2/bot/message/{messageId}/content
function fetchLineAudioBlob(messageId) {
const url = LINE_CONTENT_URL + encodeURIComponent(messageId) + "/content";
const res = UrlFetchApp.fetch(url, {
"method": "get",
"headers": { "Authorization": "Bearer " + LINE_ACCESS_TOKEN },
"muteHttpExceptions": true
});
// LINEの音声は m4a 等。コンテンツタイプを保持してOpenAIへ渡す
const ct = res.getHeaders()["Content-Type"] || "audio/m4a";
const blob = res.getBlob().setName("voice_message.m4a").setContentType(ct);
return blob;
}
// ====== 文字起こし:OpenAI STT(Whisper等) ======
function transcribeAudio(audioBlob) {
// GASのmultipartは payload に Blob を含めれば自動で boundary を付与してくれる
const payload = {
"model": OPENAI_STT_MODEL,
"file": audioBlob,
// 必要に応じて
// "response_format": "text", // デフォルトjsonが良ければ外す
// "temperature": 0,
// "prompt": "会話は日本語想定です"
};
const res = UrlFetchApp.fetch(OPENAI_STT_URL, {
"method": "post",
"headers": {
"Authorization": "Bearer " + OPENAI_APIKEY
// Content-Type は指定しない(GASが boundary を付けるため)
},
"payload": payload,
"muteHttpExceptions": true
});
const ct = res.getHeaders()["Content-Type"] || "";
const text = res.getContentText();
// response_formatを指定しない場合はJSONで返る
if (ct.indexOf("application/json") >= 0 || text.trim().startsWith("{")) {
const json = JSON.parse(text);
// whisper-1: { text: "..."} 形式
return (json.text || "").trim();
} else {
// response_format:"text" なら純テキスト
return text.trim();
}
}
// ====== LINEへ返信 ======
function sendMessage(replyToken, replyText) {
const postData = {
"replyToken" : replyToken,
"messages" : [
{ "type" : "text", "text" : replyText }
]
};
return postMessage(postData);
}
function postMessage(postData) {
const options = {
"method" : "POST",
"headers" : {
"Content-Type" : "application/json; charset=UTF-8",
"Authorization" : "Bearer " + LINE_ACCESS_TOKEN
},
"payload" : JSON.stringify(postData),
"muteHttpExceptions": true
};
return UrlFetchApp.fetch(LINE_REPLY_URL, options);
}
====== 設定 ====== 部分の2つの ********************** 部分を、それぞれ下記に変えてください。
OPENAI_APIKEY 部分
OpenAI API Key を発行する必要があります。
下記から発行しましょう!
LINE_ACCESS_TOKEN 部分
LINE チャネルアクセストークンも発行する必要があります。
下記から発行しましょう!
デプロイ
上の2つを書き換えたら、右上の青いボタン「デプロイ」から、URLを発行して、LINE側のWebhookに使いましょう。あとは、LINEに音声入力するだけ!
宣伝(3つ)
① Udemyでも、『ChatGPT×LINE』の講座を出しています。
今回使っているwhisper-1もChatGPTを作っているOpenAIという会社ですので、そのまま今回の復習や発展としてもご活用できます。
クーポンコード:『年月』を入力
※2025年9月の場合『202509』
② LINE Developers Community で司会&LT(プチ発表)します。
日時:2025年9月9日 (火) 20時〜
場所:オンライン(YouTube)
他にも、さまざまなLINEを活用したLT(ミニ発表)がございますので、ぜひお越しください!
③ 毎週火曜と金曜にYouTube更新しています。
この記事と動画が少しでも役に立てた場合、YouTubeのチャンネル登録と、いいねを、お願いします!
