{
  "apiVersion": 1,
  "id": "voicevox",
  "name": "VOICEVOX で読み上げ",
  "version": "1.2.0",
  "minAppVersion": "1.1.1",
  "description": "この Mac（127.0.0.1）か、同じローカルネットワークの機械（設定で IP アドレスを入れた 1 台）で動いている VOICEVOX にコメントを渡して読み上げます。音はこのアプリが鳴らします。VOICEVOX を先に起動しておいてください。",
  "hosts": [
    "127.0.0.1",
    "localhost"
  ],
  "windows": [
    "question",
    "moderator",
    "paid"
  ],
  "localNetwork": "host",
  "localNetworkDiscovery": {
    "portSetting": "port",
    "path": "/version"
  },
  "settings": [
    {
      "key": "host",
      "label": "VOICEVOX の機械の IP アドレス",
      "type": "text",
      "default": "",
      "help": "空ならこの Mac の中。iPad や別の機械の VOICEVOX を使うときは、VOICEVOX を動かしている機械の IP アドレス（例: 192.168.1.20）を入れるか、「探す…」で見つけます"
    },
    {
      "key": "port",
      "label": "VOICEVOX のポート",
      "type": "number",
      "default": 50021,
      "min": 1024,
      "max": 65535,
      "help": "VOICEVOX の設定＞エンジンで確かめられます（既定は 50021）"
    },
    {
      "key": "speaker",
      "label": "話者の ID",
      "type": "number",
      "default": 3,
      "min": 0,
      "max": 999,
      "help": "VOICEVOX の話者（スタイル）の番号。例: 3 = ずんだもん（ノーマル）・2 = 四国めたん（ノーマル）・8 = 春日部つむぎ。一覧は VOICEVOX を動かしている機械の http://<IP アドレス>:50021/speakers"
    },
    {
      "key": "speed",
      "label": "話す速さ（1 が標準）",
      "type": "number",
      "default": 1.2,
      "min": 0.5,
      "max": 2
    },
    {
      "key": "readName",
      "label": "名前も読む",
      "type": "bool",
      "default": true
    },
    {
      "key": "readAmount",
      "label": "スパチャ・ステッカーの金額を読む",
      "type": "bool",
      "default": true
    },
    {
      "key": "maxLength",
      "label": "読む長さの上限（文字）",
      "type": "number",
      "default": 60,
      "min": 10,
      "max": 300,
      "help": "長いコメントはここで切り、「以下略」と読みます"
    }
  ],
  "script": "// VOICEVOX で読み上げる連携プラグイン（GDR-APP-009 / GDR-INFRA-010）。\n// 本体から onComment(event, settings) でコメントが 1 件ずつ届く。VOICEVOX の audio_query → synthesis で WAV を作り、host.play で本体に鳴らしてもらう。\n\n/** 読み上げる文（名前・金額・本文）を作る。読むものがなければ空文字。 */\nfunction buildText(event, settings) {\n  var body = cleanText(event.text || \"\");\n  var max = Math.max(10, Math.floor(settings.maxLength || 60));\n  if (body.length > max) body = body.slice(0, max) + \"、以下略\";\n  var parts = [];\n  if (settings.readName && event.author) parts.push(cleanName(event.author) + \"さん\");\n  if (settings.readAmount && event.amount) parts.push(event.amount);\n  if (event.kind === \"newMember\") parts.push(\"メンバー加入\");\n  if (event.kind === \"membershipGifting\") parts.push(\"メンバーシップのギフト\");\n  if (body) parts.push(body);\n  // 名前だけ（本文のないテキスト）は読まない\n  if (!body && (event.kind === \"text\" || !event.kind)) return \"\";\n  return parts.join(\"、\");\n}\n\n/** カスタム絵文字のショートコード・URL を除き、「www」を「わら」にする。 */\nfunction cleanText(text) {\n  return text\n    .replace(/https?:\\/\\/\\S+/g, \"\")\n    .replace(/:[^:\\s]+:/g, \"\")\n    .replace(/[wｗ]{3,}/g, \"わら\")\n    .replace(/\\s+/g, \" \")\n    .trim();\n}\n\n/** 名前の先頭の @ と末尾の「-xxx」（ハンドルの数字・記号）を除く。 */\nfunction cleanName(name) {\n  return name.replace(/^@/, \"\").replace(/-[0-9a-z]{2,}$/i, \"\");\n}\n\n/** VOICEVOX の機械（空ならこの端末の中）。同じローカルネットワークの機械に送れるのは、本体が確かめた 1 台だけ（GDR-SEC-005）。 */\nfunction engineHost(settings) {\n  var host = String(settings.host || \"\").trim();\n  return host || \"127.0.0.1\";\n}\n\nasync function onComment(event, settings) {\n  var text = buildText(event, settings);\n  if (!text) return;\n  var base = \"http://\" + engineHost(settings) + \":\" + Math.floor(settings.port || 50021);\n  var speaker = Math.floor(settings.speaker || 0);\n  var query = await host.fetch(base + \"/audio_query?speaker=\" + speaker + \"&text=\" + encodeURIComponent(text), { method: \"POST\" });\n  if (query.status !== 200) throw new Error(\"VOICEVOX の audio_query が \" + query.status + \" を返しました（VOICEVOX は起動していますか？ 別の機械なら IP アドレスと、外からのつなぎを受ける設定を確かめてください）\");\n  var audioQuery = JSON.parse(query.text);\n  audioQuery.speedScale = settings.speed || 1;\n  var wav = await host.fetch(base + \"/synthesis?speaker=\" + speaker, {\n    method: \"POST\",\n    headers: { \"Content-Type\": \"application/json\" },\n    body: JSON.stringify(audioQuery),\n    as: \"base64\",\n  });\n  if (wav.status !== 200) throw new Error(\"VOICEVOX の synthesis が \" + wav.status + \" を返しました\");\n  // 再生の待ち行列に積んだら戻る（鳴らすのは本体が順に行う）\n  await host.play({ data: wav.base64, type: \"audio/wav\" });\n}\n\nif (typeof module !== \"undefined\") module.exports = { buildText, cleanText, cleanName, engineHost };\n",
  "readme": "# VOICEVOX で読み上げ\n\n[VOICEVOX](https://voicevox.hiroshiba.jp/) に、選んだ窓のコメントを渡して読み上げます。音はこのアプリが鳴らします。VOICEVOX はこの Mac の中か、同じ Wi-Fi（ローカルネットワーク）の Mac / Windows で動かします。\n\n## はじめに\n\n1. VOICEVOX を起動しておきます（エンジンが `http://127.0.0.1:50021` で動きます）\n2. 「詳しい設定」で、渡す窓・話者・速さを選びます\n3. 「試しに送る」で鳴るか確かめます\n\n## iPad・別の機械の VOICEVOX を使う\n\niPad の中では VOICEVOX が動かないので、同じ Wi-Fi の Mac / Windows で動かしている VOICEVOX に送ります（Mac から別の機械の VOICEVOX を使うときも同じです）。\n\n1. VOICEVOX を動かす機械で、エンジンが同じネットワークから受けるようにします（VOICEVOX のエンジンの設定で、待ち受けの先を `127.0.0.1` 以外にします。手順は VOICEVOX の版で違います）\n2. その機械の IP アドレスを調べます（Mac: システム設定＞ネットワーク / Windows: `ipconfig` の IPv4 アドレス）。ファイアウォールでポート 50021 を開けます\n3. 「詳しい設定」の「VOICEVOX の機械の IP アドレス」の下の「探す…」を押すと、同じネットワークで動いている VOICEVOX を探します。見つかった機械の「これを使う」を押します（IP アドレスを直接入れてもかまいません。例: `192.168.1.20`）。入れられるのは同じローカルネットワークの IP アドレスだけです\n4. 初めて送るとき、iPad（Mac）がローカルネットワークの許可を尋ねるので「許可」を選びます\n\n### VOICEVOX を使える機械を絞る（任意）\n\n同じネットワークから受けるようにすると、ほかの機械からも VOICEVOX を使えます。気になるときは、VOICEVOX を動かす機械のファイアウォールで、ポート 50021 をこの iPad の IP アドレスからだけ受けるようにします。\n\n- Windows: Windows Defender ファイアウォールの「受信の規則」で、ポート 50021（TCP）の規則を作り、「スコープ」の「リモート IP アドレス」に iPad の IP アドレスを入れます\n- Mac: 標準のファイアウォールはアプリごとにしか絞れません。送り元で絞るには `pf` の規則が要ります（上級者向け）\n- iPad の IP アドレスが変わると読み上げが止まります。ルーターで iPad のアドレスを固定する（DHCP の予約）か、iPad の Wi-Fi の設定で「プライベート Wi-Fi アドレス」を「固定」にします\n\n## 設定\n\n- **VOICEVOX の機械の IP アドレス**: 空ならこの Mac の中。iPad や別の機械の VOICEVOX を使うときに入れます。「探す…」で、同じネットワークのアドレス（例: `192.168.1.1〜254`）にポート 50021 の `/version` を聞いて回り、VOICEVOX の版が返った機械を並べます（押したときだけ・コメントは送りません）\n- **話者の ID**: VOICEVOX の話者（スタイル）の番号。例: 3 = ずんだもん（ノーマル）・2 = 四国めたん（ノーマル）・8 = 春日部つむぎ。一覧は VOICEVOX を起動した状態で `http://<VOICEVOX の機械>:50021/speakers` を開くと見られます\n- **窓ごとに声を変える**: 「連携先を足す」で連携先を窓ごとに作り、それぞれで話者を選びます\n- **読む長さの上限**: 長いコメントはここで切り、「以下略」と読みます\n\n## 困ったとき\n\n- 鳴らない: VOICEVOX が起動しているか、「詳しい設定」の最後の誤りを見ます。音の出力先はこの Mac / iPad の既定の出力です\n- 別の機械に送れない: IP アドレス・VOICEVOX が外から受ける設定・ファイアウォール・ローカルネットワークの許可（設定＞プライバシーとセキュリティ＞ローカルネットワーク）を確かめます\n- 読み上げが遅れる: 渡す窓からメインを外すか、「待てる件数」を減らします（待ちが上限を超えると古いものから捨てます）\n"
}
