マキモノ
Google Workspace無料✅ 公式検証済みv1.0.0 / 更新

GASアプリ群への不具合報告フォーム横展開 — 各アプリを匿名公開せず共通フォーム1本へリンクする

機微データを持つ・スコープを触れない・画面が無い、で止まる横展開を、フォームをパラメータ化して共通1本に集約する方式で全部さばく。器の選び方で人間のクリックを0回にする方法と、文字化け・公開範囲の検証3点まで。

出品者: kim@orgiast.jp📖 読込 約2,612トークン (約4円)💰 コスパ 19
トークン節約メーター93%節約
ゼロからAIに作らせた場合5.2万トークン
このMDを読ませた場合3,400トークン

4.9万トークンの節約 (API料金換算で約73円分)。 要件定義・技術調査・試行錯誤ぶんのトークンがまるごと不要になります。※ 出品者申告とレビューに基づく推定値。モデル・タスク内容により変動します。

この巻物について

GASアプリ群への不具合報告フォーム横展開 — 各アプリを匿名公開せず共通フォーム1本へリンクする」は、Google WorkspaceカテゴリのAI指示書(MDファイル)です。機微データを持つ・スコープを触れない・画面が無い、で止まる横展開を、フォームをパラメータ化して共通1本に集約する方式で全部さばく。器の選び方で人間のクリックを0回にする方法と、文字化け・公開範囲の検証3点まで。この巻物をAIに読み込ませると、ゼロから設計・調査する場合に比べて 約4.9万トークン(API料金換算で約73円)・93%のトークンを節約できます。

カテゴリ
Google Workspace
対応AI
claude-code、cursor、codex-cli
ライセンス
商用利用可 (再販不可)
価格
無料
ゼロから開発時
約5.2万トークン
この巻物使用時
約3,400トークン
節約量
約4.9万トークン (約73円)
更新日
2026-09-05

使い方 (AIに渡す3つの方法)

いちばん簡単なのはワンライナー。Claude Code のターミナルに貼るだけです。

⬇ .md をダウンロード
claude "https://makimono-md.vercel.app/api/v1/files/gas-1/raw を読み込んで、この指示書どおりに実装して"
claude-codecursorcodex-cliライセンス: 商用利用可 (再販不可)

中身

GAS アプリ群へ不具合報告フォームを横展開する — 各アプリを匿名公開せず「共通フォーム1本」へリンクする方式

社内に Google Apps Script のアプリが何本もあり、その全部に「不具合・要望」の報告窓口を付けたい、という場面の手順書。

素直にやると アプリごとにフォーム画面を実装して、そのアプリ自身を「全員(匿名)」でウェブアプリとして公開する(以下・方式A)。1〜2本ならこれで問題ないが、5本目あたりで必ず止まる。この手順書は、そこで方式を切り替えて残り全部をさばくための型。


1. 方式Aが止まる3パターン(先に判定する)

止まる理由具体例なぜ方式Aが使えないか
機微データを持つアプリ審査結果・人事評価・顧客財務・仕入価格同じスクリプトを匿名公開すると、doGet他の画面にも匿名で到達しうる。実質的に公開範囲を広げてしまう
oauthScopes を宣言していないアプリ古くから動いている自動化スクリプトスコープはコードからの自動検出に頼っている。明示リストを書いた瞬間に自動検出が無効になり、既存機能が壊れる
社員向け画面が存在しないアプリビーコン受信専用、JSON を返す管理用エンドポイント、アドオンそもそもリンクを置く場所が無い

候補判定は「ウェブアプリ設定があるか」ではなく「社員が実際に開く HTML があるか」で行う。 ウェブアプリのアクセス設定が「自分だけ」になっているプロジェクトは、たいてい管理用エンドポイントであって社員向け画面を持たない。画面が別のフロントエンド(Next.js 等)側にあるなら、それは GAS 用ではなくそちらのフォームパッケージの担当。


2. 方式B — フォームをパラメータ化して共通1本にする

フォームを提供する側のサーバ関数を、呼び出し元の名前と報告元URLを外から受け取る形にしておく。

function serveFeedbackForm(params) {
  var opts = params || {};
  var template = HtmlService.createTemplateFromFile('FeedbackForm');
  template.appName   = String(opts.app || '');   // 通知の見出しになる
  template.sourceUrl = String(opts.src || '');   // 報告元ページ
  return template.evaluate()
    .setTitle('不具合・要望の報告')
    .addMetaTag('viewport', 'width=device-width, initial-scale=1');
}

function doGet(e) {
  var params = (e && e.parameter) || {};
  if (String(params.form || '') === 'feedback') return serveFeedbackForm(params);
  return HtmlService.createHtmlOutput('このアプリには feedback フォーム以外の画面はありません。');
}

HTML 側は受け取った値を hidden に置き、送信ペイロードへそのまま載せる。

<input type="hidden" id="appNameValue"   value="<?= appName ?>">
<input type="hidden" id="sourceUrlValue" value="<?= sourceUrl ?>">
var payload = { /* ... */ appName: appName, sourceUrl: sourceUrl };

テンプレートの出力は必ず <?= ?>(エスケープあり)にする。<?!= ?> にすると、URL パラメータがそのまま HTML に流れて XSS の入口になる。外部から値を受ける設計なので、ここは方式Aより重要度が上がる。

これで、どのアプリの報告窓口も1本の URL で足りる

<共通フォームのURL>?form=feedback&app=<アプリ名をURLエンコード>&src=<報告元ページのURL>

各アプリ側の作業はリンクを1本置くだけになる。スクリプトも、スコープも、デプロイも、公開範囲も触らない。


3. 「器」の選び方 — ここで人間のクリック回数が決まる

共通フォームをどのプロジェクトに置くかで、人間に頼む操作が 0 回か 1 回かが決まる

  • 🔴 新規にプロジェクトを作ると、初回の権限承認(ブラウザでの OAuth 同意)が必ず1回発生する。これは自動化できない。「あと1クリックだけお願いします」を毎回生むので、避けられるなら避ける
  • 既に動いていて権限承認済みのプロジェクトのうち、doGet がフォーム専用で他の画面を持たないものを器にすると、人間の操作は0回で済む。「他の画面が無い」ことが匿名公開の安全性を担保する

器は後から差し替えられる(各アプリのリンク先 URL を書き換えるだけ)ので、この選択は可逆。まず0クリックで動かし、専用プロジェクトが要るなら後で移せばよい。

弱点は1つだけ: 記録用シートが器のプロジェクト側に集約される。通知は app 名で区別できるので運用は困らない。アプリ別にシートを分けたい時だけ方式Aを選ぶ。


4. 導入手順(各アプリ側)

対象アプリの画面(HTML、またはシートのセル)に、上記 URL へのリンクを置く。それ以外は何もしない。

  • 🔴 マニフェスト(appsscript.json)を変更しない — ウェブアプリ設定もスコープも触らない
  • 🔴 新しいデプロイを作らない
  • 🔴 サーバ側のコード(doGet 等)を変更しない
  • 🔴 フォーム側の設定投入も不要 — 設定は器の側に既に入っている
<div class="feedback-link">
  <a href="<共通フォームURL>?form=feedback&amp;app=%E2%80%A6" target="_blank" rel="noopener">🐛 不具合・要望</a>
</div>

ここで踏む罠

  • app= の値は URL エンコード済みの文字列をそのまま埋め込む。 日本語をそのまま書くと、編集経路(特に Windows のシェル経由)で文字化けする
  • HTML の属性値では &&amp; にエスケープする。 生の & はパラメータが切れる原因になる
  • ページに <base target="_top"> がある場合、リンクに target="_blank" rel="noopener" を明示する。 無いと同じタブで開き、作業中の画面を失う
  • デプロイが「最新(HEAD)」ならソースの反映は即時、バージョン固定なら再デプロイするまで画面に出ない。 デプロイ一覧で必ず確認する

5. 検証(ここまでやって初めて「完了」)

対象アプリのアクセス設定がドメイン限定なら、匿名のリクエストでは画面を取得できない。次の3点で確認する。

  1. 共通フォームがそのアプリ名で描画されるか app= を付けた URL を実際に取得し、返ってきた HTML にアプリ名が文字化けせず含まれることを確認する。 🔴 シェルの curl --data-urlencode で日本語を渡すと、Windows では文字コードが変換されて %EF%BF%BD(U+FFFD)の羅列になる。 検証は必ず Node の fetch + encodeURIComponent など、シェルを1層も通さない経路で行う。
    const url = BASE + '?form=feedback&app=' + encodeURIComponent(name);
    const t = await (await fetch(url)).text();
    console.log(t.includes(name), t.includes('�'));  // true, false を期待
    
  2. 本番のスクリプトに反映されたか 別ディレクトリに同じスクリプトIDの設定を置いて pull し、追加したリンク文字列が含まれることを確認する。あわせてマニフェストが変わっていないことも差分で確認する。
  3. 公開範囲が広がっていないか 対象アプリの URL に匿名でアクセスし、302 でログイン画面へ飛ぶ(=制限が維持されている)ことを確認する。これを飛ばすと、フォームを足したつもりで本体を公開してしまった事故に気付けない。

器の側は、設定が入っているかを返す ping 関数を1つ用意しておくと早い。

function feedbackPing() {
  var c = getConfig_();
  return { ok:true, hasUrl:Boolean(c.url), hasSecret:Boolean(c.secret), appName:c.appName };
}

hasUrl / hasSecrettrue で、appName文字化けしていないことまで見る。文字化けは通知の宛先解決を壊すので、真偽値だけ見て通すと後で詰まる。


6. まとめ(判断の順序)

  1. そのアプリに社員が開く HTML があるか → 無ければ対象外
  2. 匿名公開してまずい画面が同居していないか → していなければ方式Aでもよい
  3. していれば方式B(リンク1本・アプリ側は無変更)
  4. 器は既に権限承認済みでフォーム専用のプロジェクトを選ぶ(人間のクリック0回)
  5. 検証は描画・反映・公開範囲の3点。1つでも飛ばさない

よくある質問

「GASアプリ群への不具合報告フォーム横展開 — 各アプリを匿名公開せず共通フォーム1本へリンクする」とは何ですか?

機微データを持つ・スコープを触れない・画面が無い、で止まる横展開を、フォームをパラメータ化して共通1本に集約する方式で全部さばく。器の選び方で人間のクリックを0回にする方法と、文字化け・公開範囲の検証3点まで。

どれくらいトークン(費用)を節約できますか?

ゼロから開発すると約5.2万トークンかかりますが、この巻物を使えば約3,400トークンで済みます。差し引き約4.9万トークン(API料金換算で約73円)・93%の節約です。

どうやって使いますか?

無料です。MDファイルを Claude Code などのAIに読み込ませるだけ。ワンライナーをターミナルに貼れば実装が始まります。要件定義や技術調査を省いて実装だけにトークンを使えます。

どのAIツールに対応していますか?

claude-code、cursor、codex-cli に対応しています。

商用利用できますか?

ライセンスは「商用利用可 (再販不可)」です。

🤝 自分でAIを動かすのは、まだ不安…という方へ

この巻物の内容を、AIを使うプロに丸ごと任せることもできます。姉妹サービスAI代行堂なら「LINEで頼むだけで、仕事が完成」。

AI代行堂を見る →

関連する巻物

この巻物、誰かのトークンも救えます

𝕏 で節約レシートをシェア