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

Google Apps Scriptで作ったウェブアプリで、Google Driveに保存した画像を表示させていたのだけれど、表示できなくなったので調査。2024/1/13に対処したばかりなんだけど、別の理由で表示できなくなっている。
まず、事象を再現するところから。
- Google Driveにフォルダーを作成
- DALLEで画像を作成して、フォルダーにアップロード
- 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がちゃんと見える。
function doGet(e) {
return HtmlService.createHtmlOutputFromFile('Index');
}
<!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}
- API使う。まだ試してない

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

fileIdを指定してData URLを取り出す方法
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}`;
}

HtmlServiceのTemplateを使う方法。注意点は、Templateでエスケープをしないよう<?!=
を使うところ。エスケープ漏れはXSSにつながるので注意して使うこと。
function doGet(e) {
// return HtmlService.createHtmlOutputFromFile('Index');
return HtmlService.createTemplateFromFile('Index').evaluate();
}
<!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>

google.script.runで非同期に呼び出す方法(Templateをevaluateする必要はない)
<!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。画像データを格納するには収まらないかもしれない。また、画像を更新したらキャッシュをクリアする必要がある。

Google Drive APIの利用回数制限とかあるのかな?と思ったけれど、見つからなかった。