6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

OpenAI社の音声認識API whisper-1 で LINEbot 爆速で作る方法

Posted at

はじめに

過去に書いた記事 【LINE × ChatGPT 完全解説】非エンジニアでもAIチャットボット開発|スプシとGAS(Google Apps Script)で作る方法 では、テキスト情報のみ対応していました。

今回は、音声入力でも返答できたら良いなと思い、簡単に作れたので、YouTube動画とQiita記事を公開しました。

YouTube 動画解説

プログラム

スプレッドシートを新規作成 して、「拡張機能」から「Apps Script」を開き、下記をコピペしてください。

コード.gs
// ====== 設定 ======
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に音声入力するだけ!

image.png

宣伝(3つ)

① Udemyでも、『ChatGPT×LINE』の講座を出しています。

今回使っているwhisper-1もChatGPTを作っているOpenAIという会社ですので、そのまま今回の復習や発展としてもご活用できます。

クーポンコード:『年月』を入力
※2025年9月の場合『202509』

② LINE Developers Community で司会&LT(プチ発表)します。

日時:2025年9月9日 (火) 20時〜
場所:オンライン(YouTube)

他にも、さまざまなLINEを活用したLT(ミニ発表)がございますので、ぜひお越しください!

③ 毎週火曜と金曜にYouTube更新しています。

この記事と動画が少しでも役に立てた場合、YouTubeのチャンネル登録と、いいねを、お願いします!

6
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?