Closed12

Google Apps ScriptのウェブアプリでGoogle Driveの画像を表示できなくなったので調査してた

高玉 広和高玉 広和

Google Apps Scriptで作ったウェブアプリで、Google Driveに保存した画像を表示させていたのだけれど、表示できなくなったので調査。2024/1/13に対処したばかりなんだけど、別の理由で表示できなくなっている。

https://twitter.com/takatama_jp/status/1745925103697989896

まず、事象を再現するところから。

  1. Google Driveにフォルダーを作成
  2. DALLEで画像を作成して、フォルダーにアップロード
  3. Apps Scriptを作成して、画像を表示するウェブアプリを作る

画像のURLは、以前のやり方と対処したやり方の両方で試しておく。

高玉 広和高玉 広和
  • Google Drive上の画像のURL
    • https://drive.google.com/file/d/1dr139i1BCQpCFYOMTM054vuy6ih-8Dqu/view?usp=sharing
  • FileId
    • 1dr139i1BCQpCFYOMTM054vuy6ih-8Dqu
  • 以前の画像URL
    • https://drive.google.com/uc?export=view&id=1dr139i1BCQpCFYOMTM054vuy6ih-8Dqu
  • 対処した画像URL
    • https://lh3.google.com/u/0/d/1dr139i1BCQpCFYOMTM054vuy6ih-8Dqu
高玉 広和高玉 広和

ここだと画像のURLを指定すればそのまま表示できるので実験してみる。

以前の画像URL

対処した画像URL

Apps Scriptで作ったウェブアプリ以外だと、以前の画像URLを指定するやり方のほうが見えるのか。混乱するな...

高玉 広和高玉 広和

うまくいってるな...対処したやり方でちゃんと表示される。Oldが駄目で、Newがちゃんと見える。

Code.gs
function doGet(e) {
  return HtmlService.createHtmlOutputFromFile('Index');
}
Index.html
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <h1>Old</h1>
    <img src="https://drive.google.com/uc?export=view&id=1dr139i1BCQpCFYOMTM054vuy6ih-8Dqu" width="480">
    <h1>New</h1>
    <img src="https://lh3.google.com/u/0/d/1dr139i1BCQpCFYOMTM054vuy6ih-8Dqu" width="480">
  </body>
</html>
高玉 広和高玉 広和

確認したら、2024/2/15 夜に不具合報告があったのだけれど、2/17 お昼には直ってた...。

不具合は

  • lh3.google.comを画像のURLに指定すると302 Found レスポンスで、Cross-Origin-Resource-Policy: same-site が返ってくる
  • リダイレクト先がhttps://lh3.googleusercontent.comになるため、ウェブアプリのドメイン script.google.comと変わってしまい詰む

という事象だった。切り戻したんだろうな...。

高玉 広和高玉 広和

Google Issue Tracker https://issuetracker.google.com/issues/319531488#comment175 を確認して分かったこと

  • 2023/10/16にGoogleからアナウンス。iframeを使うように指示。2024/1/2から影響するよ。
  • 次のような対処方法が出回っているけれど、公式な方法ではないよ。
    • 試したやつ https://lh3.google.com/u/0/d/{id}
    • 試したら駄目だったやつ https://drive.usercontent.google.com/download?id={ID}&export=view
      • 403 Forbidden
    • サムネイル使う https://drive.google.com/thumbnail?id={id}&sz=w1000
      • img の width より、szで指定したサイズが優先される
  • iframeを使った公式なやり方と制約事項はここが分かりやすいよ https://justin.poehnelt.com/posts/google-drive-embed-images-403/
  • その他のやり方だと、Google Cloud Storageでホスティングするか、Cloud Functionsでお金かけるか。
    • API使う。まだ試してない https://www.googleapis.com/drive/v3/files/{id}?alt=media&key={apiKey}
高玉 広和高玉 広和

これからやること

  • 非公式なやり方が通用しなくなるときに備えて、 Google Driveから画像データを直接取得し、Data URLに変換して利用する方法を試す
高玉 広和高玉 広和

fileIdを指定してData URLを取り出す方法

Code.gs
function getDataUrl(fileId) {
  const blob = DriveApp.getFileById(fileId).getAs("image/webp");
  const contentType = blob.getContentType();
  const base64Data = Utilities.base64Encode(blob.getBytes());
  return `data:${contentType};base64,${base64Data}`;
}

参考 : https://qiita.com/a_eau_/items/01bc14bfc5cb5ef12e64

高玉 広和高玉 広和

HtmlServiceのTemplateを使う方法。注意点は、Templateでエスケープをしないよう<?!=を使うところ。エスケープ漏れはXSSにつながるので注意して使うこと。

Code.gs
function doGet(e) {
  // return HtmlService.createHtmlOutputFromFile('Index');
  return HtmlService.createTemplateFromFile('Index').evaluate();
}
Index.html
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <h1>Old</h1>
    <img src="https://drive.google.com/uc?export=view&id=1dr139i1BCQpCFYOMTM054vuy6ih-8Dqu" width="480">
    <h1>New</h1>
    <img src="https://lh3.google.com/u/0/d/1dr139i1BCQpCFYOMTM054vuy6ih-8Dqu" width="480">
    <h1>Data URL</h1>
    <img src="<?!= getDataUrl('1dr139i1BCQpCFYOMTM054vuy6ih-8Dqu') ?>" width="480">
  </body>
</html>

https://developers.google.com/apps-script/guides/html/templates?hl=ja#code.gs

高玉 広和高玉 広和

google.script.runで非同期に呼び出す方法(Templateをevaluateする必要はない)

Index.html
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <h1>Old</h1>
    <img src="https://drive.google.com/uc?export=view&id=1dr139i1BCQpCFYOMTM054vuy6ih-8Dqu" width="480">
    <h1>New</h1>
    <img src="https://lh3.google.com/u/0/d/1dr139i1BCQpCFYOMTM054vuy6ih-8Dqu" width="480">
    <h1>Data URL(Synchronous)</h1>
    <img src="<?!= getDataUrl('1dr139i1BCQpCFYOMTM054vuy6ih-8Dqu') ?>" width="480">
    <h1>Data URL (Asynchronous)</h1>
    <img data-file-id="1dr139i1BCQpCFYOMTM054vuy6ih-8Dqu" width="480">
    <script>
window.onload = function() {
  const images = document.querySelectorAll('img[data-file-id]');
  for (let image of images) {
    google.script.run.withSuccessHandler(dataUrl => {
      image.src = dataUrl;
    })
    .withFailureHandler(err => console.error(`Could not call getDataUrl: ${err}`))
    .getDataUrl(image.getAttribute('data-file-id'));
  }
}
    </script>
  </body>
</html>
高玉 広和高玉 広和

高速化するのに、getDataUrlの中でCacheServiceを使い、fileIdごとにデータをキャッシュすることを考えたけれど、キーごとに保存できるデータの最大量は 100 KB。画像データを格納するには収まらないかもしれない。また、画像を更新したらキャッシュをクリアする必要がある。

https://developers.google.com/apps-script/reference/cache/cache?hl=ja#put(String,String)

このスクラップは2024/08/17にクローズされました