Google SpreadsheetとGoogle Slidesでお手軽動的OGP with Nuxt
こんにちは。asatoです。
いま面白いスペースに出会えるspaces.bzでは、デイリーランキングで動的OGPを実装しています。
こちらのページからスペースをシェアすると画像が表示されます。
Twitterアカウントでは毎日ランキングを発表しているので、こちらでも確認できます〜。
実はこれ、Google SpreadsheetとGoogle Slidesを使って、GASで毎日OGP画像を生成して表示させているんです。 結構面白いアイデアかな、と思うのでシェアします!😊
全体像
まずこの記事で紹介する全体像を。
- 動的OGPのもとになるSlidesを作成
- 動的OGPのデータのもとになるSpreadsheetを作成
- GASを作成
- SpreadsheetのデータをもとにSlidesの文字列を置換
- Slidesを画像ファイルでエクスポートしてDriveに保存
- SpreadsheetにDriveに保存した画像ファイルのIDを追加
- Slidesを元の文字列に置換し直す
- SpreadsheetをWebApp(API)で公開
- Nuxtアプリで動的OGPを設定
ちょっと長いですが、お付き合いいただけると嬉しいです🙏
1. 動的OGPの元になるSlidesを準備
まずはいい感じのOGPのデザインをGoogle Slidesで作ります。
Coolです。ポイントとしてはOGPは1200mmx628mmのサイズがよいので、「ファイル > ページ設定」からスライドの縦横サイズを変更してます。
このスライドに書いている「name」はこの後置換するためのキーワードになってます。
2. 動的OGPのデータのもとになるSpreadsheetを準備
「name」を置換していきたいので、そんな感じのSpreadsheetを用意します。 spaces.bzの場合は、その日のランキング10位までをGASで集計して、そのデータを元に置換を行っています。 今回は簡単な例なので、以下のようなシートを用意します。
id | name | ogpId |
---|---|---|
1 | Test A | |
2 | Test B | |
3 | Test C |
ogp_id
は今のところ空ですが列だけ用意しておきます。後でGASでOGP画像のファイルを生成したときに画像ファイルのIDをセットするカラムです。
3. GASを作成
この記事の佳境です!
まずコードの全容をさらします。 説明しやすいように書いていますが、内容を理解いただければ色々な書き方があるかなーと思います。
function main() { const ss = SpreadsheetApp.openById('<SpreadsheetのID>') const sheet = ss.getSheetByName('<Sheetの名前>') const data = sheet.getDataRange().getValues() const columnNames = data.shift() const presentationId = '<SlidesのID>' const folder = DriveApp.getFolderById('<FolderのID>') data.forEach((row, index) => { const name = row[1] replaceText(presentationId, name) const ogpId = downloadImage(presentationId, `${index}.png`, folder) row[2] = ogpId resetText(presentationId, name) }) data.unshift(columnNames) sheet.getRange(1, 1, data.length, data[0].length).setValues(data) } function replaceText(presentationId, name) { const presentation = SlidesApp.openById(presentationId) const slide = presentation.getSlides()[0] slide.replaceAllText('name', name) presentation.saveAndClose() } function downloadImage(presentationId, fileName, folder) { const presentation = SlidesApp.openById(presentationId) const slide = presentation.getSlides()[0] const slideId = slide.getObjectId() const url = `https://docs.google.com/presentation/d/${presentationId}/export/png?id=${presentationId}&pageId=${slideId}` const options = { headers: { Authorization: `Bearer ${ScriptApp.getOAuthToken()}` } } const response = UrlFetchApp.fetch(url, options) const image = response.getAs(MimeType.PNG) image.setName(fileName) const file = folder.createFile(image) return file.getId() } function resetText(presentationId, name) { const presentation = SlidesApp.openById(presentationId) const slide = presentation.getSlides()[0] slide.replaceAllText(name, 'name') presentation.saveAndClose() }
少し長いですが、少しずつ区切ってみていきます。
3-1. 各種変数の設定
const ss = SpreadsheetApp.openById('<SpreadsheetのID>') const sheet = ss.getSheetByName('<Sheetの名前>') const data = sheet.getDataRange().getValues() const columnNames = data.shift() const presentationId = '<SlidesのID>' const folder = DriveApp.getFolderById('<FolderのID>')
最初のconstたちは変数の設定です。
変数 | 説明 |
---|---|
ss |
2で作成したSpreadsheet。<SpreadsheetのID>はhttps://docs.google.com/spreadsheets/d/<この部分>/edit |
sheet |
2で作成したシート |
data |
sheet のデータをArrayで取得したもの |
columnNames |
data の1行目を取り出したもの |
presentationId |
1で作成したSlidesのID。https://docs.google.com/presentation/d/<この部分>/edit#slide=id.p |
folder | 作成したOGP画像を格納しておきたいGoogle Driveのフォルダ。<FolderのID>はhttps://drive.google.com/drive/u/0/folders/<この部分> 。このフォルダは公開設定にしておきます(後述) |
このとき、data
とcolumnNames
は以下のようになっています。
data = [ ['1', 'Test A', ''], ['2', 'Test B', ''], ['3', 'Test C', ''] ] columnNames = ['id', 'name', 'ogpId']
3-2. 置換して画像保存して置換し直す
data.forEach((row, index) => { const name = row[1] replaceText(presentationId, name) const ogpId = downloadImage(presentationId, `${index}.png`, folder) row[2] = ogpId resetText(presentationId, name) })
ここが核です!なにか色々やっているようですが、ほとんどの行は別に作成した関数を呼び出しているのでそこも説明していきます。
まず、全体はdata.forEach
で回しています。
最初にrow[1]
で、Test A, Test B, Test Cをループごとにname
に格納しています。
それに続いて、
- スライドのテキストを
name
に置換(replaceText()
) - スライドをイメージ保存(
downloadImage()
) - 保存したファイルのIDを
data
に追加(row[2] = ogpId
) - スライドのテキストを置換し直す(
resetText()
)
と処理を流しています。
3-2-1. スライドのテキストを置換
スライドのテキストの置換処理を行うreplaceText()
関数を定義しました。
function replaceText(presentationId, name) { const presentation = SlidesApp.openById(presentationId) const slide = presentation.getSlides()[0] slide.replaceAllText('name', name) presentation.saveAndClose() }
引数はpresentationId
と、置換後の文字列name
です。
まず、SlidesApp.openById(presentationId)
で置換したいスライドがあるSlidesを開きます。
そして、getSlides()[0]
を使って、そのSlidesの1枚目のスライドを取得します。
そのslide
に対してreplaceAllText()
を実行することで、slide
の中の'name'
の文字列を引数のname
に置換しています。
最後にpresentation.saveAndClose()
で置換を確定しています。saveAndClose()
を行わないと、次のイメージ保存で置換前の状態で保存されてしまうので要チェックです。
置換の処理はこれだけです。置換したい文字列が複数ある場合でも同じやり方でslide.replaceAllText()
を追加すればできますし、複数のスライドに対してやりたい場合はpresentation
に対してreplaceAllText()
したり、getSlides()
の結果をforEach
で回してスライド1枚ずつに処理することで実現できますね。
3-2-2. スライドを保存
これで動的OGPのイメージの準備ができたので、これをイメージファイルに保存します。今回はpngで。
function downloadImage(presentationId, fileName, folder) { const presentation = SlidesApp.openById(presentationId) const slide = presentation.getSlides()[0] const slideId = slide.getObjectId() const url = `https://docs.google.com/presentation/d/${presentationId}/export/png?id=${presentationId}&pageId=${slideId}` const options = { headers: { Authorization: `Bearer ${ScriptApp.getOAuthToken()}` } } const response = UrlFetchApp.fetch(url, options) const image = response.getAs(MimeType.PNG) image.setName(fileName) const file = folder.createFile(image) return file.getId() }
何をするかというと、SlidesをダウンロードするURLにリクエストを投げて、レスポンスをpngファイルにして保存しようというやり方です。
引数は、presentationId
とfolder
、そしてファイル名としてfileName
を取ります。
最初のpresentation
とslide
の式はreplaceText()
と同じなので説明は省略します。
slideId
はそのスライドのIDのことで、https://docs.google.com/presentation/d/<presentationId>/edit#slide=id.<'この部分'>
です。これはslide.getObjectId()
で取得ができます。
これらの変数を使ってリクエストURL url
を作ります。
リクエストには認証が必要なので、ScriptApp.getOAuthToken()
を使ってBearer認証できるようにoptions
を作っておきます。
このurl
とoptions
を使って、UrlFetchApp.fetch()
でリクエストを投げ、レスポンスをresponse
に格納しています。
response
はHTTPResponseというClassになっているので、画像ファイルとして扱えるようにgetAs(MimeType.PNG)
でpngファイルに変換し、setName()
でファイル名を設定しました。
それを、folder.createFile()
でfolder
にファイルとして保存しているって流れです。
最後に、保存したファイル file
のIDをgetId()
で取得して、返り値にしています。
3-2-3. 保存したファイルのIDをデータに追加
const ogpId = downloadImage(presentationId, `${index}.png`, folder) row[2] = ogpId
先程説明したようにdownloadImage()
は作成したpngファイルのIDをreturnしてます。
それをrow[2]
、つまりogpId
のカラムに設定しています。
詳しくは後ほど紹介しますが、これがNuxtアプリでGoogle Driveの画像ファイルをOGPに設定する肝だったりします。
3-2-4. スライドのテキストを置換し直す
ここまで終わったら次のループのためにスライドの文字列を元の'name'に戻しておきます。このためにresetText()
の関数を用意して呼び出しています。
function resetText(presentationId, name) { const presentation = SlidesApp.openById(presentationId) const slide = presentation.getSlides()[0] slide.replaceAllText(name, 'name') presentation.saveAndClose() }
やっていることはreplaceText()
の逆なので説明は省略!
3-3. スプレッドシートのogpIdを更新
data.unshift(columnNames) sheet.getRange(1, 1, data.length, data[0].length).setValues(data)
最後にSpreadsheetのogpIdカラムを更新しましょう。
すでにここまでの処理でdata
の各Array要素の3つ目の要素にogpId
が格納されているので、Spreadsheetを上書きすればOKですね。
ということで、data.unshift(columnNames)
でdata
の1つ目の要素に列の名前を戻して、
sheet.getRange().setValues()
でデータを上書きしています。
ここまでで画像を作成するステップが完了です。😊 ここからはSpreadsheetをもとにNuxtアプリでOGP画像を動的に設定してみましょう!
4. SpreadsheetをWebApp(API)で公開
次は先程OGPのIDを追記したSpreadsheetをWebAppで公開していきます。 このやり方は、以前に記事を書いたので詳細はそちらを見てください!
GASのコードは以下のようになります。先程のコード.jsに追記していきましょう。
... function doGet() { const users = getUsers() return ContentService .createTextOutput(JSON.stringify(users)) .setMimeType(ContentService.MimeType.JSON) } function getUsers() { const ss = SpreadsheetApp.openById('<SpreadsheetのID>') const sheet = ss.getSheetByName('<シート名>') const table = sheet.getDataRange().getValues() const keys = table.shift() const users = table.map((row) => { const object = {} row.map((value, index) => { object[String(keys[index])] = String(value) }) return object }) return users }
これを、デプロイしてWebApp公開しましょー。
5. Nuxtアプリで動的OGPを設定
まずは、Nuxtアプリで先程公開したWebAppにリクエストする下準備をします。今回はaxiosを使うことを前提として、serverMiddleware経由でWebAppを呼び出します。 こちらも先程の記事に詳細を載せております!
以下の更新、もしくは新規作成を行います。
# nuxt.config.js export default { ... + axios: { + proxy: true, + }, + serverMiddleware: [ + '@api/' + ], ... }
# api/index.js const express = require('express') const axios = require('axios') const app = express() app.get('/', async (req, res) => { const response = axios.get('<WebAppのURL>') res.send(response.data) }) module.exports = { path: '/api/', handler: app, }
これで下準備が完了です。あとはpages
のファイルでfetch()
で情報取得して、head()
で画像のパスを指定してあげるだけです。
今回はpages/index.vue
でクエリパラメーターid
に応じてOGP画像を出し入れしましょう。パスパラメーターとかでもやり方は基本的に変わりないです。
# pages/index.vue ... <script> export default { async fetch() { this.users = await this.$axios.$get('/api') const user = this.$route.query.id ? this.users.find((user) => user.id === this.$route.query.id) : null this.ogpUrl = user ? `https://drive.google.com/uc?export=view&id=${user.ogpId}` : <共通のOGPイメージのパス> }, data() { return { users: [], ogpUrl: null, } }, head() { return { meta: [ { hid: 'og:image', property: 'og:image', content: this.ogpUrl } ] } } } </script> ...
例えばこんな感じ。(nuxt.config.js
で他のタグは設定されている前提です!)
fetch
内でクエリパラメーターidが存在する場合は、usersから同じidのuserを探してます。
見つかったらogpUrl
にhttps://drive.google.com/uc?export=view&id=${user.ogpId}
を、存在しない場合は<共通のOGPイメージのパス>
をセットし、head()
でog:image
のproperty
としてogpUrl
を設定しています。
こうすることでクエリパラメーターid
に応じて動的にOGPを設定することができます。
さて、ここで出てきたhttps://drive.google.com/uc?export=view&id=
です。
Google Drive上で画像ファイルを見ようとするとプレビューモードで表示されると思います。この状態ではコードからすれば画像ファイルとして扱うことができません。
実はhttps://drive.google.com/uc?export=view&id=<表示したい画像ファイルのID>
であれば、プレビューモードではなく画像ファイルとして認識させることができます。
また、これで表示できるのは閲覧権限をもつユーザーのみですので、OGP画像を保存しているフォルダはすべてのユーザーに閲覧権限で公開されている必要があります。
ngrokで公開し、Twitter Card ValidatorでOGPが正しく設定されているか確認してみましょう。
id=1のとき
id=2のとき
動的にOGP画像が表示されてることを確認できました!
まとめ
長くなってしまいましたが、これでSpreadsheetとSlidesを使ってOGP画像を生成しNuxtアプリで動的OGPを実現する一連の流れを紹介させていただきました。 かなり色々なサイトを参考にさせていただいてここまでできたので、この場を借りてお礼を。m( )m GAS、かなり色々なことができるので楽しいですね。
参考
- Slides Service | Apps Script | Google Developers
- Spreadsheet Service | Apps Script | Google Developers
- Drive Service | Apps Script | Google Developers
- [Google Apps Script]Googleスライドのプレゼンテーションを他形式に変換する | 初心者備忘録
- Google ドライブでファイル名とリンクの一覧をお手軽に取得する方法 | DevelopersIO
- Google Drive に保存した画像を直接呼び出せるURLの取得 - Qiita